Skip to content

Commit

Permalink
tsh: print kubernetes info in profile status
Browse files Browse the repository at this point in the history
Print when k8s support is detected, and if so what users/groups are
used.
  • Loading branch information
Andrew Lytvynov committed Oct 1, 2020
1 parent 8bc142b commit 7a54cb3
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 18 deletions.
44 changes: 35 additions & 9 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import (
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/shell"
"github.com/gravitational/teleport/lib/sshutils/scp"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/agentconn"
"github.com/gravitational/teleport/lib/wrappers"
Expand Down Expand Up @@ -300,6 +301,16 @@ type ProfileStatus struct {
// Logins are the Linux accounts, also known as principals in OpenSSH terminology.
Logins []string

// KubeEnabled is true when this profile is configured to connect to a
// kubernetes cluster.
KubeEnabled bool

// KubeUsers are the kubernetes users used by this profile.
KubeUsers []string

// KubeGroups are the kubernetes groups used by this profile.
KubeGroups []string

// ValidUntil is the time at which this SSH certificate will expire.
ValidUntil time.Time

Expand Down Expand Up @@ -376,26 +387,26 @@ func readProfile(profileDir string, profileName string) (*ProfileStatus, error)
if err != nil {
return nil, trace.Wrap(err)
}
keys, err := store.GetKey(profile.Name(), profile.Username)
key, err := store.GetKey(profile.Name(), profile.Username)
if err != nil {
return nil, trace.Wrap(err)
}
publicKey, _, _, _, err := ssh.ParseAuthorizedKey(keys.Cert)
publicKey, _, _, _, err := ssh.ParseAuthorizedKey(key.Cert)
if err != nil {
return nil, trace.Wrap(err)
}
cert, ok := publicKey.(*ssh.Certificate)
sshCert, ok := publicKey.(*ssh.Certificate)
if !ok {
return nil, trace.BadParameter("no certificate found")
}

// Extract from the certificate how much longer it will be valid for.
validUntil := time.Unix(int64(cert.ValidBefore), 0)
validUntil := time.Unix(int64(sshCert.ValidBefore), 0)

// Extract roles from certificate. Note, if the certificate is in old format,
// this will be empty.
var roles []string
rawRoles, ok := cert.Extensions[teleport.CertExtensionTeleportRoles]
rawRoles, ok := sshCert.Extensions[teleport.CertExtensionTeleportRoles]
if ok {
roles, err = services.UnmarshalCertRoles(rawRoles)
if err != nil {
Expand All @@ -407,7 +418,7 @@ func readProfile(profileDir string, profileName string) (*ProfileStatus, error)
// Extract traits from the certificate. Note if the certificate is in the
// old format, this will be empty.
var traits wrappers.Traits
rawTraits, ok := cert.Extensions[teleport.CertExtensionTeleportTraits]
rawTraits, ok := sshCert.Extensions[teleport.CertExtensionTeleportTraits]
if ok {
err = wrappers.UnmarshalTraits([]byte(rawTraits), &traits)
if err != nil {
Expand All @@ -416,7 +427,7 @@ func readProfile(profileDir string, profileName string) (*ProfileStatus, error)
}

var activeRequests services.RequestIDs
rawRequests, ok := cert.Extensions[teleport.CertExtensionTeleportActiveRequests]
rawRequests, ok := sshCert.Extensions[teleport.CertExtensionTeleportActiveRequests]
if ok {
if err := activeRequests.Unmarshal([]byte(rawRequests)); err != nil {
return nil, trace.Wrap(err)
Expand All @@ -426,7 +437,7 @@ func readProfile(profileDir string, profileName string) (*ProfileStatus, error)
// Extract extensions from certificate. This lists the abilities of the
// certificate (like can the user request a PTY, port forwarding, etc.)
var extensions []string
for ext := range cert.Extensions {
for ext := range sshCert.Extensions {
if ext == teleport.CertExtensionTeleportRoles ||
ext == teleport.CertExtensionTeleportTraits ||
ext == teleport.CertExtensionTeleportRouteToCluster ||
Expand All @@ -448,19 +459,34 @@ func readProfile(profileDir string, profileName string) (*ProfileStatus, error)
clusterName = profile.Name()
}

tlsCert, err := key.TLSCertificate()
if err != nil {
return nil, trace.Wrap(err)
}
tlsID, err := tlsca.FromSubject(tlsCert.Subject, time.Time{})
if err != nil {
return nil, trace.Wrap(err)
}

return &ProfileStatus{
ProxyURL: url.URL{
Scheme: "https",
Host: profile.WebProxyAddr,
},
Username: profile.Username,
Logins: cert.ValidPrincipals,
Logins: sshCert.ValidPrincipals,
ValidUntil: validUntil,
Extensions: extensions,
Roles: roles,
Cluster: clusterName,
Traits: traits,
ActiveRequests: activeRequests,
// The TLS cert may have k8s users and groups even when proxy isn't
// configured to talk to a k8s cluster.
// RouteToCluster is only set when k8s support is enabled though.
KubeEnabled: tlsID.RouteToCluster != "" && (len(tlsID.KubernetesUsers) > 0 || len(tlsID.KubernetesGroups) > 0),
KubeUsers: tlsID.KubernetesUsers,
KubeGroups: tlsID.KubernetesGroups,
}, nil
}

Expand Down
29 changes: 20 additions & 9 deletions tool/tsh/tsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -1278,25 +1278,36 @@ func printStatus(debug bool, p *client.ProfileStatus, isActive bool) {
humanDuration = fmt.Sprintf("valid for %v", duration.Round(time.Minute))
}

fmt.Printf("%vProfile URL: %v\n", prefix, p.ProxyURL.String())
fmt.Printf(" Logged in as: %v\n", p.Username)
fmt.Printf("%vProfile URL: %v\n", prefix, p.ProxyURL.String())
fmt.Printf(" Logged in as: %v\n", p.Username)
if p.Cluster != "" {
fmt.Printf(" Cluster: %v\n", p.Cluster)
fmt.Printf(" Cluster: %v\n", p.Cluster)
}
fmt.Printf(" Roles: %v*\n", strings.Join(p.Roles, ", "))
fmt.Printf(" Roles: %v*\n", strings.Join(p.Roles, ", "))
if debug {
for k, v := range p.Traits {
if count == 0 {
fmt.Printf(" Traits: %v: %v\n", k, v)
fmt.Printf(" Traits: %v: %v\n", k, v)
} else {
fmt.Printf(" %v: %v\n", k, v)
fmt.Printf(" %v: %v\n", k, v)
}
count = count + 1
}
}
fmt.Printf(" Logins: %v\n", strings.Join(p.Logins, ", "))
fmt.Printf(" Valid until: %v [%v]\n", p.ValidUntil, humanDuration)
fmt.Printf(" Extensions: %v\n", strings.Join(p.Extensions, ", "))
fmt.Printf(" Logins: %v\n", strings.Join(p.Logins, ", "))
if p.KubeEnabled {
fmt.Printf(" Kubernetes: enabled\n")
} else {
fmt.Printf(" Kubernetes: disabled\n")
}
if p.KubeEnabled && len(p.KubeUsers) > 0 {
fmt.Printf(" Kubernetes users: %v\n", strings.Join(p.KubeUsers, ", "))
}
if p.KubeEnabled && len(p.KubeGroups) > 0 {
fmt.Printf(" Kubernetes groups: %v\n", strings.Join(p.KubeGroups, ", "))
}
fmt.Printf(" Valid until: %v [%v]\n", p.ValidUntil, humanDuration)
fmt.Printf(" Extensions: %v\n", strings.Join(p.Extensions, ", "))

fmt.Printf("\n")
}
Expand Down

0 comments on commit 7a54cb3

Please sign in to comment.