diff --git a/api/ssh.go b/api/ssh.go index b2305fc6..11fd3a89 100644 --- a/api/ssh.go +++ b/api/ssh.go @@ -35,6 +35,7 @@ type SSHSignRequest struct { ValidAfter TimeDuration `json:"validAfter,omitempty"` ValidBefore TimeDuration `json:"validBefore,omitempty"` AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"` + KeyID string `json:"keyID"` } // Validate validates the SSHSignRequest. @@ -239,6 +240,7 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) { opts := provisioner.SSHOptions{ CertType: body.CertType, + KeyID: body.KeyID, Principals: body.Principals, ValidBefore: body.ValidBefore, ValidAfter: body.ValidAfter, diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index a3a7d1d9..52f83846 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -180,8 +180,7 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, if !p.claimer.IsSSHCAEnabled() { return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID()) } - // TODO: fix audiences - claims, err := p.authorizeToken(token, p.audiences.Sign) + claims, err := p.authorizeToken(token, p.audiences.SSHSign) if err != nil { return nil, err } @@ -192,8 +191,6 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, signOptions := []SignOption{ // validates user's SSHOptions with the ones in the token sshCertificateOptionsValidator(*opts), - // set the key id to the token subject - sshCertificateKeyIDModifier(claims.Subject), } t := now() @@ -219,6 +216,8 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, &sshDefaultExtensionModifier{}, // Set the validity bounds if not set. sshDefaultValidityModifier(p.claimer), + // Validate that the keyID is equivalent to the token subject. + sshCertKeyIDValidator(claims.Subject), // Validate public key &sshDefaultPublicKeyValidator{}, // Validate the validity period. @@ -230,7 +229,6 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, // AuthorizeSSHRevoke returns nil if the token is valid, false otherwise. func (p *JWK) AuthorizeSSHRevoke(ctx context.Context, token string) error { - // TODO fix audience. _, err := p.authorizeToken(token, p.audiences.SSHRevoke) return err } diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index 0abed1f3..63f16205 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -212,11 +212,6 @@ func (p *K8sSA) AuthorizeSign(ctx context.Context, token string) ([]SignOption, return nil, err } - // Check for SSH sign-ing request. - if MethodFromContext(ctx) == SignSSHMethod { - return nil, errors.New("ssh certificates not enabled for k8s ServiceAccount provisioners") - } - return []SignOption{ // modifiers / withOptions newProvisionerExtensionOption(TypeK8sSA, p.Name, ""), @@ -227,14 +222,41 @@ func (p *K8sSA) AuthorizeSign(ctx context.Context, token string) ([]SignOption, }, nil } -// AuthorizeRenewal returns an error if the renewal is disabled. -func (p *K8sSA) AuthorizeRenewal(cert *x509.Certificate) error { +// AuthorizeRenew returns an error if the renewal is disabled. +func (p *K8sSA) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { if p.claimer.IsDisableRenewal() { return errors.Errorf("renew is disabled for provisioner %s", p.GetID()) } return nil } +// AuthorizeSSHSign validates an request for an SSH certificate. +func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { + if !p.claimer.IsSSHCAEnabled() { + return nil, errors.Errorf("authorizeSSHSign: ssh ca is disabled for provisioner %s", p.GetID()) + } + _, err := p.authorizeToken(token, p.audiences.SSHSign) + if err != nil { + return nil, errors.Wrap(err, "authorizeSSHSign") + } + + // Default to a user certificate with no principals if not set + signOptions := []SignOption{sshCertificateDefaultsModifier{CertType: SSHUserCert}} + + return append(signOptions, + // Set the default extensions. + &sshDefaultExtensionModifier{}, + // Set the validity bounds if not set. + sshDefaultValidityModifier(p.claimer), + // Validate public key + &sshDefaultPublicKeyValidator{}, + // Validate the validity period. + &sshCertificateValidityValidator{p.claimer}, + // Require and validate all the default fields in the SSH certificate. + &sshCertificateDefaultValidator{}, + ), nil +} + /* func checkAccess(authz kauthz.AuthorizationV1Interface) error { r := &kauthzApi.SelfSubjectAccessReview{ diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 90e46701..d97f96f2 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -322,7 +322,7 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption return nil, err } signOptions := []SignOption{ - // set the key id to the token subject + // set the key id to the token email sshCertificateKeyIDModifier(claims.Email), } diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index a8f63cd5..5b65c159 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -49,6 +49,7 @@ type SSHCertificateOptionsValidator interface { // SSHOptions contains the options that can be passed to the SignSSH method. type SSHOptions struct { CertType string `json:"certType"` + KeyID string `json:"keyID"` Principals []string `json:"principals"` ValidAfter TimeDuration `json:"validAfter,omitempty"` ValidBefore TimeDuration `json:"validBefore,omitempty"` @@ -70,6 +71,8 @@ func (o SSHOptions) Modify(cert *ssh.Certificate) error { default: return errors.Errorf("ssh certificate has an unknown type: %s", o.CertType) } + + cert.KeyId = o.KeyID cert.ValidPrincipals = o.Principals if !o.ValidAfter.IsZero() { cert.ValidAfter = uint64(o.ValidAfter.Time().Unix()) @@ -373,6 +376,17 @@ func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate) error { } } +// sshCertKeyIDValidator implements a validator for the KeyId attribute. +type sshCertKeyIDValidator string + +// Valid returns an error if the given certificate does not contain the necessary fields. +func (v sshCertKeyIDValidator) Valid(cert *ssh.Certificate) error { + if string(v) != cert.KeyId { + return errors.Errorf("invalid ssh certificate KeyId; want %s, but got %s", string(v), cert.KeyId) + } + return nil +} + // sshCertTypeUInt32 func sshCertTypeUInt32(ct string) uint32 { switch ct { diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 84236b2c..a282692e 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -183,14 +183,6 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er return nil, err } - // Check for SSH sign-ing request. - if MethodFromContext(ctx) == SignSSHMethod { - if !p.claimer.IsSSHCAEnabled() { - return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID()) - } - return p.authorizeSSHSign(claims) - } - // NOTE: This is for backwards compatibility with older versions of cli // and certificates. Older versions added the token subject as the only SAN // in a CSR by default. @@ -222,8 +214,17 @@ func (p *X5C) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error return nil } -// authorizeSSHSign returns the list of SignOption for a SignSSH request. -func (p *X5C) authorizeSSHSign(claims *x5cPayload) ([]SignOption, error) { +// AuthorizeSSHSign returns the list of SignOption for a SignSSH request. +func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { + if !p.claimer.IsSSHCAEnabled() { + return nil, errors.Errorf("ssh ca is disabled for provisioner %s", p.GetID()) + } + + claims, err := p.authorizeToken(token, p.audiences.SSHSign) + if err != nil { + return nil, err + } + if claims.Step == nil || claims.Step.SSH == nil { return nil, errors.New("authorization token must be an SSH provisioning token") } @@ -231,8 +232,6 @@ func (p *X5C) authorizeSSHSign(claims *x5cPayload) ([]SignOption, error) { signOptions := []SignOption{ // validates user's SSHOptions with the ones in the token sshCertificateOptionsValidator(*opts), - // set the key id to the token subject - sshCertificateKeyIDModifier(claims.Subject), } // Add modifiers from custom claims @@ -258,6 +257,8 @@ func (p *X5C) authorizeSSHSign(claims *x5cPayload) ([]SignOption, error) { &sshDefaultExtensionModifier{}, // Checks the validity bounds, and set the validity if has not been set. sshLimitValidityModifier(p.claimer, claims.chains[0][0].NotAfter), + // set the key id to the token subject + sshCertKeyIDValidator(claims.Subject), // Validate public key. &sshDefaultPublicKeyValidator{}, // Validate the validity period.