diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index 3c37cae7..dc11e3eb 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/x509util" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/jose" @@ -41,13 +42,14 @@ type k8sSAPayload struct { // entity trusted to make signature requests. type K8sSA struct { *base - Type string `json:"type"` - Name string `json:"name"` - PubKeys []byte `json:"publicKeys,omitempty"` - Claims *Claims `json:"claims,omitempty"` - Options *Options `json:"options,omitempty"` - claimer *Claimer - audiences Audiences + Type string `json:"type"` + Name string `json:"name"` + PubKeys []byte `json:"publicKeys,omitempty"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` + SSHOptions *SSHOptions `json:"sshOptions,omitempty"` + claimer *Claimer + audiences Audiences //kauthn kauthn.AuthenticationV1Interface pubKeys []interface{} } @@ -249,16 +251,27 @@ func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio if !p.claimer.IsSSHCAEnabled() { return nil, errs.Unauthorized("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner %s", p.GetID()) } - if _, err := p.authorizeToken(token, p.audiences.SSHSign); err != nil { + claims, err := p.authorizeToken(token, p.audiences.SSHSign) + if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSSHSign") } - // Default to a user certificate with no principals if not set - signOptions := []SignOption{sshCertDefaultsModifier{CertType: SSHUserCert}} + // Certificate templates. + // Set some default variables to be used in the templates. + data := sshutil.CreateTemplateData(sshutil.HostCert, claims.ServiceAccountName, []string{claims.ServiceAccountName}) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + + templateOptions, err := CustomSSHTemplateOptions(p.SSHOptions, data, sshutil.CertificateRequestTemplate) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSSHSign") + } + signOptions := []SignOption{templateOptions} return append(signOptions, - // Set the default extensions. - &sshDefaultExtensionModifier{}, + // Require type, key-id and principals in the SignSSHOptions. + &sshCertOptionsRequireValidator{CertType: true, KeyID: true, Principals: true}, // Set the validity bounds if not set. &sshDefaultDuration{p.claimer}, // Validate public key diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index b9bf63b5..8c6000f0 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -299,6 +299,26 @@ func (v sshCertOptionsValidator) Valid(got SignSSHOptions) error { return want.match(got) } +// sshCertOptionsRequireValidator defines which elements in the SignSSHOptions are required. +type sshCertOptionsRequireValidator struct { + CertType bool + KeyID bool + Principals bool +} + +func (v sshCertOptionsRequireValidator) Valid(got SignSSHOptions) error { + switch { + case v.CertType && got.CertType == "": + return errors.New("ssh certificate certType cannot be empty") + case v.KeyID && got.KeyID == "": + return errors.New("ssh certificate keyID cannot be empty") + case v.Principals && len(got.Principals) == 0: + return errors.New("ssh certificate principals cannot be empty") + default: + return nil + } +} + type sshCertValidityValidator struct { *Claimer } diff --git a/authority/ssh.go b/authority/ssh.go index 1d449b80..41f37579 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -206,8 +206,6 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user string, hostname str // SignSSH creates a signed SSH certificate with the given public key and options. func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { var ( - err error - certType sshutil.CertType certOptions []sshutil.Option mods []provisioner.SSHCertModifier validators []provisioner.SSHCertValidator @@ -216,14 +214,6 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi // Set backdate with the configured value opts.Backdate = a.config.AuthorityConfig.Backdate.Duration - // Validate certificate type. - if opts.CertType != "" { - certType, err = sshutil.CertTypeFromString(opts.CertType) - if err != nil { - return nil, errs.Wrap(http.StatusBadRequest, err, "authority.SignSSH") - } - } - for _, op := range signOpts { switch o := op.(type) { // add options to NewCertificate @@ -251,7 +241,7 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi // Simulated certificate request with request options. cr := sshutil.CertificateRequest{ - Type: certType, + Type: opts.CertType, KeyID: opts.KeyID, Principals: opts.Principals, Key: key, diff --git a/sshutil/certificate_request.go b/sshutil/certificate_request.go index d4081dc3..85a69502 100644 --- a/sshutil/certificate_request.go +++ b/sshutil/certificate_request.go @@ -11,7 +11,7 @@ import "golang.org/x/crypto/ssh" // passed with the API instead of the validated ones. type CertificateRequest struct { Key ssh.PublicKey - Type CertType + Type string KeyID string Principals []string } diff --git a/sshutil/templates.go b/sshutil/templates.go index 8f0265ab..8710f8e2 100644 --- a/sshutil/templates.go +++ b/sshutil/templates.go @@ -143,3 +143,18 @@ const DefaultIIDCertificate = `{ {{- end }} "extensions": {{ toJson .Extensions }} }` + +const CertificateRequestTemplate = `{ + "type": "{{ .Insecure.CR.Type }}", + "keyId": "{{ .Insecure.CR.KeyID }}", + "principals": {{ toJson .Insecure.CR.Principals }} +{{- if eq .Insecure.CR.Type "user" }} + , "extensions": { + "permit-X11-forwarding": "", + "permit-agent-forwarding": "", + "permit-port-forwarding": "", + "permit-pty": "", + "permit-user-rc": "" + } +{{- end }} +}`