diff --git a/command/ssh/login.go b/command/ssh/login.go index 6cfcdcba..3d497346 100644 --- a/command/ssh/login.go +++ b/command/ssh/login.go @@ -197,6 +197,14 @@ func loginAction(ctx *cli.Context) error { identityKey = key } + // NOTE: For OIDC token the principals should be completely empty. The OIDC + // provisioner is responsible for setting default principals by using an + // identity function. + if email, ok := tokenHasEmail(token); ok { + principals = []string{} + subject = email + } + resp, err := caClient.SSHSign(&api.SSHSignRequest{ PublicKey: sshPub.Marshal(), OTT: token, diff --git a/command/ssh/proxycommand.go b/command/ssh/proxycommand.go index 76780d2e..bf9c4c13 100644 --- a/command/ssh/proxycommand.go +++ b/command/ssh/proxycommand.go @@ -1,6 +1,7 @@ package ssh import ( + "crypto" "encoding/json" "io" "net" @@ -13,6 +14,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/ca" "github.com/smallstep/cli/command" "github.com/smallstep/cli/crypto/keys" "github.com/smallstep/cli/crypto/sshutil" @@ -146,11 +148,36 @@ func doLoginIfNeeded(ctx *cli.Context, subject string) error { return err } + // NOTE: For OIDC token the principals should be completely empty. The OIDC + // provisioner is responsible for setting default principals by using an + // identity function. + if email, ok := tokenHasEmail(token); ok { + principals = []string{} + subject = email + } + caClient, err := flow.GetClient(ctx, token) if err != nil { return err } + version, err := caClient.Version() + if err != nil { + return err + } + + // Generate identity certificate (x509) if necessary + var identityCSR api.CertificateRequest + var identityKey crypto.PrivateKey + if version.RequireClientAuthentication { + csr, key, err := ca.NewIdentityRequest(subject) + if err != nil { + return err + } + identityCSR = *csr + identityKey = key + } + // Generate keypair pub, priv, err := keys.GenerateDefaultKeyPair() if err != nil { @@ -170,11 +197,19 @@ func doLoginIfNeeded(ctx *cli.Context, subject string) error { CertType: provisioner.SSHUserCert, ValidAfter: validAfter, ValidBefore: validBefore, + IdentityCSR: identityCSR, }) if err != nil { return err } + // Write x509 identity certificate + if version.RequireClientAuthentication { + if err := ca.WriteDefaultIdentity(resp.IdentityCertificate, identityKey); err != nil { + return err + } + } + // Add certificate and private key to agent if err := agent.AddCertificate(subject, resp.Certificate.Certificate, priv); err != nil { return err diff --git a/command/ssh/ssh.go b/command/ssh/ssh.go index b76b1bab..731a4900 100644 --- a/command/ssh/ssh.go +++ b/command/ssh/ssh.go @@ -194,3 +194,13 @@ func loginOnUnauthorized(ctx *cli.Context) (ca.RetryFunc, error) { return true }, nil } + +// tokenHasEmail returns if the token payload has an email address. This is +// mainly used on OIDC token. +func tokenHasEmail(s string) (string, bool) { + jwt, err := token.ParseInsecure(s) + if err != nil { + return "", false + } + return jwt.Payload.Email, jwt.Payload.Email != "" +}