Skip to content

Commit

Permalink
Remote tctl execution. (#2991)
Browse files Browse the repository at this point in the history
* Teach tctl to use remote auth servers and identity.

* Tests and cleanups.
  • Loading branch information
r0mant authored and klizhentas committed Sep 24, 2019
1 parent c3f72ac commit 07b2508
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 209 deletions.
8 changes: 8 additions & 0 deletions lib/client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ type Key struct {
ClusterName string
}

// TLSCAs returns all TLS CA certificates from this key
func (k *Key) TLSCAs() (result [][]byte) {
for _, ca := range k.TrustedCA {
result = append(result, ca.TLSCertificates...)
}
return result
}

// TLSConfig returns client TLS configuration used
// to authenticate against API servers
func (k *Key) ClientTLSConfig() (*tls.Config, error) {
Expand Down
12 changes: 12 additions & 0 deletions lib/utils/addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ func (a *NetAddr) Set(s string) error {
return nil
}

// ParseAddrs parses the provided slice of strings as a slice of NetAddr's.
func ParseAddrs(addrs []string) (result []NetAddr, err error) {
for _, addr := range addrs {
parsed, err := ParseAddr(addr)
if err != nil {
return nil, trace.Wrap(err)
}
result = append(result, *parsed)
}
return result, nil
}

// ParseAddr takes strings like "tcp://host:port/path" and returns
// *NetAddr or an error
func ParseAddr(a string) (*NetAddr, error) {
Expand Down
25 changes: 25 additions & 0 deletions lib/utils/addr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,28 @@ func (s *AddrTestSuite) TestUnmarshal(c *C) {
Commentf("test case %v, %v should be unmarshalled to: %v", i, testCase.in, testCase.expected))
}
}

func (s *AddrTestSuite) TestParseMultiple(c *C) {
tests := []struct {
in []string
out []NetAddr
}{
{
in: []string{
"https://localhost:3080",
"tcp://example:587/path",
"[::1]:465",
},
out: []NetAddr{
{Addr: "localhost:3080", AddrNetwork: "https"},
{Addr: "example:587", AddrNetwork: "tcp", Path: "/path"},
{Addr: "[::1]:465", AddrNetwork: "tcp"},
},
},
}
for _, test := range tests {
parsed, err := ParseAddrs(test.in)
c.Assert(err, IsNil)
c.Assert(parsed, DeepEquals, test.out)
}
}
84 changes: 70 additions & 14 deletions tool/tctl/common/tctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/tool/tsh/common"

"github.com/gravitational/kingpin"
"github.com/gravitational/trace"
Expand All @@ -35,9 +36,16 @@ import (

// GlobalCLIFlags keeps the CLI flags that apply to all tctl commands
type GlobalCLIFlags struct {
Debug bool
ConfigFile string
// Debug enables verbose logging mode to the console
Debug bool
// ConfigFile is the path to the Teleport configuration file
ConfigFile string
// ConfigString is the base64-encoded string with Teleport configuration
ConfigString string
// AuthServerAddr lists addresses of auth servers to connect to
AuthServerAddr []string
// IdentityFilePath is the path to the identity file
IdentityFilePath string
}

// CLICommand interface must be implemented by every CLI command
Expand Down Expand Up @@ -85,6 +93,13 @@ func Run(commands []CLICommand) {
ExistingFileVar(&ccf.ConfigFile)
app.Flag("config-string",
"Base64 encoded configuration string").Hidden().Envar(defaults.ConfigEnvar).StringVar(&ccf.ConfigString)
app.Flag("auth-server",
fmt.Sprintf("Address of the auth server [%v]. Can be supplied multiple times", defaults.AuthConnectAddr().Addr)).
Default(defaults.AuthConnectAddr().Addr).
StringsVar(&ccf.AuthServerAddr)
app.Flag("identity", "Path to the identity file exported with 'tctl auth sign'").
Short('i').
StringVar(&ccf.IdentityFilePath)

// "version" command is always available:
ver := app.Command("version", "Print cluster version")
Expand Down Expand Up @@ -133,19 +148,19 @@ func connectToAuthService(cfg *service.Config) (client auth.ClientI, err error)
*defaults.AuthConnectAddr(),
}
}
// read the host SSH keys and use them to open an SSH connection to the auth service
i, err := auth.ReadLocalIdentity(filepath.Join(cfg.DataDir, teleport.ComponentProcess), auth.IdentityID{Role: teleport.RoleAdmin, HostUUID: cfg.HostUUID})

identity, err := getIdentity(cfg)
if err != nil {
// the "admin" identity is not present? this means the tctl is running NOT on the auth server.
if trace.IsNotFound(err) {
return nil, trace.AccessDenied("tctl must be used on the auth server")
}
return nil, trace.Wrap(err)
}
tlsConfig, err := i.TLSConfig(cfg.CipherSuites)

tlsConfig, err := identity.TLSConfig(cfg.CipherSuites)
if err != nil {
return nil, trace.Wrap(err)
}

logrus.Debugf("Connecting to auth servers: %v.", cfg.AuthServers)

client, err = auth.NewTLSClient(auth.ClientConfig{Addrs: cfg.AuthServers, TLS: tlsConfig})
if err != nil {
return nil, trace.Wrap(err)
Expand All @@ -162,6 +177,27 @@ func connectToAuthService(cfg *service.Config) (client auth.ClientI, err error)
return client, nil
}

// getIdentity returns auth.Identity to use when connecting to auth server
func getIdentity(cfg *service.Config) (*auth.Identity, error) {
// If identity was provided in the configuration, use it
if len(cfg.Identities) != 0 {
return cfg.Identities[0], nil
}
// Otherwise, assume we're running on the auth node and read the host-local
// identity from Teleport data directory
identity, err := auth.ReadLocalIdentity(filepath.Join(cfg.DataDir, teleport.ComponentProcess), auth.IdentityID{Role: teleport.RoleAdmin, HostUUID: cfg.HostUUID})
if err != nil {
// The "admin" identity is not present? This means the tctl is running
// NOT on the auth server
if trace.IsNotFound(err) {
return nil, trace.AccessDenied("tctl must be either used on the auth " +
"server or provided with the identity file via --identity flag")
}
return nil, trace.Wrap(err)
}
return identity, nil
}

// applyConfig takes configuration values from the config file and applies
// them to 'service.Config' object
func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config) error {
Expand All @@ -187,11 +223,31 @@ func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config) error {
utils.InitLogger(utils.LoggingForCLI, logrus.DebugLevel)
logrus.Debugf("DEBUG logging enabled")
}

// read a host UUID for this node
cfg.HostUUID, err = utils.ReadHostUUID(cfg.DataDir)
if err != nil {
utils.FatalError(err)
// --auth-server flag(-s)
if len(ccf.AuthServerAddr) != 0 {
cfg.AuthServers, err = utils.ParseAddrs(ccf.AuthServerAddr)
if err != nil {
return trace.Wrap(err)
}
}
// --identity flag
if ccf.IdentityFilePath != "" {
key, _, err := common.LoadIdentity(ccf.IdentityFilePath)
if err != nil {
return trace.Wrap(err)
}
identity, err := auth.ReadTLSIdentityFromKeyPair(key.Priv, key.TLSCert, key.TLSCAs())
if err != nil {
return trace.Wrap(err)
}
cfg.Identities = append(cfg.Identities, identity)
} else {
// read the host UUID only in case the identity was not provided,
// because it will be used for reading local auth server identity
cfg.HostUUID, err = utils.ReadHostUUID(cfg.DataDir)
if err != nil {
return trace.Wrap(err)
}
}
return nil
}
Loading

0 comments on commit 07b2508

Please sign in to comment.