Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "tsh kube" commands #4769

Merged
merged 1 commit into from
Nov 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e
Submodule e updated from 166abf to c3ac85
2 changes: 1 addition & 1 deletion lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ func (a *Server) generateUserCert(req certRequest) (*certs, error) {
// Only validate/default kubernetes cluster name for the current teleport
// cluster. If this cert is targeting a trusted teleport cluster, leave all
// the kubernetes cluster validation up to them.
if req.routeToCluster == clusterName {
if req.routeToCluster == "" || req.routeToCluster == clusterName {
req.kubernetesCluster, err = kubeutils.CheckOrSetKubeCluster(a.closeCtx, a.Presence, req.kubernetesCluster, clusterName)
if err != nil {
if !trace.IsNotFound(err) {
Expand Down
75 changes: 75 additions & 0 deletions lib/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,81 @@ func (s *AuthSuite) TestAuthenticateSSHUser(c *C) {
c.Assert(err, IsNil)
c.Assert(*gotID, DeepEquals, wantID)

// Register a kubernetes cluster to verify the defaulting logic in TLS cert
// generation.
err = s.a.UpsertKubeService(ctx, &services.ServerV2{
Metadata: services.Metadata{Name: "kube-service"},
Kind: services.KindKubeService,
Version: services.V2,
Spec: services.ServerSpecV2{
KubernetesClusters: []*services.KubernetesCluster{{Name: "root-kube-cluster"}},
},
})
c.Assert(err, IsNil)

// Login specifying a valid kube cluster. It should appear in the TLS cert.
resp, err = s.a.AuthenticateSSHUser(AuthenticateSSHRequest{
AuthenticateUserRequest: AuthenticateUserRequest{
Username: user,
Pass: &PassCreds{Password: pass},
},
PublicKey: pub,
TTL: time.Hour,
RouteToCluster: "me.localhost",
KubernetesCluster: "root-kube-cluster",
})
c.Assert(err, IsNil)
c.Assert(resp.Username, Equals, user)
gotTLSCert, err = tlsca.ParseCertificatePEM(resp.TLSCert)
c.Assert(err, IsNil)
wantID = tlsca.Identity{
Username: user,
Groups: []string{role.GetName()},
Principals: []string{user},
KubernetesUsers: []string{user},
KubernetesGroups: []string{"system:masters"},
KubernetesCluster: "root-kube-cluster",
Expires: gotTLSCert.NotAfter,
RouteToCluster: "me.localhost",
TeleportCluster: "me.localhost",
}
gotID, err = tlsca.FromSubject(gotTLSCert.Subject, gotTLSCert.NotAfter)
c.Assert(err, IsNil)
c.Assert(*gotID, DeepEquals, wantID)

// Login without specifying kube cluster. A registered one should be picked
// automatically.
resp, err = s.a.AuthenticateSSHUser(AuthenticateSSHRequest{
AuthenticateUserRequest: AuthenticateUserRequest{
Username: user,
Pass: &PassCreds{Password: pass},
},
PublicKey: pub,
TTL: time.Hour,
RouteToCluster: "me.localhost",
// Intentionally empty, auth server should default to a registered
// kubernetes cluster.
KubernetesCluster: "",
})
c.Assert(err, IsNil)
c.Assert(resp.Username, Equals, user)
gotTLSCert, err = tlsca.ParseCertificatePEM(resp.TLSCert)
c.Assert(err, IsNil)
wantID = tlsca.Identity{
Username: user,
Groups: []string{role.GetName()},
Principals: []string{user},
KubernetesUsers: []string{user},
KubernetesGroups: []string{"system:masters"},
KubernetesCluster: "root-kube-cluster",
Expires: gotTLSCert.NotAfter,
RouteToCluster: "me.localhost",
TeleportCluster: "me.localhost",
}
gotID, err = tlsca.FromSubject(gotTLSCert.Subject, gotTLSCert.NotAfter)
c.Assert(err, IsNil)
c.Assert(*gotID, DeepEquals, wantID)

// Login specifying an invalid kube cluster. This should fail.
_, err = s.a.AuthenticateSSHUser(AuthenticateSSHRequest{
AuthenticateUserRequest: AuthenticateUserRequest{
Expand Down
21 changes: 18 additions & 3 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ func readProfile(profileDir string, profileName string) (*ProfileStatus, error)
if err != nil {
return nil, trace.Wrap(err)
}
key, err := store.GetKey(profile.Name(), profile.Username)
key, err := store.GetKey(profile.Name(), profile.Username, WithKubeCerts(profile.SiteName))
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down Expand Up @@ -474,7 +474,7 @@ func readProfile(profileDir string, profileName string) (*ProfileStatus, error)
clusterName = profile.Name()
}

tlsCert, err := key.TLSCertificate()
tlsCert, err := key.TeleportTLSCertificate()
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down Expand Up @@ -906,6 +906,8 @@ func (tc *TeleportClient) ReissueUserCerts(ctx context.Context, params ReissuePa
if err != nil {
return trace.Wrap(err)
}
defer proxyClient.Close()

return proxyClient.ReissueUserCerts(ctx, params)
}

Expand All @@ -915,6 +917,8 @@ func (tc *TeleportClient) CreateAccessRequest(ctx context.Context, req services.
if err != nil {
return trace.Wrap(err)
}
defer proxyClient.Close()

return proxyClient.CreateAccessRequest(ctx, req)
}

Expand All @@ -924,6 +928,8 @@ func (tc *TeleportClient) GetAccessRequests(ctx context.Context, filter services
if err != nil {
return nil, trace.Wrap(err)
}
defer proxyClient.Close()

return proxyClient.GetAccessRequests(ctx, filter)
}

Expand All @@ -933,6 +939,8 @@ func (tc *TeleportClient) GetRole(ctx context.Context, name string) (services.Ro
if err != nil {
return nil, trace.Wrap(err)
}
defer proxyClient.Close()

return proxyClient.GetRole(ctx, name)
}

Expand All @@ -942,6 +950,8 @@ func (tc *TeleportClient) NewWatcher(ctx context.Context, watch services.Watch)
if err != nil {
return nil, trace.Wrap(err)
}
defer proxyClient.Close()

return proxyClient.NewWatcher(ctx, watch)
}

Expand Down Expand Up @@ -1148,6 +1158,8 @@ func (tc *TeleportClient) Play(ctx context.Context, namespace, sessionID string)
if err != nil {
return trace.Wrap(err)
}
defer proxyClient.Close()

site, err := proxyClient.ConnectToCurrentCluster(ctx, false)
if err != nil {
return trace.Wrap(err)
Expand Down Expand Up @@ -1662,7 +1674,7 @@ func (tc *TeleportClient) Logout() error {
if tc.localAgent == nil {
return nil
}
if err := tc.localAgent.DeleteKey(); err != nil {
if err := tc.localAgent.DeleteKey(WithKubeCerts(tc.SiteName)); err != nil {
return trace.Wrap(err)
}

Expand Down Expand Up @@ -1750,6 +1762,9 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) {
// extract the new certificate out of the response
key.Cert = response.Cert
key.TLSCert = response.TLSCert
if tc.KubernetesCluster != "" {
key.KubeTLSCerts[tc.KubernetesCluster] = response.TLSCert
}
key.ProxyHost = webProxyHost
key.TrustedCA = response.HostSigners

Expand Down
11 changes: 7 additions & 4 deletions lib/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,15 @@ type ReissueParams struct {
// that have a metadata instructing server to route the requests to the cluster
func (proxy *ProxyClient) ReissueUserCerts(ctx context.Context, params ReissueParams) error {
localAgent := proxy.teleportClient.LocalAgent()
key, err := localAgent.GetKey()
key, err := localAgent.GetKey(WithKubeCerts(params.RouteToCluster))
if err != nil {
return trace.Wrap(err)
}
cert, err := key.SSHCert()
if err != nil {
return trace.Wrap(err)
}
tlsCert, err := key.TLSCertificate()
tlsCert, err := key.TeleportTLSCertificate()
if err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -202,6 +202,9 @@ func (proxy *ProxyClient) ReissueUserCerts(ctx context.Context, params ReissuePa
}
key.Cert = certs.SSH
key.TLSCert = certs.TLS
if params.KubernetesCluster != "" {
key.KubeTLSCerts[params.KubernetesCluster] = certs.TLS
}

// save the cert to the local storage (~/.tsh usually):
_, err = localAgent.AddKey(key)
Expand All @@ -215,7 +218,7 @@ func (proxy *ProxyClient) RootClusterName() (string, error) {
if err != nil {
return "", trace.Wrap(err)
}
tlsCert, err := key.TLSCertificate()
tlsCert, err := key.TeleportTLSCertificate()
if err != nil {
return "", trace.Wrap(err)
}
Expand Down Expand Up @@ -378,7 +381,7 @@ func (proxy *ProxyClient) ConnectToCluster(ctx context.Context, clusterName stri
if err != nil {
return nil, trace.Wrap(err, "failed to fetch TLS key for %v", proxy.teleportClient.Username)
}
tlsConfig, err := key.ClientTLSConfig(nil)
tlsConfig, err := key.TeleportClientTLSConfig(nil)
if err != nil {
return nil, trace.Wrap(err, "failed to generate client TLS config")
}
Expand Down
6 changes: 3 additions & 3 deletions lib/client/identityfile/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ func Write(filePath string, key *client.Key, format Format, clusterAddr string)
case FormatKubernetes:
filesWritten = append(filesWritten, filePath)
if err := kubeconfig.Update(filePath, kubeconfig.Values{
Name: key.ClusterName,
ClusterAddr: clusterAddr,
Credentials: key,
TeleportClusterName: key.ClusterName,
ClusterAddr: clusterAddr,
Credentials: key,
}); err != nil {
return nil, trace.Wrap(err)
}
Expand Down
63 changes: 40 additions & 23 deletions lib/client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
package client

import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
Expand Down Expand Up @@ -48,8 +47,12 @@ type Key struct {
Pub []byte `json:"Pub,omitempty"`
// Cert is an SSH client certificate
Cert []byte `json:"Cert,omitempty"`
// TLSCert is a PEM encoded client TLS x509 certificate
// TLSCert is a PEM encoded client TLS x509 certificate.
// It's used to authenticate to the Teleport APIs.
TLSCert []byte `json:"TLSCert,omitempty"`
// KubeTLSCerts are TLS certificates (PEM-encoded) for individual
// kubernetes clusters. Map key is a kubernetes cluster name.
KubeTLSCerts map[string][]byte `json:"KubeCerts,omitempty"`

// ProxyHost (optionally) contains the hostname of the proxy server
// which issued this key
Expand All @@ -71,8 +74,9 @@ func NewKey() (key *Key, err error) {
}

return &Key{
Priv: priv,
Pub: pub,
Priv: priv,
Pub: pub,
KubeTLSCerts: make(map[string][]byte),
}, nil
}

Expand All @@ -92,9 +96,23 @@ func (k *Key) SSHCAs() (result [][]byte) {
return result
}

// TLSConfig returns client TLS configuration used
// to authenticate against API servers
func (k *Key) ClientTLSConfig(cipherSuites []uint16) (*tls.Config, error) {
// KubeClientTLSConfig returns client TLS configuration used
// to authenticate against kubernetes servers.
func (k *Key) KubeClientTLSConfig(cipherSuites []uint16, kubeClusterName string) (*tls.Config, error) {
tlsCert, ok := k.KubeTLSCerts[kubeClusterName]
if !ok {
return nil, trace.NotFound("TLS certificate for kubernetes cluster %q not found", kubeClusterName)
}
return k.clientTLSConfig(cipherSuites, tlsCert)
}

// TeleportClientTLSConfig returns client TLS configuration used
// to authenticate against API servers.
func (k *Key) TeleportClientTLSConfig(cipherSuites []uint16) (*tls.Config, error) {
return k.clientTLSConfig(cipherSuites, k.TLSCert)
}

func (k *Key) clientTLSConfig(cipherSuites []uint16, tlsCertRaw []byte) (*tls.Config, error) {
tlsConfig := utils.TLSConfig(cipherSuites)

pool := x509.NewCertPool()
Expand All @@ -106,7 +124,7 @@ func (k *Key) ClientTLSConfig(cipherSuites []uint16) (*tls.Config, error) {
}
}
tlsConfig.RootCAs = pool
tlsCert, err := tls.X509KeyPair(k.TLSCert, k.Priv)
tlsCert, err := tls.X509KeyPair(tlsCertRaw, k.Priv)
if err != nil {
return nil, trace.Wrap(err, "failed to parse TLS cert and key")
}
Expand Down Expand Up @@ -241,25 +259,24 @@ func (k *Key) AsAgentKeys() ([]agent.AddedKey, error) {
}, nil
}

// EqualsTo returns true if this key is the same as the other.
// Primarily used in tests
func (k *Key) EqualsTo(other *Key) bool {
if k == other {
return true
}
return bytes.Equal(k.Cert, other.Cert) &&
bytes.Equal(k.Priv, other.Priv) &&
bytes.Equal(k.Pub, other.Pub) &&
bytes.Equal(k.TLSCert, other.TLSCert)
// TeleportTLSCertificate returns the parsed x509 certificate for
// authentication against Teleport APIs.
func (k *Key) TeleportTLSCertificate() (*x509.Certificate, error) {
return tlsca.ParseCertificatePEM(k.TLSCert)
}

// TLSCertificate returns x509 certificate
func (k *Key) TLSCertificate() (*x509.Certificate, error) {
return tlsca.ParseCertificatePEM(k.TLSCert)
// KubeTLSCertificate returns the parsed x509 certificate for
// authentication against a named kubernetes cluster.
func (k *Key) KubeTLSCertificate(kubeClusterName string) (*x509.Certificate, error) {
tlsCert, ok := k.KubeTLSCerts[kubeClusterName]
if !ok {
return nil, trace.NotFound("TLS certificate for kubernetes cluster %q not found", kubeClusterName)
}
return tlsca.ParseCertificatePEM(tlsCert)
}

// TLSCertValidBefore returns the time of the TLS cert expiration
func (k *Key) TLSCertValidBefore() (t time.Time, err error) {
// TeleportTLSCertValidBefore returns the time of the TLS cert expiration
func (k *Key) TeleportTLSCertValidBefore() (t time.Time, err error) {
cert, err := tlsca.ParseCertificatePEM(k.TLSCert)
if err != nil {
return t, trace.Wrap(err)
Expand Down
14 changes: 10 additions & 4 deletions lib/client/keyagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,11 @@ func (a *LocalKeyAgent) UnloadKeys() error {

// GetKey returns the key for this user in a proxy from the filesystem keystore
// at ~/.tsh.
func (a *LocalKeyAgent) GetKey() (*Key, error) {
return a.keyStore.GetKey(a.proxyHost, a.username)
//
// clusterName is an optional teleport cluster name to load kubernetes
// certificates for.
func (a *LocalKeyAgent) GetKey(opts ...KeyOption) (*Key, error) {
return a.keyStore.GetKey(a.proxyHost, a.username, opts...)
}

// AddHostSignersToCache takes a list of CAs whom we trust. This list is added to a database
Expand Down Expand Up @@ -417,9 +420,12 @@ func (a *LocalKeyAgent) AddKey(key *Key) (*agent.AddedKey, error) {

// DeleteKey removes the key from the key store as well as unloading the key
// from the agent.
func (a *LocalKeyAgent) DeleteKey() error {
//
// clusterName is an optional teleport cluster name to delete kubernetes
// certificates for.
func (a *LocalKeyAgent) DeleteKey(opts ...KeyOption) error {
// remove key from key store
err := a.keyStore.DeleteKey(a.proxyHost, a.username)
err := a.keyStore.DeleteKey(a.proxyHost, a.username, opts...)
if err != nil {
return trace.Wrap(err)
}
Expand Down
Loading