Skip to content

Commit

Permalink
New format for "trusted_clusters" configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
kontsevoy committed Jun 11, 2016
1 parent 7caccf2 commit 196400f
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 46 deletions.
2 changes: 1 addition & 1 deletion fixtures/trusted_clusters/cluster-a
1 change: 0 additions & 1 deletion fixtures/trusted_clusters/cluster-b

This file was deleted.

53 changes: 45 additions & 8 deletions lib/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,19 @@ func (s *ConfigTestSuite) TestLocateWebAssets(c *check.C) {
}

func (s *ConfigTestSuite) TestTrustedClusters(c *check.C) {
files := []string{
"../../fixtures/trusted_clusters/cluster-a",
}
authorities, err := readTrustedClusters(files)
err := readTrustedClusters(nil, nil)
c.Assert(err, check.IsNil)

var conf service.Config
err = readTrustedClusters([]TrustedCluster{
{
AllowedLogins: "vagrant, root",
KeyFile: "../../fixtures/trusted_clusters/cluster-a",
TunnelAddr: "one,two",
},
}, &conf)
c.Assert(err, check.IsNil)
authorities := conf.Auth.Authorities
c.Assert(len(authorities), check.Equals, 2)
c.Assert(authorities[0].DomainName, check.Equals, "cluster-a")
c.Assert(authorities[0].Type, check.Equals, services.HostCA)
Expand All @@ -228,11 +236,40 @@ func (s *ConfigTestSuite) TestTrustedClusters(c *check.C) {
_, _, _, _, err = ssh.ParseAuthorizedKey(authorities[1].CheckingKeys[0])
c.Assert(err, check.IsNil)

// try to read the file of a wrong format:
authorities, err = readTrustedClusters([]string{"../../README.md"})
tunnels := conf.ReverseTunnels
c.Assert(len(tunnels), check.Equals, 1)
c.Assert(tunnels[0].DomainName, check.Equals, "cluster-a")
c.Assert(len(tunnels[0].DialAddrs), check.Equals, 2)
c.Assert(tunnels[0].DialAddrs[0], check.Equals, "tcp://one:3024")
c.Assert(tunnels[0].DialAddrs[1], check.Equals, "tcp://two:3024")

// invalid data:
err = readTrustedClusters([]TrustedCluster{
{
AllowedLogins: "vagrant, root",
KeyFile: "non-existing",
TunnelAddr: "one,two",
},
}, &conf)
c.Assert(err, check.NotNil)
c.Assert(authorities, check.IsNil)
c.Assert(err, check.ErrorMatches, "^.*invalid file format.*$")
c.Assert(err, check.ErrorMatches, "^.*reading trusted cluster keys.*$")
err = readTrustedClusters([]TrustedCluster{
{
KeyFile: "../../fixtures/trusted_clusters/cluster-a",
TunnelAddr: "one,two",
},
}, &conf)
c.Assert(err, check.ErrorMatches, ".*needs allow_logins parameter")
conf.ReverseTunnels = nil
err = readTrustedClusters([]TrustedCluster{
{
KeyFile: "../../fixtures/trusted_clusters/cluster-a",
AllowedLogins: "vagrant",
TunnelAddr: "",
},
}, &conf)
c.Assert(err, check.IsNil)
c.Assert(len(conf.ReverseTunnels), check.Equals, 0)
}

func (s *ConfigTestSuite) TestApplyConfig(c *check.C) {
Expand Down
96 changes: 67 additions & 29 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,69 +320,107 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error {
}
// read 'trusted_clusters' section:
if fc.Auth.Enabled() && len(fc.Auth.TrustedClusters) > 0 {
authorities, err := readTrustedClusters(fc.Auth.TrustedClusters)
if err != nil {
if err := readTrustedClusters(fc.Auth.TrustedClusters, cfg); err != nil {
return trace.Wrap(err)
}
cfg.Auth.Authorities = append(cfg.Auth.Authorities, authorities...)
}
return nil
}

// readTrustedClusters takes a list of files, parses them and returns a list of CertAuthority
// objects. Each file is exected to have an output of "tctl auth export"
func readTrustedClusters(certFiles []string) (cas []services.CertAuthority, err error) {
if len(certFiles) == 0 {
return nil, nil
// readTrustedClusters parses the content of "trusted_clusters" YAML structure
// and modifies Teleport 'conf' by adding "authorities" and "reverse tunnels"
// to it
func readTrustedClusters(clusters []TrustedCluster, conf *service.Config) error {
if len(clusters) == 0 {
return nil
}
processCert := func(bytes []byte) error {
// is this a "host CA" key (known host)
// parseCAKey() gets called for every line in a "CA key file" which is
// the same as 'known_hosts' format for openssh
parseCAKey := func(bytes []byte) (*services.CertAuthority, error) {
marker, options, pubKey, comment, _, err := ssh.ParseKnownHosts(bytes)
if marker != "cert-authority" {
return trace.Errorf("invalid file format. expected '@cert-authority` marker")
return nil, trace.Errorf("invalid file format. expected '@cert-authority` marker")
}
if err != nil {
return trace.Errorf("invalid public key")
return nil, trace.Errorf("invalid public key")
}
teleportOpts, err := url.ParseQuery(comment)
if err != nil {
return trace.Errorf("invalid key comment: '%s'", comment)
return nil, trace.Errorf("invalid key comment: '%s'", comment)
}
authType := services.CertAuthType(teleportOpts.Get("type"))
if authType != services.HostCA && authType != services.UserCA {
return trace.Errorf("unsupported CA type: '%s'", authType)
return nil, trace.Errorf("unsupported CA type: '%s'", authType)
}
if len(options) == 0 {
return trace.Errorf("key without cluster_name")
return nil, trace.Errorf("key without cluster_name")
}
const prefix = "*."
domainName := strings.TrimPrefix(options[0], prefix)
ca := services.CertAuthority{
return &services.CertAuthority{
CheckingKeys: [][]byte{ssh.MarshalAuthorizedKey(pubKey)},
Type: authType,
DomainName: domainName,
}
cas = append(cas, ca)
return nil
}, nil
}

// go over all certificate files, find and process all certs:
for _, fp := range certFiles {
log.Debugf("reading 'trusted cluster' file %s", fp)
f, err := os.Open(fp)
// go over all trusted clusters:
for i := range clusters {
tc := &clusters[i]
// parse "allow_logins"
var allowedLogins []string
for _, login := range strings.Split(tc.AllowedLogins, ",") {
login = strings.TrimSpace(login)
if login != "" {
allowedLogins = append(allowedLogins, login)
}
}
// open the key file for this cluster:
log.Debugf("reading trusted cluster key file %s", tc.KeyFile)
f, err := os.Open(tc.KeyFile)
if err != nil {
return nil, trace.Errorf("error reading trusted cluster keys: %v", err)
return trace.Errorf("reading trusted cluster keys: %v", err)
}
defer f.Close()
// read the keyfile for this cluster and get trusted CA keys:
var authorities []services.CertAuthority
scanner := bufio.NewScanner(f)
// every line in a file is a cert:
for line := 0; scanner.Scan(); {
if err = processCert(scanner.Bytes()); err != nil {
return nil, trace.Errorf("%s:L%d. %v", fp, line, err)
ca, err := parseCAKey(scanner.Bytes())
if err != nil {
return trace.Errorf("%s:L%d. %v", tc.KeyFile, line, err)
}
if ca.Type == services.UserCA && len(allowedLogins) == 0 && len(tc.TunnelAddr) > 0 {
return trace.Errorf("trusted cluster '%s' needs allow_logins parameter",
ca.DomainName)
}
ca.AllowedLogins = allowedLogins
authorities = append(authorities, *ca)
}
conf.Auth.Authorities = append(conf.Auth.Authorities, authorities...)
clusterName := authorities[0].DomainName
// parse "tunnel_addr"
var tunnelAddresses []string
for _, ta := range strings.Split(tc.TunnelAddr, ",") {
ta := strings.TrimSpace(ta)
if ta == "" {
continue
}
addr, err := utils.ParseHostPortAddr(ta, defaults.SSHProxyTunnelListenPort)
if err != nil {
return trace.Wrap(err,
"Invalid tunnel address '%s' for cluster '%s'. Expect host:port format",
ta, clusterName)
}
tunnelAddresses = append(tunnelAddresses, addr.FullAddress())
}
if len(tunnelAddresses) > 0 {
conf.ReverseTunnels = append(conf.ReverseTunnels, services.ReverseTunnel{
DomainName: clusterName,
DialAddrs: tunnelAddresses,
})
}
}
return cas, nil
return nil
}

// applyString takes 'src' and overwrites target with it, unless 'src' is empty
Expand Down
13 changes: 12 additions & 1 deletion lib/config/fileconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ type Auth struct {

// TrustedClustersFile is a file path to a file containing public CA keys
// of clusters we trust. One key per line, those starting with '#' are comments
TrustedClusters []string `yaml:"trusted_clusters,omitempty"`
TrustedClusters []TrustedCluster `yaml:"trusted_clusters,omitempty"`

// FOR INTERNAL USE:
// Authorities : 3rd party certificate authorities (CAs) this auth service trusts.
Expand All @@ -404,6 +404,17 @@ type Auth struct {
StaticTokens []StaticToken `yaml:"tokens,omitempty"`
}

// TrustedCluster struct holds configuration values under "trusted_clusters" key
type TrustedCluster struct {
// KeyFile is a path to a remote authority (AKA "trusted cluster") public keys
KeyFile string `yaml:"keyfile,omitempty"`
// AllowedLogins is a comma-separated list of user logins allowed from that cluster
AllowedLogins string `yaml:"allow_logins,omitempty"`
// TunnelAddr is a comma-separated list of reverse tunnel addressess to
// connect to
TunnelAddr string `yaml:"tunnel_addr,omitempty"`
}

type StaticToken string

// SSH is 'ssh_service' section of the config file
Expand Down
2 changes: 1 addition & 1 deletion lib/srv/sshserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ func (s *Server) checkPermissionToLogin(cert ssh.PublicKey, teleportUser, osUser

// for other authorities, check for authoritiy permissions
for _, login := range ca.AllowedLogins {
if login == osUser {
if login == osUser || login == "*" {
return nil
}
}
Expand Down
7 changes: 3 additions & 4 deletions vagrant/libvirt/data/opt/a-auth.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ auth_service:
enabled: yes
cluster_name: cluster-a
trusted_clusters:
- /opt/shared/cluster-b
reverse_tunnels:
- domain_name: cluster-b
addresses: [b-auth]
- keyfile: /opt/shared/cluster-b
allow_logins: root,vagrant
tunnel_addr: b-auth

ssh_service:
enabled: yes
Expand Down
2 changes: 1 addition & 1 deletion vagrant/libvirt/data/opt/b-auth.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ auth_service:
enabled: yes
cluster_name: cluster-b
trusted_clusters:
- /opt/shared/cluster-a
- keyfile: /opt/shared/cluster-b

ssh_service:
enabled: yes
Expand Down

0 comments on commit 196400f

Please sign in to comment.