Skip to content

Commit

Permalink
Fetch trusted CA from the main cluster. (#2487)
Browse files Browse the repository at this point in the history
This PR fixes an issue with tsh login.

Here is a flaw in logic described using the following
scenario:

Assume there are two clusters, 'main' and 'east'.

1. User logs into the first cluster 'main'
2. Selects the cluster 'east' in the profile
3. Next day, logs in again
4. Client pulls the trusted CA from the cluster 'main'
as a part of SSH login procedure and adds to the keystore
5. Client connects to cluster 'east' because it is
set as a current cluster in the profile
6. Client attempts to connect to the auth server of the cluster
'east' and fails because it does not trust the certificate
of the 'east' yet, only 'main.

This PR fixes the issue by making sure the client
always connects to the cluster 'main' in the step 5 instead.
  • Loading branch information
klizhentas authored Jan 16, 2019
1 parent f5af491 commit 7fc238e
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 21 deletions.
2 changes: 1 addition & 1 deletion integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ func (s *IntSuite) TestTwoClusters(c *check.C) {
c.Assert(outputA.String(), check.Equals, "hello world\n")

// Update trusted CAs.
err = tc.UpdateTrustedCA(context.TODO())
err = tc.UpdateTrustedCA(context.TODO(), a.Secrets.SiteName)
c.Assert(err, check.IsNil)

// The known_hosts file should have two certificates, the way bytes.Split
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ func (a *AuthWithRoles) NewWatcher(ctx context.Context, watch services.Watch) (s
for _, kind := range watch.Kinds {
switch kind {
case services.KindCertAuthority:
if err := a.action(defaults.Namespace, services.KindCertAuthority, services.VerbRead); err != nil {
if err := a.action(defaults.Namespace, services.KindCertAuthority, services.VerbReadNoSecrets); err != nil {
return nil, trace.Wrap(err)
}
default:
Expand Down
17 changes: 11 additions & 6 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ func (tc *TeleportClient) Join(ctx context.Context, namespace string, sessionID
return trace.Wrap(err)
}
defer proxyClient.Close()
site, err := proxyClient.ConnectToSite(ctx, false)
site, err := proxyClient.ConnectToCurrentCluster(ctx, false)
if err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -985,7 +985,7 @@ func (tc *TeleportClient) Play(ctx context.Context, namespace, sessionId string)
if err != nil {
return trace.Wrap(err)
}
site, err := proxyClient.ConnectToSite(ctx, false)
site, err := proxyClient.ConnectToCurrentCluster(ctx, false)
if err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -1580,6 +1580,11 @@ func (tc *TeleportClient) Login(ctx context.Context, activateKey bool) (*Key, er
key.Cert = response.Cert
key.TLSCert = response.TLSCert

if len(response.HostSigners) <= 0 {
return nil, trace.BadParameter("bad response from the server: expected at least one certificate, got 0")
}
key.ClusterName = response.HostSigners[0].ClusterName

if activateKey {
// save the list of CAs client trusts to ~/.tsh/known_hosts
err = tc.localAgent.AddHostSignersToCache(response.HostSigners)
Expand All @@ -1604,7 +1609,7 @@ func (tc *TeleportClient) Login(ctx context.Context, activateKey bool) (*Key, er

// GetTrustedCA returns a list of host certificate authorities
// trusted by the cluster client is authenticated with.
func (tc *TeleportClient) GetTrustedCA(ctx context.Context) ([]services.CertAuthority, error) {
func (tc *TeleportClient) GetTrustedCA(ctx context.Context, clusterName string) ([]services.CertAuthority, error) {
// Connect to the proxy.
if !tc.Config.ProxySpecified() {
return nil, trace.BadParameter("proxy server is not specified")
Expand All @@ -1616,7 +1621,7 @@ func (tc *TeleportClient) GetTrustedCA(ctx context.Context) ([]services.CertAuth
defer proxyClient.Close()

// Get a client to the Auth Server.
clt, err := proxyClient.ClusterAccessPoint(ctx, true)
clt, err := proxyClient.ClusterAccessPoint(ctx, clusterName, true)
if err != nil {
return nil, trace.Wrap(err)
}
Expand All @@ -1627,9 +1632,9 @@ func (tc *TeleportClient) GetTrustedCA(ctx context.Context) ([]services.CertAuth

// UpdateTrustedCA connects to the Auth Server and fetches all host certificates
// and updates ~/.tsh/keys/proxy/certs.pem and ~/.tsh/known_hosts.
func (tc *TeleportClient) UpdateTrustedCA(ctx context.Context) error {
func (tc *TeleportClient) UpdateTrustedCA(ctx context.Context, clusterName string) error {
// Get the list of host certificates that this cluster knows about.
hostCerts, err := tc.GetTrustedCA(ctx)
hostCerts, err := tc.GetTrustedCA(ctx, clusterName)
if err != nil {
return trace.Wrap(err)
}
Expand Down
39 changes: 28 additions & 11 deletions lib/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (proxy *ProxyClient) FindServersByLabels(ctx context.Context, namespace str
return nil, trace.BadParameter(auth.MissingNamespaceError)
}
nodes := make([]services.Server, 0)
site, err := proxy.ClusterAccessPoint(ctx, false)
site, err := proxy.CurrentClusterAccessPoint(ctx, false)
if err != nil {
return nil, trace.Wrap(err)
}
Expand All @@ -135,35 +135,52 @@ func (proxy *ProxyClient) FindServersByLabels(ctx context.Context, namespace str
return nodes, nil
}

// ClusterAccessPoint returns cluster access point used for discovery
// CurrentClusterAccessPoint returns cluster access point to the currently
// selected cluster and is used for discovery
// and could be cached based on the access policy
func (proxy *ProxyClient) ClusterAccessPoint(ctx context.Context, quiet bool) (auth.AccessPoint, error) {
func (proxy *ProxyClient) CurrentClusterAccessPoint(ctx context.Context, quiet bool) (auth.AccessPoint, error) {
// get the current cluster:
cluster, err := proxy.currentCluster()
if err != nil {
return nil, trace.Wrap(err)
}
clt, err := proxy.ConnectToSite(ctx, quiet)
return proxy.ClusterAccessPoint(ctx, cluster.Name, quiet)
}

// ClusterAccessPoint returns cluster access point used for discovery
// and could be cached based on the access policy
func (proxy *ProxyClient) ClusterAccessPoint(ctx context.Context, clusterName string, quiet bool) (auth.AccessPoint, error) {
if clusterName == "" {
return nil, trace.BadParameter("parameter clusterName is missing")
}
clt, err := proxy.ConnectToCluster(ctx, clusterName, quiet)
if err != nil {
return nil, trace.Wrap(err)
}
return proxy.teleportClient.accessPoint(clt, proxy.proxyAddress, cluster.Name)
return proxy.teleportClient.accessPoint(clt, proxy.proxyAddress, clusterName)
}

// ConnectToSite connects to the auth server of the given site via proxy.
// It returns connected and authenticated auth server client
// ConnectToCurrentCluster connects to the auth server of the currently selected
// cluster via proxy. It returns connected and authenticated auth server client
//
// if 'quiet' is set to true, no errors will be printed to stdout, otherwise
// any connection errors are visible to a user.
func (proxy *ProxyClient) ConnectToSite(ctx context.Context, quiet bool) (auth.ClientI, error) {
// get the current cluster:
site, err := proxy.currentCluster()
func (proxy *ProxyClient) ConnectToCurrentCluster(ctx context.Context, quiet bool) (auth.ClientI, error) {
cluster, err := proxy.currentCluster()
if err != nil {
return nil, trace.Wrap(err)
}
return proxy.ConnectToCluster(ctx, cluster.Name, quiet)
}

// ConnectToCluster connects to the auth server of the given cluster via proxy.
// It returns connected and authenticated auth server client
//
// if 'quiet' is set to true, no errors will be printed to stdout, otherwise
// any connection errors are visible to a user.
func (proxy *ProxyClient) ConnectToCluster(ctx context.Context, clusterName string, quiet bool) (auth.ClientI, error) {
dialer := func(ctx context.Context, network, _ string) (net.Conn, error) {
return proxy.dialAuthServer(ctx, site.Name)
return proxy.dialAuthServer(ctx, clusterName)
}

if proxy.teleportClient.SkipLocalAuth {
Expand Down
3 changes: 3 additions & 0 deletions lib/client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ type Key struct {

// TrustedCA is a list of trusted certificate authorities
TrustedCA []auth.TrustedCerts

// ClusterName is a cluster name this key is associated with
ClusterName string
}

// TLSConfig returns client TLS configuration used
Expand Down
4 changes: 2 additions & 2 deletions tool/tsh/tsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ func onLogin(cf *CLIConf) {
if err := setupNoninteractiveClient(tc, key); err != nil {
utils.FatalError(err)
}
authorities, err := tc.GetTrustedCA(cf.Context)
authorities, err := tc.GetTrustedCA(cf.Context, key.ClusterName)
if err != nil {
utils.FatalError(err)
}
Expand All @@ -439,7 +439,7 @@ func onLogin(cf *CLIConf) {
tc.SaveProfile("")

// Connect to the Auth Server and fetch the known hosts for this cluster.
err = tc.UpdateTrustedCA(cf.Context)
err = tc.UpdateTrustedCA(cf.Context, key.ClusterName)
if err != nil {
utils.FatalError(err)
}
Expand Down

0 comments on commit 7fc238e

Please sign in to comment.