Added support for auth-server and tokens

This commit is contained in:
Ev Kontsevoy 2016-02-08 20:55:13 -08:00
parent 058f6eb9cf
commit d6d5cb7d9c
8 changed files with 211 additions and 43 deletions

View file

@ -13,6 +13,11 @@ export GO15VENDOREXPERIMENT=1
teleport:
go build -o teleport -i github.com/gravitational/teleport/tool/teleport
.PHONY: t
t:
go test github.com/gravitational/teleport/lib/utils/...
all:
go build -o teleport -i github.com/gravitational/teleport/tool/teleport
go build -o tctl -i github.com/gravitational/teleport/tool/tctl

View file

@ -65,9 +65,16 @@ const (
LimiterMaxConcurrentUsers = 25
)
// list of roles teleport service can run as:
const (
RoleNode = "node"
RoleProxy = "proxy"
RoleAuthService = "auth"
)
var (
// Default roles teleport assumes when started via 'start' command
StartRoles = []string{"node", "proxy", "auth"}
StartRoles = []string{RoleProxy, RoleNode, RoleAuthService}
)
const (
@ -82,33 +89,38 @@ func ConfigureLimiter(lc *limiter.LimiterConfig) {
// AuthListenAddr returns the default listening address for the Auth service
func AuthListenAddr() *utils.NetAddr {
return makeDefaultAddr(AuthListenPort)
return makeAddr(BindIP, AuthListenPort)
}
// AuthConnectAddr returns the default address to search for auth. service on
func AuthConnectAddr() *utils.NetAddr {
return makeAddr("127.0.0.1", AuthListenPort)
}
// ProxyListenAddr returns the default listening address for the SSH Proxy service
func ProxyListenAddr() *utils.NetAddr {
return makeDefaultAddr(SSHProxyListenPort)
return makeAddr(BindIP, SSHProxyListenPort)
}
// ProxyWebListenAddr returns the default listening address for the Web-based SSH Proxy service
func ProxyWebListenAddr() *utils.NetAddr {
return makeDefaultAddr(HTTPListenPort)
return makeAddr(BindIP, HTTPListenPort)
}
// SSHServerListenAddr returns the default listening address for the Web-based SSH Proxy service
func SSHServerListenAddr() *utils.NetAddr {
return makeDefaultAddr(SSHServerListenPort)
return makeAddr(BindIP, SSHServerListenPort)
}
// ReverseTunnellAddr returns the default listening address for the SSH Proxy service used
// by the SSH nodes to establish proxy<->ssh_node connection from behind a firewall which
// blocks inbound connecions to ssh_nodes
func ReverseTunnellAddr() *utils.NetAddr {
return makeDefaultAddr(SSHProxyTunnelListenPort)
return makeAddr(BindIP, SSHProxyTunnelListenPort)
}
func makeDefaultAddr(port int16) *utils.NetAddr {
addrSpec := fmt.Sprintf("tcp://%v:%d", BindIP, port)
func makeAddr(host string, port int16) *utils.NetAddr {
addrSpec := fmt.Sprintf("tcp://%s:%d", host, port)
retval, err := utils.ParseAddr(addrSpec)
if err != nil {
panic(fmt.Sprintf("%s: error parsing '%v'", initError, addrSpec))

View file

@ -18,6 +18,7 @@ package service
import (
"encoding/base64"
"encoding/json"
"gopkg.in/yaml.v2"
"io"
"io/ioutil"
@ -78,6 +79,16 @@ func (cfg *Config) RoleConfig() RoleConfig {
}
}
// DebugDumpToYAML() is useful for debugging: it dumps the Config structure into
// a string
func (cfg *Config) DebugDumpToYAML() string {
out, err := yaml.Marshal(cfg)
if err != nil {
return err.Error()
}
return string(out)
}
type ProxyConfig struct {
// Enabled turns proxy role on or off for this process
Enabled bool `yaml:"enabled" env:"TELEPORT_PROXY_ENABLED"`

View file

@ -17,7 +17,9 @@ package utils
import (
"fmt"
"net"
"net/url"
"strconv"
"strings"
)
@ -71,6 +73,8 @@ func (a *NetAddr) Set(s string) error {
return nil
}
// ParseAddr takes strings like "tcp://host:port/path" and returns
// *NetAddr or an error
func ParseAddr(a string) (*NetAddr, error) {
u, err := url.Parse(a)
if err != nil {
@ -86,6 +90,24 @@ func ParseAddr(a string) (*NetAddr, error) {
}
}
// ParseHostPortAddr takes strings like "host:port" and returns
// *NetAddr or an error
//
// If defaultPort == -1 it expects 'hostport' string to have it
func ParseHostPortAddr(hostport string, defaultPort int) (*NetAddr, error) {
host, port, err := net.SplitHostPort(hostport)
if err != nil {
if defaultPort > 0 {
host, port, err = net.SplitHostPort(
net.JoinHostPort(hostport, strconv.Itoa(defaultPort)))
}
if err != nil {
return nil, fmt.Errorf("failed to parse '%v': %v", hostport, err)
}
}
return ParseAddr(fmt.Sprintf("tcp://%s", net.JoinHostPort(host, port)))
}
func NewNetAddrVal(defaultVal NetAddr, val *NetAddr) *NetAddrVal {
*val = defaultVal
return (*NetAddrVal)(val)

62
lib/utils/addr_test.go Normal file
View file

@ -0,0 +1,62 @@
/*
Copyright 2015 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
. "gopkg.in/check.v1"
"testing"
)
func TestAddrSturct(t *testing.T) { TestingT(t) }
type AddrTestSuite struct {
}
var _ = Suite(&AddrTestSuite{})
func (s *AddrTestSuite) TestParseHostPort(c *C) {
// success
addr, err := ParseHostPortAddr("localhost:22", -1)
c.Assert(err, IsNil)
c.Assert(addr.AddrNetwork, Equals, "tcp")
c.Assert(addr.Addr, Equals, "localhost:22")
// success
addr, err = ParseHostPortAddr("localhost", 1111)
c.Assert(err, IsNil)
c.Assert(addr.AddrNetwork, Equals, "tcp")
c.Assert(addr.Addr, Equals, "localhost:1111")
// missing port
addr, err = ParseHostPortAddr("localhost", -1)
c.Assert(err, NotNil)
c.Assert(addr, IsNil)
}
func (s *AddrTestSuite) TestEmpty(c *C) {
var a NetAddr
c.Assert(a.IsEmpty(), Equals, true)
}
func (s *AddrTestSuite) TestParse(c *C) {
addr, err := ParseAddr("tcp://one:25/path")
c.Assert(err, IsNil)
c.Assert(addr, NotNil)
c.Assert(addr.Addr, Equals, "one:25")
c.Assert(addr.Path, Equals, "/path")
c.Assert(addr.FullAddress(), Equals, "tcp://one:25")
c.Assert(addr.IsEmpty(), Equals, false)
}

View file

@ -26,15 +26,17 @@ import (
"net"
"os"
"path/filepath"
"strings"
)
// CLIConfig represents command line flags+args
type CLIConfig struct {
ProxyAddr string
ListenIP net.IP
ConfigFile string
Roles []string
Debug bool
AuthServerAddr string
AuthToken string
ListenIP net.IP
ConfigFile string
Roles string
Debug bool
}
// confnigure merges command line arguments with what's in a configuration file
@ -52,6 +54,9 @@ func configure(ccf *CLIConfig) (cfg service.Config, err error) {
}
// parse the config file. these values will override defaults:
utils.ConsoleMessage(os.Stdout, "Using config file: %s", configPath)
// TODO: replace with simplified config file
log.Warning("Need to implement simplified config file")
if err := service.ParseYAMLFile(configPath, &cfg); err != nil {
return cfg, err
}
@ -65,9 +70,43 @@ func configure(ccf *CLIConfig) (cfg service.Config, err error) {
utils.InitLoggerDebug()
}
// apply --auth-server flag:
if ccf.AuthServerAddr != "" {
addr, err := utils.ParseHostPortAddr(ccf.AuthServerAddr, int(defaults.AuthListenPort))
if err != nil {
return cfg, err
}
log.Infof("Using auth server: %v", addr.FullAddress())
cfg.AuthServers = append(cfg.AuthServers, *addr)
}
// apply --token flag:
if ccf.AuthToken != "" {
log.Infof("Using auth token: %s", ccf.AuthToken)
cfg.SSH.Token = ccf.AuthToken
cfg.Proxy.Token = ccf.AuthToken
}
// apply --roles flag:
if ccf.Roles != "" {
if err := validateRoles(ccf.Roles); err != nil {
log.Error(err.Error())
return cfg, err
}
if strings.Index(ccf.Roles, defaults.RoleNode) == -1 {
cfg.SSH.Enabled = false
}
if strings.Index(ccf.Roles, defaults.RoleAuthService) == -1 {
cfg.Auth.Enabled = false
}
if strings.Index(ccf.Roles, defaults.RoleProxy) == -1 {
cfg.Proxy.Enabled = false
cfg.ReverseTunnel.Enabled = false
}
}
// apply --listen-ip flag:
if ccf.ListenIP != nil {
log.Infof("applying listen-ip flag: '%v'", ccf.ListenIP)
if err = applyListenIP(ccf.ListenIP, &cfg); err != nil {
return cfg, err
}
@ -126,8 +165,10 @@ func applyDefaults(cfg *service.Config) error {
cfg.Proxy.AssetsDir = defaults.DataDir
cfg.Proxy.SSHAddr = *defaults.ProxyListenAddr()
cfg.Proxy.WebAddr = *defaults.ProxyWebListenAddr()
cfg.ReverseTunnel.Enabled = true
cfg.Proxy.ReverseTunnelListenAddr = *defaults.ReverseTunnellAddr()
defaults.ConfigureLimiter(&cfg.Proxy.Limiter)
defaults.ConfigureLimiter(&cfg.ReverseTunnel.Limiter)
// defaults for the SSH service:
cfg.SSH.Enabled = true
@ -137,7 +178,9 @@ func applyDefaults(cfg *service.Config) error {
// global defaults
cfg.Hostname = hostname
cfg.DataDir = defaults.DataDir
cfg.AuthServers = []utils.NetAddr{cfg.Auth.SSHAddr}
if cfg.Auth.Enabled {
cfg.AuthServers = []utils.NetAddr{cfg.Auth.SSHAddr}
}
cfg.Console = os.Stdout
return nil
}
@ -155,3 +198,18 @@ func fileExists(fp string) bool {
}
return true
}
// validateRoles makes sure that value upassed to --roles flag is valid
func validateRoles(roles string) error {
for _, role := range strings.Split(roles, ",") {
switch role {
case defaults.RoleAuthService,
defaults.RoleNode,
defaults.RoleProxy:
break
default:
return fmt.Errorf("unknown role: '%s'", role)
}
}
return nil
}

View file

@ -53,17 +53,17 @@ func main() {
start.Flag("roles",
fmt.Sprintf("Comma-separated list of roles to start with [%s]", strings.Join(defaults.StartRoles, ","))).
Short('r').
StringsVar(&ccf.Roles)
StringVar(&ccf.Roles)
start.Flag("listen-ip",
fmt.Sprintf("IP address to bind to [%s]", defaults.BindIP)).
Short('l').
IPVar(&ccf.ListenIP)
start.Flag("proxy",
"Address and port of the proxy server [none]").
StringVar(&ccf.ProxyAddr)
start.Flag("proxy-token",
"One-time join token to connect to a proxy [none]").
StringVar(&ccf.ProxyAddr)
start.Flag("auth-server",
fmt.Sprintf("Address of the auth server [%s]", defaults.AuthConnectAddr().Addr)).
StringVar(&ccf.AuthServerAddr)
start.Flag("token",
"One-time token to register with an auth server [none]").
StringVar(&ccf.AuthToken)
start.Flag("config",
fmt.Sprintf("Path to a configuration file [%v]", defaults.ConfigFilePath)).
Short('c').

View file

@ -4,20 +4,16 @@ const (
usageNotes = `
Notes:
--no-ssh=false
--roles=node,proxy,auth
When set, Teleport does not start the SSH service, only allowing proxied connections to
other SSH nodes.
Use this flag to tell Teleport which services to run. By default it runs all three, but
in a production environment you may want to separate them all.
--proxy-addr=<host>[:port]/<token>
--token=xyz
Tells teleport to run as an SSH node behind the given proxy host. You need to obtain the
token by executing "tctl nodes add" on the host where Teleport proxy is running.
--advertise-ip=ip_address
When connecting to a proxy from behind a NAT, this tells the proxy which IP to find
this node on.
This token is needed to connect any node (web proxy or SSH service) to an auth server.
Obtain it by running "tctl nodes add" on the auth server. It is only used once and ignored
on subsequent restarts.
`
usageExamples = `
@ -25,20 +21,22 @@ Examples:
> teleport start
Without cofiguration, teleport starts by default in a "showcase mode": it becomes an
SSH server and a proxy to itself with a Web UI.
Without any cofiguration teleport starts in a "showroom mode": it's the equivalent of
running with --roles=node,proxy,auth
> teleport start --no-ssh
> teleport start --listen-ip=10.5.0.1 --roles=node --auth-server=10.5.0.2 --token=xyz
Starts teleport in a proxy+auth mode, serving the Web UI for 2-factor auth. You must
execute 'tctl nodes add' now to generate one-time tokens to add nodes to the cluster.
Starts a SSH node listening on 10.5.0.1 and authenticating incoming clients via the
auth server running on 10.5.0.2.
> teleport start --proxy-addr=bastion.host:3023/token \
--listen_interface=0.0.0.0 \
--advertise_interface=10.0.1.50
> teleport start --roles=proxy,auth
Starts teleport as an SSH node connected to the SSH proxy/bastion on bastion.host:3023
Tells the proxy that this node is reachable via 10.0.1.50
Starts Teleport auth server with a web proxy (which also serves Web UI).
> teleport start --roles=proxy --auth-server=10.5.0.2 --token=xyz
Starts Teleport Web proxy and configure it to authenticate/authorize against an auth
server running on 10.5.0.2
`
sampleConfig = `##