diff --git a/Makefile b/Makefile index b6b7dce7..012e8e32 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ SRC=$(shell find . -type f -name '*.go' -not -path "./vendor/*") GOOS_OVERRIDE ?= OUTPUT_ROOT=output/ -all: lint build test +all: lint test build .PHONY: all diff --git a/acme/common.go b/acme/common.go index d8b2b7e4..d2d710cc 100644 --- a/acme/common.go +++ b/acme/common.go @@ -18,6 +18,7 @@ type Provisioner interface { AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) GetName() string DefaultTLSCertDuration() time.Duration + GetOptions() *provisioner.Options } // MockProvisioner for testing @@ -27,6 +28,7 @@ type MockProvisioner struct { MgetName func() string MauthorizeSign func(ctx context.Context, ott string) ([]provisioner.SignOption, error) MdefaultTLSCertDuration func() time.Duration + MgetOptions func() *provisioner.Options } // GetName mock @@ -53,6 +55,13 @@ func (m *MockProvisioner) DefaultTLSCertDuration() time.Duration { return m.Mret1.(time.Duration) } +func (m *MockProvisioner) GetOptions() *provisioner.Options { + if m.MgetOptions != nil { + return m.MgetOptions() + } + return m.Mret1.(*provisioner.Options) +} + // ContextKey is the key type for storing and searching for ACME request // essentials in the context of a request. type ContextKey string @@ -125,7 +134,7 @@ func ProvisionerFromContext(ctx context.Context) (Provisioner, error) { // SignAuthority is the interface implemented by a CA authority. type SignAuthority interface { - Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) LoadProvisionerByID(string) (provisioner.Interface, error) } diff --git a/acme/order.go b/acme/order.go index 8642c6d1..57168419 100644 --- a/acme/order.go +++ b/acme/order.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/nosql" + "go.step.sm/crypto/x509util" ) var defaultOrderExpiry = time.Hour * 24 @@ -299,23 +300,23 @@ func (o *order) finalize(db nosql.DB, csr *x509.CertificateRequest, auth SignAut orderNames = uniqueLowerNames(orderNames) // Validate identifier names against CSR alternative names. + // + // Note that with certificate templates we are not going to check for the + // absence of other SANs as they will only be set if the templates allows + // them. if len(csr.DNSNames) != len(orderNames) { return nil, BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly: CSR names = %v, Order names = %v", csr.DNSNames, orderNames)) } + + sans := make([]x509util.SubjectAlternativeName, len(csr.DNSNames)) for i := range csr.DNSNames { if csr.DNSNames[i] != orderNames[i] { return nil, BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly: CSR names = %v, Order names = %v", csr.DNSNames, orderNames)) } - } - - if len(csr.IPAddresses) > 0 { - return nil, BadCSRErr(errors.Errorf("CSR contains IP Address SANs, but should only contain DNS Names")) - } - if len(csr.EmailAddresses) > 0 { - return nil, BadCSRErr(errors.Errorf("CSR contains Email Address SANs, but should only contain DNS Names")) - } - if len(csr.URIs) > 0 { - return nil, BadCSRErr(errors.Errorf("CSR contains URI SANs, but should only contain DNS Names")) + sans[i] = x509util.SubjectAlternativeName{ + Type: x509util.DNSType, + Value: csr.DNSNames[i], + } } // Get authorizations from the ACME provisioner. @@ -325,8 +326,19 @@ func (o *order) finalize(db nosql.DB, csr *x509.CertificateRequest, auth SignAut return nil, ServerInternalErr(errors.Wrapf(err, "error retrieving authorization options from ACME provisioner")) } + // Template data + data := x509util.NewTemplateData() + data.SetCommonName(csr.Subject.CommonName) + data.Set(x509util.SANsKey, sans) + + templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) + if err != nil { + return nil, ServerInternalErr(errors.Wrapf(err, "error creating template options from ACME provisioner")) + } + signOps = append(signOps, templateOptions) + // Create and store a new certificate. - certChain, err := auth.Sign(csr, provisioner.Options{ + certChain, err := auth.Sign(csr, provisioner.SignOptions{ NotBefore: provisioner.NewTimeDuration(o.NotBefore), NotAfter: provisioner.NewTimeDuration(o.NotAfter), }, signOps...) diff --git a/acme/order_test.go b/acme/order_test.go index f3894a2a..785b24c4 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -895,13 +895,13 @@ func TestOrderUpdateStatus(t *testing.T) { } type mockSignAuth struct { - sign func(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + sign func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) loadProvisionerByID func(string) (provisioner.Interface, error) ret1, ret2 interface{} err error } -func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { +func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { if m.sign != nil { return m.sign(csr, signOpts, extraOpts...) } else if m.err != nil { @@ -1066,13 +1066,13 @@ func TestOrderFinalize(t *testing.T) { Subject: pkix.Name{ CommonName: "", }, - DNSNames: []string{"acme.example.com", "step.example.com"}, + // DNSNames: []string{"acme.example.com", "step.example.com"}, IPAddresses: []net.IP{net.ParseIP("1.1.1.1")}, } return test{ o: o, csr: csr, - err: BadCSRErr(errors.Errorf("CSR contains IP Address SANs, but should only contain DNS Names")), + err: BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly")), } }, "fail/ready/no-emailAddresses": func(t *testing.T) test { @@ -1084,13 +1084,13 @@ func TestOrderFinalize(t *testing.T) { Subject: pkix.Name{ CommonName: "", }, - DNSNames: []string{"acme.example.com", "step.example.com"}, + // DNSNames: []string{"acme.example.com", "step.example.com"}, EmailAddresses: []string{"max@smallstep.com", "mariano@smallstep.com"}, } return test{ o: o, csr: csr, - err: BadCSRErr(errors.Errorf("CSR contains Email Address SANs, but should only contain DNS Names")), + err: BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly")), } }, "fail/ready/no-URIs": func(t *testing.T) test { @@ -1104,13 +1104,13 @@ func TestOrderFinalize(t *testing.T) { Subject: pkix.Name{ CommonName: "", }, - DNSNames: []string{"acme.example.com", "step.example.com"}, - URIs: []*url.URL{u}, + // DNSNames: []string{"acme.example.com", "step.example.com"}, + URIs: []*url.URL{u}, } return test{ o: o, csr: csr, - err: BadCSRErr(errors.Errorf("CSR contains URI SANs, but should only contain DNS Names")), + err: BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly")), } }, "fail/ready/provisioner-auth-sign-error": func(t *testing.T) test { @@ -1262,8 +1262,8 @@ func TestOrderFinalize(t *testing.T) { res: clone, csr: csr, sa: &mockSignAuth{ - sign: func(csr *x509.CertificateRequest, pops provisioner.Options, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { - assert.Equals(t, len(signOps), 5) + sign: func(csr *x509.CertificateRequest, pops provisioner.SignOptions, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { + assert.Equals(t, len(signOps), 6) return []*x509.Certificate{crt, inter}, nil }, }, @@ -1311,8 +1311,8 @@ func TestOrderFinalize(t *testing.T) { res: &clone, csr: csr, sa: &mockSignAuth{ - sign: func(csr *x509.CertificateRequest, pops provisioner.Options, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { - assert.Equals(t, len(signOps), 5) + sign: func(csr *x509.CertificateRequest, pops provisioner.SignOptions, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { + assert.Equals(t, len(signOps), 6) return []*x509.Certificate{crt, inter}, nil }, }, @@ -1358,8 +1358,8 @@ func TestOrderFinalize(t *testing.T) { res: &clone, csr: csr, sa: &mockSignAuth{ - sign: func(csr *x509.CertificateRequest, pops provisioner.Options, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { - assert.Equals(t, len(signOps), 5) + sign: func(csr *x509.CertificateRequest, pops provisioner.SignOptions, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { + assert.Equals(t, len(signOps), 6) return []*x509.Certificate{crt, inter}, nil }, }, diff --git a/api/api.go b/api/api.go index 6e0b08eb..205f6d1b 100644 --- a/api/api.go +++ b/api/api.go @@ -34,7 +34,7 @@ type Authority interface { AuthorizeSign(ott string) ([]provisioner.SignOption, error) GetTLSOptions() *tlsutil.TLSOptions Root(shasum string) (*x509.Certificate, error) - Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) Renew(peer *x509.Certificate) ([]*x509.Certificate, error) Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error) diff --git a/api/api_test.go b/api/api_test.go index 646dfd09..aab47e53 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -550,7 +550,7 @@ type mockAuthority struct { authorizeSign func(ott string) ([]provisioner.SignOption, error) getTLSOptions func() *tlsutil.TLSOptions root func(shasum string) (*x509.Certificate, error) - sign func(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + sign func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) renew func(cert *x509.Certificate) ([]*x509.Certificate, error) rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error) @@ -560,7 +560,7 @@ type mockAuthority struct { getEncryptedKey func(kid string) (string, error) getRoots func() ([]*x509.Certificate, error) getFederation func() ([]*x509.Certificate, error) - signSSH func(ctx context.Context, key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) + signSSH func(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) signSSHAddUser func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error) rekeySSH func(ctx context.Context, cert *ssh.Certificate, key ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) @@ -599,7 +599,7 @@ func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) { return m.ret1.(*x509.Certificate), m.err } -func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { +func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { if m.sign != nil { return m.sign(cr, opts, signOpts...) } @@ -669,7 +669,7 @@ func (m *mockAuthority) GetFederation() ([]*x509.Certificate, error) { return m.ret1.([]*x509.Certificate), m.err } -func (m *mockAuthority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { +func (m *mockAuthority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { if m.signSSH != nil { return m.signSSH(ctx, key, opts, signOpts...) } diff --git a/api/sign.go b/api/sign.go index 7826f47b..eb977bcd 100644 --- a/api/sign.go +++ b/api/sign.go @@ -2,6 +2,7 @@ package api import ( "crypto/tls" + "encoding/json" "net/http" "github.com/smallstep/certificates/authority/provisioner" @@ -11,10 +12,11 @@ import ( // SignRequest is the request body for a certificate signature request. type SignRequest struct { - CsrPEM CertificateRequest `json:"csr"` - OTT string `json:"ott"` - NotAfter TimeDuration `json:"notAfter"` - NotBefore TimeDuration `json:"notBefore"` + CsrPEM CertificateRequest `json:"csr"` + OTT string `json:"ott"` + NotAfter TimeDuration `json:"notAfter"` + NotBefore TimeDuration `json:"notBefore"` + TemplateData json.RawMessage `json:"templateData"` } // Validate checks the fields of the SignRequest and returns nil if they are ok @@ -58,9 +60,10 @@ func (h *caHandler) Sign(w http.ResponseWriter, r *http.Request) { return } - opts := provisioner.Options{ - NotBefore: body.NotBefore, - NotAfter: body.NotAfter, + opts := provisioner.SignOptions{ + NotBefore: body.NotBefore, + NotAfter: body.NotAfter, + TemplateData: body.TemplateData, } signOpts, err := h.Authority.AuthorizeSign(body.OTT) diff --git a/api/ssh.go b/api/ssh.go index f423583b..70469fbf 100644 --- a/api/ssh.go +++ b/api/ssh.go @@ -19,7 +19,7 @@ import ( // SSHAuthority is the interface implemented by a SSH CA authority. type SSHAuthority interface { - SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) + SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) RenewSSH(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error) RekeySSH(ctx context.Context, cert *ssh.Certificate, key ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) SignSSHAddUser(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) @@ -274,7 +274,7 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) { } } - opts := provisioner.SSHOptions{ + opts := provisioner.SignSSHOptions{ CertType: body.CertType, KeyID: body.KeyID, Principals: body.Principals, @@ -322,7 +322,7 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) { NotAfter: time.Unix(int64(cert.ValidBefore), 0), }) - certChain, err := h.Authority.Sign(cr, provisioner.Options{}, signOpts...) + certChain, err := h.Authority.Sign(cr, provisioner.SignOptions{}, signOpts...) if err != nil { WriteError(w, errs.ForbiddenErr(err)) return diff --git a/api/ssh_test.go b/api/ssh_test.go index cb1afa6f..7561709a 100644 --- a/api/ssh_test.go +++ b/api/ssh_test.go @@ -319,13 +319,13 @@ func Test_caHandler_SSHSign(t *testing.T) { authorizeSign: func(ott string) ([]provisioner.SignOption, error) { return []provisioner.SignOption{}, tt.authErr }, - signSSH: func(ctx context.Context, key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { + signSSH: func(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { return tt.signCert, tt.signErr }, signSSHAddUser: func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) { return tt.addUserCert, tt.addUserErr }, - sign: func(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + sign: func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { return tt.tlsSignCerts, tt.tlsSignErr }, }).(*caHandler) diff --git a/authority/authority_test.go b/authority/authority_test.go index 3ab3e142..f87f1df3 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -283,7 +283,7 @@ func TestNewEmbedded_Sign(t *testing.T) { csr, err := x509.ParseCertificateRequest(cr) assert.FatalError(t, err) - cert, err := a.Sign(csr, provisioner.Options{}) + cert, err := a.Sign(csr, provisioner.SignOptions{}) assert.FatalError(t, err) assert.Equals(t, []string{"foo.bar.zar"}, cert[0].DNSNames) assert.Equals(t, crt, cert[1]) diff --git a/authority/authorize_test.go b/authority/authorize_test.go index 1d7e69ad..c562437c 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -449,7 +449,7 @@ func TestAuthority_authorizeSign(t *testing.T) { } } else { if assert.Nil(t, tc.err) { - assert.Len(t, 6, got) + assert.Len(t, 7, got) } } }) @@ -830,17 +830,17 @@ func TestAuthority_authorizeRenew(t *testing.T) { } func generateSimpleSSHUserToken(iss, aud string, jwk *jose.JSONWebKey) (string, error) { - return generateSSHToken("subject@localhost", iss, aud, time.Now(), &provisioner.SSHOptions{ + return generateSSHToken("subject@localhost", iss, aud, time.Now(), &provisioner.SignSSHOptions{ CertType: "user", Principals: []string{"name"}, }, jwk) } type stepPayload struct { - SSH *provisioner.SSHOptions `json:"ssh,omitempty"` + SSH *provisioner.SignSSHOptions `json:"ssh,omitempty"` } -func generateSSHToken(sub, iss, aud string, iat time.Time, sshOpts *provisioner.SSHOptions, jwk *jose.JSONWebKey) (string, error) { +func generateSSHToken(sub, iss, aud string, iat time.Time, sshOpts *provisioner.SignSSHOptions, jwk *jose.JSONWebKey) (string, error) { sig, err := jose.NewSigner( jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID), diff --git a/authority/provisioner/acme.go b/authority/provisioner/acme.go index 88189d01..a36c496d 100644 --- a/authority/provisioner/acme.go +++ b/authority/provisioner/acme.go @@ -13,10 +13,11 @@ import ( // provisioning flow. type ACME struct { *base - Type string `json:"type"` - Name string `json:"name"` - Claims *Claims `json:"claims,omitempty"` - ForceCN bool `json:"forceCN,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + ForceCN bool `json:"forceCN,omitempty"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` claimer *Claimer } @@ -45,6 +46,11 @@ func (p *ACME) GetEncryptedKey() (string, string, bool) { return "", "", false } +// GetOptions returns the configured provisioner options. +func (p *ACME) GetOptions() *Options { + return p.Options +} + // DefaultTLSCertDuration returns the default TLS cert duration enforced by // the provisioner. func (p *ACME) DefaultTLSCertDuration() time.Duration { diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index 12dffaa8..381441b9 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -18,6 +18,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/x509util" ) // awsIssuer is the string used as issuer in the generated tokens. @@ -150,6 +151,7 @@ type AWS struct { IMDSVersions []string `json:"imdsVersions"` InstanceAge Duration `json:"instanceAge,omitempty"` Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` claimer *Claimer config *awsConfig audiences Audiences @@ -310,22 +312,38 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er } doc := payload.document + + // Template options + data := x509util.NewTemplateData() + data.SetCommonName(payload.Claims.Subject) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + // Enforce known CN and default DNS and IP if configured. // By default we'll accept the CN and SANs in the CSR. // There's no way to trust them other than TOFU. var so []SignOption if p.DisableCustomSANs { - so = append(so, dnsNamesValidator([]string{ - fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region), - })) + dnsName := fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region) + so = append(so, dnsNamesValidator([]string{dnsName})) so = append(so, ipAddressesValidator([]net.IP{ net.ParseIP(doc.PrivateIP), })) so = append(so, emailAddressesValidator(nil)) so = append(so, urisValidator(nil)) + + // Template options + data.SetSANs([]string{dnsName, doc.PrivateIP}) + } + + templateOptions, err := CustomTemplateOptions(p.Options, data, x509util.DefaultIIDLeafTemplate) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "aws.AuthorizeSign") } return append(so, + templateOptions, // modifiers / withOptions newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), @@ -577,7 +595,7 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, } // Default to cert type to host - defaults := SSHOptions{ + defaults := SignSSHOptions{ CertType: SSHHostCert, Principals: principals, } diff --git a/authority/provisioner/aws_test.go b/authority/provisioner/aws_test.go index f2a5d100..7409532d 100644 --- a/authority/provisioner/aws_test.go +++ b/authority/provisioner/aws_test.go @@ -595,11 +595,11 @@ func TestAWS_AuthorizeSign(t *testing.T) { code int wantErr bool }{ - {"ok", p1, args{t1, "foo.local"}, 5, http.StatusOK, false}, - {"ok", p2, args{t2, "instance-id"}, 9, http.StatusOK, false}, - {"ok", p2, args{t2Hostname, "ip-127-0-0-1.us-west-1.compute.internal"}, 9, http.StatusOK, false}, - {"ok", p2, args{t2PrivateIP, "127.0.0.1"}, 9, http.StatusOK, false}, - {"ok", p1, args{t4, "instance-id"}, 5, http.StatusOK, false}, + {"ok", p1, args{t1, "foo.local"}, 6, http.StatusOK, false}, + {"ok", p2, args{t2, "instance-id"}, 10, http.StatusOK, false}, + {"ok", p2, args{t2Hostname, "ip-127-0-0-1.us-west-1.compute.internal"}, 10, http.StatusOK, false}, + {"ok", p2, args{t2PrivateIP, "127.0.0.1"}, 10, http.StatusOK, false}, + {"ok", p1, args{t4, "instance-id"}, 6, http.StatusOK, false}, {"fail account", p3, args{token: t3}, 0, http.StatusUnauthorized, true}, {"fail token", p1, args{token: "token"}, 0, http.StatusUnauthorized, true}, {"fail subject", p1, args{token: failSubject}, 0, http.StatusUnauthorized, true}, @@ -629,6 +629,7 @@ func TestAWS_AuthorizeSign(t *testing.T) { assert.Len(t, tt.wantLen, got) for _, o := range got { switch v := o.(type) { + case certificateOptionsFunc: case *provisionerExtensionOption: assert.Equals(t, v.Type, int(TypeAWS)) assert.Equals(t, v.Name, tt.aws.GetName()) @@ -701,51 +702,51 @@ func TestAWS_AuthorizeSSHSign(t *testing.T) { assert.FatalError(t, err) hostDuration := p1.claimer.DefaultHostSSHCertDuration() - expectedHostOptions := &SSHOptions{ + expectedHostOptions := &SignSSHOptions{ CertType: "host", Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } - expectedHostOptionsIP := &SSHOptions{ + expectedHostOptionsIP := &SignSSHOptions{ CertType: "host", Principals: []string{"127.0.0.1"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } - expectedHostOptionsHostname := &SSHOptions{ + expectedHostOptionsHostname := &SignSSHOptions{ CertType: "host", Principals: []string{"ip-127-0-0-1.us-west-1.compute.internal"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } - expectedCustomOptions := &SSHOptions{ + expectedCustomOptions := &SignSSHOptions{ CertType: "host", Principals: []string{"foo.local"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } type args struct { token string - sshOpts SSHOptions + sshOpts SignSSHOptions key interface{} } tests := []struct { name string aws *AWS args args - expected *SSHOptions + expected *SignSSHOptions code int wantErr bool wantSignErr bool }{ - {"ok", p1, args{t1, SSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-type", p1, args{t1, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-principal-ip", p1, args{t1, SSHOptions{Principals: []string{"127.0.0.1"}}, pub}, expectedHostOptionsIP, http.StatusOK, false, false}, - {"ok-principal-hostname", p1, args{t1, SSHOptions{Principals: []string{"ip-127-0-0-1.us-west-1.compute.internal"}}, pub}, expectedHostOptionsHostname, http.StatusOK, false, false}, - {"ok-options", p1, args{t1, SSHOptions{CertType: "host", Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-custom", p2, args{t2, SSHOptions{Principals: []string{"foo.local"}}, pub}, expectedCustomOptions, http.StatusOK, false, false}, - {"fail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true}, - {"fail-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, nil, http.StatusOK, false, true}, - {"fail-principal", p1, args{t1, SSHOptions{Principals: []string{"smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, - {"fail-extra-principal", p1, args{t1, SSHOptions{Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal", "smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, - {"fail-sshCA-disabled", p3, args{"foo", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, - {"fail-invalid-token", p1, args{"foo", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, + {"ok", p1, args{t1, SignSSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-type", p1, args{t1, SignSSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-principal-ip", p1, args{t1, SignSSHOptions{Principals: []string{"127.0.0.1"}}, pub}, expectedHostOptionsIP, http.StatusOK, false, false}, + {"ok-principal-hostname", p1, args{t1, SignSSHOptions{Principals: []string{"ip-127-0-0-1.us-west-1.compute.internal"}}, pub}, expectedHostOptionsHostname, http.StatusOK, false, false}, + {"ok-options", p1, args{t1, SignSSHOptions{CertType: "host", Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-custom", p2, args{t2, SignSSHOptions{Principals: []string{"foo.local"}}, pub}, expectedCustomOptions, http.StatusOK, false, false}, + {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true}, + {"fail-type", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, nil, http.StatusOK, false, true}, + {"fail-principal", p1, args{t1, SignSSHOptions{Principals: []string{"smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, + {"fail-extra-principal", p1, args{t1, SignSSHOptions{Principals: []string{"127.0.0.1", "ip-127-0-0-1.us-west-1.compute.internal", "smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, + {"fail-sshCA-disabled", p3, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, + {"fail-invalid-token", p1, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index 8580eced..ba281133 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -15,6 +15,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/x509util" ) // azureOIDCBaseURL is the base discovery url for Microsoft Azure tokens. @@ -90,6 +91,7 @@ type Azure struct { DisableCustomSANs bool `json:"disableCustomSANs"` DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` claimer *Claimer config *azureConfig oidcConfig openIDConfiguration @@ -276,6 +278,13 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption, } } + // Template options + data := x509util.NewTemplateData() + data.SetCommonName(name) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + // Enforce known common name and default DNS if configured. // By default we'll accept the CN and SANs in the CSR. // There's no way to trust them other than TOFU. @@ -287,9 +296,18 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption, so = append(so, ipAddressesValidator(nil)) so = append(so, emailAddressesValidator(nil)) so = append(so, urisValidator(nil)) + + // Enforce SANs in the template. + data.SetSANs([]string{name}) + } + + templateOptions, err := CustomTemplateOptions(p.Options, data, x509util.DefaultIIDLeafTemplate) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "aws.AuthorizeSign") } return append(so, + templateOptions, // modifiers / withOptions newProvisionerExtensionOption(TypeAzure, p.Name, p.TenantID), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), @@ -332,7 +350,7 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio } // Default to host + known hostnames - defaults := SSHOptions{ + defaults := SignSSHOptions{ CertType: SSHHostCert, Principals: principals, } diff --git a/authority/provisioner/azure_test.go b/authority/provisioner/azure_test.go index 500ab2bb..e919a5cd 100644 --- a/authority/provisioner/azure_test.go +++ b/authority/provisioner/azure_test.go @@ -431,9 +431,9 @@ func TestAzure_AuthorizeSign(t *testing.T) { code int wantErr bool }{ - {"ok", p1, args{t1}, 4, http.StatusOK, false}, - {"ok", p2, args{t2}, 9, http.StatusOK, false}, - {"ok", p1, args{t11}, 4, http.StatusOK, false}, + {"ok", p1, args{t1}, 5, http.StatusOK, false}, + {"ok", p2, args{t2}, 10, http.StatusOK, false}, + {"ok", p1, args{t11}, 5, http.StatusOK, false}, {"fail tenant", p3, args{t3}, 0, http.StatusUnauthorized, true}, {"fail resource group", p4, args{t4}, 0, http.StatusUnauthorized, true}, {"fail token", p1, args{"token"}, 0, http.StatusUnauthorized, true}, @@ -458,6 +458,7 @@ func TestAzure_AuthorizeSign(t *testing.T) { assert.Len(t, tt.wantLen, got) for _, o := range got { switch v := o.(type) { + case certificateOptionsFunc: case *provisionerExtensionOption: assert.Equals(t, v.Type, int(TypeAzure)) assert.Equals(t, v.Name, tt.azure.GetName()) @@ -570,41 +571,41 @@ func TestAzure_AuthorizeSSHSign(t *testing.T) { assert.FatalError(t, err) hostDuration := p1.claimer.DefaultHostSSHCertDuration() - expectedHostOptions := &SSHOptions{ + expectedHostOptions := &SignSSHOptions{ CertType: "host", Principals: []string{"virtualMachine"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } - expectedCustomOptions := &SSHOptions{ + expectedCustomOptions := &SignSSHOptions{ CertType: "host", Principals: []string{"foo.bar"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } type args struct { token string - sshOpts SSHOptions + sshOpts SignSSHOptions key interface{} } tests := []struct { name string azure *Azure args args - expected *SSHOptions + expected *SignSSHOptions code int wantErr bool wantSignErr bool }{ - {"ok", p1, args{t1, SSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-type", p1, args{t1, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"virtualMachine"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-options", p1, args{t1, SSHOptions{CertType: "host", Principals: []string{"virtualMachine"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-custom", p2, args{t2, SSHOptions{Principals: []string{"foo.bar"}}, pub}, expectedCustomOptions, http.StatusOK, false, false}, - {"fail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true}, - {"fail-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, nil, http.StatusOK, false, true}, - {"fail-principal", p1, args{t1, SSHOptions{Principals: []string{"smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, - {"fail-extra-principal", p1, args{t1, SSHOptions{Principals: []string{"virtualMachine", "smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, - {"fail-sshCA-disabled", p3, args{"foo", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, - {"fail-invalid-token", p1, args{"foo", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, + {"ok", p1, args{t1, SignSSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-type", p1, args{t1, SignSSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"virtualMachine"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-options", p1, args{t1, SignSSHOptions{CertType: "host", Principals: []string{"virtualMachine"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-custom", p2, args{t2, SignSSHOptions{Principals: []string{"foo.bar"}}, pub}, expectedCustomOptions, http.StatusOK, false, false}, + {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true}, + {"fail-type", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, nil, http.StatusOK, false, true}, + {"fail-principal", p1, args{t1, SignSSHOptions{Principals: []string{"smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, + {"fail-extra-principal", p1, args{t1, SignSSHOptions{Principals: []string{"virtualMachine", "smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, + {"fail-sshCA-disabled", p3, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, + {"fail-invalid-token", p1, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 507eea1b..5ad00a96 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -16,6 +16,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/x509util" ) // gcpCertsURL is the url that serves Google OAuth2 public keys. @@ -84,6 +85,7 @@ type GCP struct { DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` InstanceAge Duration `json:"instanceAge,omitempty"` Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` claimer *Claimer config *gcpConfig keyStore *keyStore @@ -215,6 +217,14 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er } ce := claims.Google.ComputeEngine + + // Template options + data := x509util.NewTemplateData() + data.SetCommonName(ce.InstanceName) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + // Enforce known common name and default DNS if configured. // By default we we'll accept the CN and SANs in the CSR. // There's no way to trust them other than TOFU. @@ -231,9 +241,18 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er so = append(so, ipAddressesValidator(nil)) so = append(so, emailAddressesValidator(nil)) so = append(so, urisValidator(nil)) + + // Template SANs + data.SetSANs([]string{dnsName1, dnsName2}) + } + + templateOptions, err := CustomTemplateOptions(p.Options, data, x509util.DefaultIIDLeafTemplate) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "gcp.AuthorizeSign") } return append(so, + templateOptions, // modifiers / withOptions newProvisionerExtensionOption(TypeGCP, p.Name, claims.Subject, "InstanceID", ce.InstanceID, "InstanceName", ce.InstanceName), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), @@ -375,7 +394,7 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, } // Default to host + known hostnames - defaults := SSHOptions{ + defaults := SignSSHOptions{ CertType: SSHHostCert, Principals: principals, } diff --git a/authority/provisioner/gcp_test.go b/authority/provisioner/gcp_test.go index a9c781d2..23e306f4 100644 --- a/authority/provisioner/gcp_test.go +++ b/authority/provisioner/gcp_test.go @@ -515,9 +515,9 @@ func TestGCP_AuthorizeSign(t *testing.T) { code int wantErr bool }{ - {"ok", p1, args{t1}, 4, http.StatusOK, false}, - {"ok", p2, args{t2}, 9, http.StatusOK, false}, - {"ok", p3, args{t3}, 4, http.StatusOK, false}, + {"ok", p1, args{t1}, 5, http.StatusOK, false}, + {"ok", p2, args{t2}, 10, http.StatusOK, false}, + {"ok", p3, args{t3}, 5, http.StatusOK, false}, {"fail token", p1, args{"token"}, 0, http.StatusUnauthorized, true}, {"fail key", p1, args{failKey}, 0, http.StatusUnauthorized, true}, {"fail iss", p1, args{failIss}, 0, http.StatusUnauthorized, true}, @@ -547,6 +547,7 @@ func TestGCP_AuthorizeSign(t *testing.T) { assert.Len(t, tt.wantLen, got) for _, o := range got { switch v := o.(type) { + case certificateOptionsFunc: case *provisionerExtensionOption: assert.Equals(t, v.Type, int(TypeGCP)) assert.Equals(t, v.Name, tt.gcp.GetName()) @@ -622,51 +623,51 @@ func TestGCP_AuthorizeSSHSign(t *testing.T) { assert.FatalError(t, err) hostDuration := p1.claimer.DefaultHostSSHCertDuration() - expectedHostOptions := &SSHOptions{ + expectedHostOptions := &SignSSHOptions{ CertType: "host", Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } - expectedHostOptionsPrincipal1 := &SSHOptions{ + expectedHostOptionsPrincipal1 := &SignSSHOptions{ CertType: "host", Principals: []string{"instance-name.c.project-id.internal"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } - expectedHostOptionsPrincipal2 := &SSHOptions{ + expectedHostOptionsPrincipal2 := &SignSSHOptions{ CertType: "host", Principals: []string{"instance-name.zone.c.project-id.internal"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } - expectedCustomOptions := &SSHOptions{ + expectedCustomOptions := &SignSSHOptions{ CertType: "host", Principals: []string{"foo.bar", "bar.foo"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } type args struct { token string - sshOpts SSHOptions + sshOpts SignSSHOptions key interface{} } tests := []struct { name string gcp *GCP args args - expected *SSHOptions + expected *SignSSHOptions code int wantErr bool wantSignErr bool }{ - {"ok", p1, args{t1, SSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-type", p1, args{t1, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-principal1", p1, args{t1, SSHOptions{Principals: []string{"instance-name.c.project-id.internal"}}, pub}, expectedHostOptionsPrincipal1, http.StatusOK, false, false}, - {"ok-principal2", p1, args{t1, SSHOptions{Principals: []string{"instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptionsPrincipal2, http.StatusOK, false, false}, - {"ok-options", p1, args{t1, SSHOptions{CertType: "host", Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-custom", p2, args{t2, SSHOptions{Principals: []string{"foo.bar", "bar.foo"}}, pub}, expectedCustomOptions, http.StatusOK, false, false}, - {"fail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true}, - {"fail-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, nil, http.StatusOK, false, true}, - {"fail-principal", p1, args{t1, SSHOptions{Principals: []string{"smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, - {"fail-extra-principal", p1, args{t1, SSHOptions{Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal", "smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, - {"fail-sshCA-disabled", p3, args{"foo", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, - {"fail-invalid-token", p1, args{"foo", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, + {"ok", p1, args{t1, SignSSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-type", p1, args{t1, SignSSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-principal1", p1, args{t1, SignSSHOptions{Principals: []string{"instance-name.c.project-id.internal"}}, pub}, expectedHostOptionsPrincipal1, http.StatusOK, false, false}, + {"ok-principal2", p1, args{t1, SignSSHOptions{Principals: []string{"instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptionsPrincipal2, http.StatusOK, false, false}, + {"ok-options", p1, args{t1, SignSSHOptions{CertType: "host", Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-custom", p2, args{t2, SignSSHOptions{Principals: []string{"foo.bar", "bar.foo"}}, pub}, expectedCustomOptions, http.StatusOK, false, false}, + {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true}, + {"fail-type", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, nil, http.StatusOK, false, true}, + {"fail-principal", p1, args{t1, SignSSHOptions{Principals: []string{"smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, + {"fail-extra-principal", p1, args{t1, SignSSHOptions{Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal", "smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, + {"fail-sshCA-disabled", p3, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, + {"fail-invalid-token", p1, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index cc513dc6..2716a8d9 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/x509util" ) // jwtPayload extends jwt.Claims with step attributes. @@ -19,7 +20,7 @@ type jwtPayload struct { } type stepPayload struct { - SSH *SSHOptions `json:"ssh,omitempty"` + SSH *SignSSHOptions `json:"ssh,omitempty"` } // JWK is the default provisioner, an entity that can sign tokens necessary for @@ -31,6 +32,7 @@ type JWK struct { Key *jose.JSONWebKey `json:"key"` EncryptedKey string `json:"encryptedKey,omitempty"` Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` claimer *Claimer audiences Audiences } @@ -151,7 +153,19 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er claims.SANs = []string{claims.Subject} } + // Certificate templates + data := x509util.CreateTemplateData(claims.Subject, claims.SANs) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + + templateOptions, err := TemplateOptions(p.Options, data) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign") + } + return []SignOption{ + templateOptions, // modifiers / withOptions newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 5644f49f..2048bc64 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -295,9 +295,10 @@ func TestJWK_AuthorizeSign(t *testing.T) { } } else { if assert.NotNil(t, got) { - assert.Len(t, 6, got) + assert.Len(t, 7, got) for _, o := range got { switch v := o.(type) { + case certificateOptionsFunc: case *provisionerExtensionOption: assert.Equals(t, v.Type, int(TypeJWK)) assert.Equals(t, v.Name, tt.prov.GetName()) @@ -403,41 +404,41 @@ func TestJWK_AuthorizeSSHSign(t *testing.T) { userDuration := p1.claimer.DefaultUserSSHCertDuration() hostDuration := p1.claimer.DefaultHostSSHCertDuration() - expectedUserOptions := &SSHOptions{ + expectedUserOptions := &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)), } - expectedHostOptions := &SSHOptions{ + expectedHostOptions := &SignSSHOptions{ CertType: "host", Principals: []string{"smallstep.com"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } type args struct { token string - sshOpts SSHOptions + sshOpts SignSSHOptions key interface{} } tests := []struct { name string prov *JWK args args - expected *SSHOptions + expected *SignSSHOptions code int wantErr bool wantSignErr bool }{ - {"user", p1, args{t1, SSHOptions{}, pub}, expectedUserOptions, http.StatusOK, false, false}, - {"user-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedUserOptions, http.StatusOK, false, false}, - {"user-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false}, - {"user-principals", p1, args{t1, SSHOptions{Principals: []string{"name"}}, pub}, expectedUserOptions, http.StatusOK, false, false}, - {"user-options", p1, args{t1, SSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, expectedUserOptions, http.StatusOK, false, false}, - {"host", p1, args{t2, SSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"host-type", p1, args{t2, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"host-principals", p1, args{t2, SSHOptions{Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"host-options", p1, args{t2, SSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"fail-sshCA-disabled", p2, args{"foo", SSHOptions{}, pub}, expectedUserOptions, http.StatusUnauthorized, true, false}, - {"fail-signature", p1, args{failSig, SSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, - {"rail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true}, + {"user", p1, args{t1, SignSSHOptions{}, pub}, expectedUserOptions, http.StatusOK, false, false}, + {"user-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedUserOptions, http.StatusOK, false, false}, + {"user-type", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false}, + {"user-principals", p1, args{t1, SignSSHOptions{Principals: []string{"name"}}, pub}, expectedUserOptions, http.StatusOK, false, false}, + {"user-options", p1, args{t1, SignSSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, expectedUserOptions, http.StatusOK, false, false}, + {"host", p1, args{t2, SignSSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"host-type", p1, args{t2, SignSSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"host-principals", p1, args{t2, SignSSHOptions{Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"host-options", p1, args{t2, SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"fail-sshCA-disabled", p2, args{"foo", SignSSHOptions{}, pub}, expectedUserOptions, http.StatusUnauthorized, true, false}, + {"fail-signature", p1, args{failSig, SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, + {"rail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -486,72 +487,72 @@ func TestJWK_AuthorizeSign_SSHOptions(t *testing.T) { userDuration := p1.claimer.DefaultUserSSHCertDuration() hostDuration := p1.claimer.DefaultHostSSHCertDuration() - expectedUserOptions := &SSHOptions{ + expectedUserOptions := &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)), } - expectedHostOptions := &SSHOptions{ + expectedHostOptions := &SignSSHOptions{ CertType: "host", Principals: []string{"smallstep.com"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } type args struct { sub, iss, aud string iat time.Time - tokSSHOpts *SSHOptions - userSSHOpts *SSHOptions + tokSSHOpts *SignSSHOptions + userSSHOpts *SignSSHOptions jwk *jose.JSONWebKey } tests := []struct { name string prov *JWK args args - expected *SSHOptions + expected *SignSSHOptions wantErr bool wantSignErr bool }{ - {"ok-user", p1, args{sub, iss, aud, iat, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, jwk}, expectedUserOptions, false, false}, - {"ok-host", p1, args{sub, iss, aud, iat, &SSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, &SSHOptions{}, jwk}, expectedHostOptions, false, false}, - {"ok-user-opts", p1, args{sub, iss, aud, iat, &SSHOptions{}, &SSHOptions{CertType: "user", Principals: []string{"name"}}, jwk}, expectedUserOptions, false, false}, - {"ok-host-opts", p1, args{sub, iss, aud, iat, &SSHOptions{}, &SSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, jwk}, expectedHostOptions, false, false}, - {"ok-user-mixed", p1, args{sub, iss, aud, iat, &SSHOptions{CertType: "user"}, &SSHOptions{Principals: []string{"name"}}, jwk}, expectedUserOptions, false, false}, - {"ok-host-mixed", p1, args{sub, iss, aud, iat, &SSHOptions{Principals: []string{"smallstep.com"}}, &SSHOptions{CertType: "host"}, jwk}, expectedHostOptions, false, false}, - {"ok-user-validAfter", p1, args{sub, iss, aud, iat, &SSHOptions{ + {"ok-user", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, &SignSSHOptions{}, jwk}, expectedUserOptions, false, false}, + {"ok-host", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, &SignSSHOptions{}, jwk}, expectedHostOptions, false, false}, + {"ok-user-opts", p1, args{sub, iss, aud, iat, &SignSSHOptions{}, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, jwk}, expectedUserOptions, false, false}, + {"ok-host-opts", p1, args{sub, iss, aud, iat, &SignSSHOptions{}, &SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, jwk}, expectedHostOptions, false, false}, + {"ok-user-mixed", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "user"}, &SignSSHOptions{Principals: []string{"name"}}, jwk}, expectedUserOptions, false, false}, + {"ok-host-mixed", p1, args{sub, iss, aud, iat, &SignSSHOptions{Principals: []string{"smallstep.com"}}, &SignSSHOptions{CertType: "host"}, jwk}, expectedHostOptions, false, false}, + {"ok-user-validAfter", p1, args{sub, iss, aud, iat, &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, - }, &SSHOptions{ + }, &SignSSHOptions{ ValidAfter: NewTimeDuration(tm.Add(-time.Hour)), - }, jwk}, &SSHOptions{ + }, jwk}, &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm.Add(-time.Hour)), ValidBefore: NewTimeDuration(tm.Add(userDuration - time.Hour)), }, false, false}, - {"ok-user-validBefore", p1, args{sub, iss, aud, iat, &SSHOptions{ + {"ok-user-validBefore", p1, args{sub, iss, aud, iat, &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, - }, &SSHOptions{ + }, &SignSSHOptions{ ValidBefore: NewTimeDuration(tm.Add(time.Hour)), - }, jwk}, &SSHOptions{ + }, jwk}, &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(time.Hour)), }, false, false}, - {"ok-user-validAfter-validBefore", p1, args{sub, iss, aud, iat, &SSHOptions{ + {"ok-user-validAfter-validBefore", p1, args{sub, iss, aud, iat, &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, - }, &SSHOptions{ + }, &SignSSHOptions{ ValidAfter: NewTimeDuration(tm.Add(10 * time.Minute)), ValidBefore: NewTimeDuration(tm.Add(time.Hour)), - }, jwk}, &SSHOptions{ + }, jwk}, &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm.Add(10 * time.Minute)), ValidBefore: NewTimeDuration(tm.Add(time.Hour)), }, false, false}, - {"ok-user-match", p1, args{sub, iss, aud, iat, &SSHOptions{ + {"ok-user-match", p1, args{sub, iss, aud, iat, &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(1 * time.Hour)), - }, &SSHOptions{ + }, &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(1 * time.Hour)), - }, jwk}, &SSHOptions{ + }, jwk}, &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(time.Hour)), }, false, false}, - {"fail-certType", p1, args{sub, iss, aud, iat, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{CertType: "host"}, jwk}, nil, false, true}, - {"fail-principals", p1, args{sub, iss, aud, iat, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{Principals: []string{"root"}}, jwk}, nil, false, true}, - {"fail-validAfter", p1, args{sub, iss, aud, iat, &SSHOptions{CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm)}, &SSHOptions{ValidAfter: NewTimeDuration(tm.Add(time.Hour))}, jwk}, nil, false, true}, - {"fail-validBefore", p1, args{sub, iss, aud, iat, &SSHOptions{CertType: "user", Principals: []string{"name"}, ValidBefore: NewTimeDuration(tm.Add(time.Hour))}, &SSHOptions{ValidBefore: NewTimeDuration(tm.Add(10 * time.Hour))}, jwk}, nil, false, true}, - {"fail-subject", p1, args{"", iss, aud, iat, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, jwk}, nil, true, false}, - {"fail-issuer", p1, args{sub, "invalid", aud, iat, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, jwk}, nil, true, false}, - {"fail-audience", p1, args{sub, iss, "invalid", iat, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, jwk}, nil, true, false}, - {"fail-expired", p1, args{sub, iss, aud, iat.Add(-6 * time.Minute), &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, jwk}, nil, true, false}, - {"fail-notBefore", p1, args{sub, iss, aud, iat.Add(5 * time.Minute), &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, jwk}, nil, true, false}, + {"fail-certType", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, &SignSSHOptions{CertType: "host"}, jwk}, nil, false, true}, + {"fail-principals", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, &SignSSHOptions{Principals: []string{"root"}}, jwk}, nil, false, true}, + {"fail-validAfter", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm)}, &SignSSHOptions{ValidAfter: NewTimeDuration(tm.Add(time.Hour))}, jwk}, nil, false, true}, + {"fail-validBefore", p1, args{sub, iss, aud, iat, &SignSSHOptions{CertType: "user", Principals: []string{"name"}, ValidBefore: NewTimeDuration(tm.Add(time.Hour))}, &SignSSHOptions{ValidBefore: NewTimeDuration(tm.Add(10 * time.Hour))}, jwk}, nil, false, true}, + {"fail-subject", p1, args{"", iss, aud, iat, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, &SignSSHOptions{}, jwk}, nil, true, false}, + {"fail-issuer", p1, args{sub, "invalid", aud, iat, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, &SignSSHOptions{}, jwk}, nil, true, false}, + {"fail-audience", p1, args{sub, iss, "invalid", iat, &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, &SignSSHOptions{}, jwk}, nil, true, false}, + {"fail-expired", p1, args{sub, iss, aud, iat.Add(-6 * time.Minute), &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, &SignSSHOptions{}, jwk}, nil, true, false}, + {"fail-notBefore", p1, args{sub, iss, aud, iat.Add(5 * time.Minute), &SignSSHOptions{CertType: "user", Principals: []string{"name"}}, &SignSSHOptions{}, jwk}, nil, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -560,7 +561,7 @@ func TestJWK_AuthorizeSign_SSHOptions(t *testing.T) { if got, err := tt.prov.AuthorizeSSHSign(context.Background(), token); (err != nil) != tt.wantErr { t.Errorf("JWK.AuthorizeSSHSign() error = %v, wantErr %v", err, tt.wantErr) } else if !tt.wantErr && assert.NotNil(t, got) { - var opts SSHOptions + var opts SignSSHOptions if tt.args.userSSHOpts != nil { opts = *tt.args.userSSHOpts } diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index b63ce979..b830e098 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -3,6 +3,7 @@ package provisioner import ( "context" "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "crypto/x509" "encoding/pem" @@ -12,7 +13,7 @@ import ( "github.com/smallstep/certificates/errs" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/jose" - "golang.org/x/crypto/ed25519" + "go.step.sm/crypto/x509util" ) // NOTE: There can be at most one kubernetes service account provisioner configured @@ -40,10 +41,11 @@ type k8sSAPayload struct { // entity trusted to make signature requests. type K8sSA struct { *base - Type string `json:"type"` - Name string `json:"name"` - Claims *Claims `json:"claims,omitempty"` - PubKeys []byte `json:"publicKeys,omitempty"` + 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 //kauthn kauthn.AuthenticationV1Interface @@ -204,11 +206,27 @@ func (p *K8sSA) AuthorizeRevoke(ctx context.Context, token string) error { // AuthorizeSign validates the given token. func (p *K8sSA) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) { - if _, err := p.authorizeToken(token, p.audiences.Sign); err != nil { + claims, err := p.authorizeToken(token, p.audiences.Sign) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSign") + } + + // Add some values to use in custom templates. + data := x509util.NewTemplateData() + data.SetCommonName(claims.ServiceAccountName) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + + // Certificate templates: on K8sSA the default template is the certificate + // request. + templateOptions, err := CustomTemplateOptions(p.Options, data, x509util.CertificateRequestTemplate) + if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSign") } return []SignOption{ + templateOptions, // modifiers / withOptions newProvisionerExtensionOption(TypeK8sSA, p.Name, ""), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), diff --git a/authority/provisioner/k8sSA_test.go b/authority/provisioner/k8sSA_test.go index f1d12b4a..16a57aaf 100644 --- a/authority/provisioner/k8sSA_test.go +++ b/authority/provisioner/k8sSA_test.go @@ -274,6 +274,7 @@ func TestK8sSA_AuthorizeSign(t *testing.T) { tot := 0 for _, o := range opts { switch v := o.(type) { + case certificateOptionsFunc: case *provisionerExtensionOption: assert.Equals(t, v.Type, int(TypeK8sSA)) assert.Equals(t, v.Name, tc.p.GetName()) @@ -290,7 +291,7 @@ func TestK8sSA_AuthorizeSign(t *testing.T) { } tot++ } - assert.Equals(t, tot, 4) + assert.Equals(t, tot, 5) } } } diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index c7acc6f5..295dbb59 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -14,6 +14,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/x509util" ) // openIDConfiguration contains the necessary properties in the @@ -63,6 +64,7 @@ type OIDC struct { Groups []string `json:"groups,omitempty"` ListenAddress string `json:"listenAddress,omitempty"` Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` configuration openIDConfiguration keyStore *keyStore claimer *Claimer @@ -72,10 +74,12 @@ type OIDC struct { // IsAdmin returns true if the given email is in the Admins allowlist, false // otherwise. func (o *OIDC) IsAdmin(email string) bool { - email = sanitizeEmail(email) - for _, e := range o.Admins { - if email == sanitizeEmail(e) { - return true + if email != "" { + email = sanitizeEmail(email) + for _, e := range o.Admins { + if email == sanitizeEmail(e) { + return true + } } } return false @@ -203,13 +207,8 @@ func (o *OIDC) ValidatePayload(p openIDPayload) error { return errs.Unauthorized("validatePayload: failed to validate oidc token payload: invalid azp") } - // Enforce an email claim - if p.Email == "" { - return errs.Unauthorized("validatePayload: failed to validate oidc token payload: email not found") - } - // Validate domains (case-insensitive) - if !o.IsAdmin(p.Email) && len(o.Domains) > 0 { + if p.Email != "" && len(o.Domains) > 0 && !o.IsAdmin(p.Email) { email := sanitizeEmail(p.Email) var found bool for _, d := range o.Domains { @@ -301,20 +300,49 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSign") } - so := []SignOption{ + // Certificate templates + sans := []string{} + if claims.Email != "" { + sans = append(sans, claims.Email) + } + + // Add uri SAN with iss#sub if issuer is a URL with schema. + // + // According to https://openid.net/specs/openid-connect-core-1_0.html the + // iss value is a case sensitive URL using the https scheme that contains + // scheme, host, and optionally, port number and path components and no + // query or fragment components. + if iss, err := url.Parse(claims.Issuer); err == nil && iss.Scheme != "" { + iss.Fragment = claims.Subject + sans = append(sans, iss.String()) + } + + data := x509util.CreateTemplateData(claims.Subject, sans) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + + // Use the default template unless no-templates are configured and email is + // an admin, in that case we will use the CR template. + defaultTemplate := x509util.DefaultLeafTemplate + if !o.Options.GetX509Options().HasTemplate() && o.IsAdmin(claims.Email) { + defaultTemplate = x509util.CertificateRequestTemplate + } + + templateOptions, err := CustomTemplateOptions(o.Options, data, defaultTemplate) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSign") + } + + return []SignOption{ + templateOptions, // modifiers / withOptions newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID), profileDefaultDuration(o.claimer.DefaultTLSCertDuration()), // validators defaultPublicKeyValidator{}, newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()), - } - // Admins should be able to authorize any SAN - if o.IsAdmin(claims.Email) { - return so, nil - } - - return append(so, emailOnlyIdentity(claims.Email)), nil + }, nil } // AuthorizeRenew returns an error if the renewal is disabled. @@ -337,6 +365,10 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSSHSign") } + // Enforce an email claim + if claims.Email == "" { + return nil, errs.Unauthorized("oidc.AuthorizeSSHSign: failed to validate oidc token payload: email not found") + } signOptions := []SignOption{ // set the key id to the token email sshCertKeyIDModifier(claims.Email), @@ -348,7 +380,7 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSSHSign") } - defaults := SSHOptions{ + defaults := SignSSHOptions{ CertType: SSHUserCert, Principals: iden.Usernames, } diff --git a/authority/provisioner/oidc_test.go b/authority/provisioner/oidc_test.go index c879a66b..55093a91 100644 --- a/authority/provisioner/oidc_test.go +++ b/authority/provisioner/oidc_test.go @@ -179,12 +179,12 @@ func TestOIDC_authorizeToken(t *testing.T) { assert.FatalError(t, err) t4, err := generateToken("subject", issuer, p3.ClientID, "foo@smallstep.com", []string{}, time.Now(), &keys.Keys[2]) assert.FatalError(t, err) - // Invalid email - failEmail, err := generateToken("subject", issuer, p3.ClientID, "", []string{}, time.Now(), &keys.Keys[2]) - assert.FatalError(t, err) - failDomain, err := generateToken("subject", issuer, p3.ClientID, "name@example.com", []string{}, time.Now(), &keys.Keys[2]) + t5, err := generateToken("subject", issuer, p3.ClientID, "", []string{}, time.Now(), &keys.Keys[2]) assert.FatalError(t, err) + // Invalid email + failDomain, err := generateToken("subject", issuer, p3.ClientID, "name@example.com", []string{}, time.Now(), &keys.Keys[2]) + assert.FatalError(t, err) // Invalid tokens parts := strings.Split(t1, ".") key, err := generateJSONWebKey() @@ -226,7 +226,7 @@ func TestOIDC_authorizeToken(t *testing.T) { {"ok tenantid", p2, args{t2}, http.StatusOK, tenantIssuer, false}, {"ok admin", p3, args{t3}, http.StatusOK, issuer, false}, {"ok domain", p3, args{t4}, http.StatusOK, issuer, false}, - {"fail-email", p3, args{failEmail}, http.StatusUnauthorized, "", true}, + {"ok no email", p3, args{t5}, http.StatusOK, issuer, false}, {"fail-domain", p3, args{failDomain}, http.StatusUnauthorized, "", true}, {"fail-key", p1, args{failKey}, http.StatusUnauthorized, "", true}, {"fail-token", p1, args{failTok}, http.StatusUnauthorized, "", true}, @@ -290,8 +290,8 @@ func TestOIDC_AuthorizeSign(t *testing.T) { // Admin email not in domains okAdmin, err := generateToken("subject", "the-issuer", p3.ClientID, "root@example.com", []string{"test.smallstep.com"}, time.Now(), &keys.Keys[0]) assert.FatalError(t, err) - // Invalid email - failEmail, err := generateToken("subject", "the-issuer", p3.ClientID, "", []string{}, time.Now(), &keys.Keys[0]) + // No email + noEmail, err := generateToken("subject", "the-issuer", p3.ClientID, "", []string{}, time.Now(), &keys.Keys[0]) assert.FatalError(t, err) type args struct { @@ -306,7 +306,8 @@ func TestOIDC_AuthorizeSign(t *testing.T) { }{ {"ok1", p1, args{t1}, http.StatusOK, false}, {"admin", p3, args{okAdmin}, http.StatusOK, false}, - {"fail-email", p3, args{failEmail}, http.StatusUnauthorized, true}, + {"no-email", p3, args{noEmail}, http.StatusOK, false}, + {"bad-token", p3, args{"foobar"}, http.StatusUnauthorized, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -323,12 +324,13 @@ func TestOIDC_AuthorizeSign(t *testing.T) { } else { if assert.NotNil(t, got) { if tt.name == "admin" { - assert.Len(t, 4, got) + assert.Len(t, 5, got) } else { assert.Len(t, 5, got) } for _, o := range got { switch v := o.(type) { + case certificateOptionsFunc: case *provisionerExtensionOption: assert.Equals(t, v.Type, int(TypeOIDC)) assert.Equals(t, v.Name, tt.prov.GetName()) @@ -514,7 +516,7 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) { // Admin email not in domains okAdmin, err := generateToken("subject", "the-issuer", p3.ClientID, "root@example.com", []string{}, time.Now(), &keys.Keys[0]) assert.FatalError(t, err) - // Invalid email + // Empty email failEmail, err := generateToken("subject", "the-issuer", p3.ClientID, "", []string{}, time.Now(), &keys.Keys[0]) assert.FatalError(t, err) @@ -532,64 +534,64 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) { userDuration := p1.claimer.DefaultUserSSHCertDuration() hostDuration := p1.claimer.DefaultHostSSHCertDuration() - expectedUserOptions := &SSHOptions{ + expectedUserOptions := &SignSSHOptions{ CertType: "user", Principals: []string{"name", "name@smallstep.com"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)), } - expectedAdminOptions := &SSHOptions{ + expectedAdminOptions := &SignSSHOptions{ CertType: "user", Principals: []string{"root", "root@example.com"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)), } - expectedHostOptions := &SSHOptions{ + expectedHostOptions := &SignSSHOptions{ CertType: "host", Principals: []string{"smallstep.com"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } type args struct { token string - sshOpts SSHOptions + sshOpts SignSSHOptions key interface{} } tests := []struct { name string prov *OIDC args args - expected *SSHOptions + expected *SignSSHOptions code int wantErr bool wantSignErr bool }{ - {"ok", p1, args{t1, SSHOptions{}, pub}, expectedUserOptions, http.StatusOK, false, false}, - {"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedUserOptions, http.StatusOK, false, false}, - {"ok-user", p1, args{t1, SSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false}, - {"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"name"}}, pub}, - &SSHOptions{CertType: "user", Principals: []string{"name"}, + {"ok", p1, args{t1, SignSSHOptions{}, pub}, expectedUserOptions, http.StatusOK, false, false}, + {"ok-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedUserOptions, http.StatusOK, false, false}, + {"ok-user", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false}, + {"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"name"}}, pub}, + &SignSSHOptions{CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, - {"ok-principals-getIdentity", p4, args{okGetIdentityToken, SSHOptions{Principals: []string{"mariano"}}, pub}, - &SSHOptions{CertType: "user", Principals: []string{"mariano"}, + {"ok-principals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{Principals: []string{"mariano"}}, pub}, + &SignSSHOptions{CertType: "user", Principals: []string{"mariano"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, - {"ok-emptyPrincipals-getIdentity", p4, args{okGetIdentityToken, SSHOptions{}, pub}, - &SSHOptions{CertType: "user", Principals: []string{"max", "mariano"}, + {"ok-emptyPrincipals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{}, pub}, + &SignSSHOptions{CertType: "user", Principals: []string{"max", "mariano"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, - {"ok-options", p1, args{t1, SSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, - &SSHOptions{CertType: "user", Principals: []string{"name"}, + {"ok-options", p1, args{t1, SignSSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, + &SignSSHOptions{CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, - {"admin", p3, args{okAdmin, SSHOptions{}, pub}, expectedAdminOptions, http.StatusOK, false, false}, - {"admin-user", p3, args{okAdmin, SSHOptions{CertType: "user"}, pub}, expectedAdminOptions, http.StatusOK, false, false}, - {"admin-principals", p3, args{okAdmin, SSHOptions{Principals: []string{"root"}}, pub}, - &SSHOptions{CertType: "user", Principals: []string{"root"}, + {"admin", p3, args{okAdmin, SignSSHOptions{}, pub}, expectedAdminOptions, http.StatusOK, false, false}, + {"admin-user", p3, args{okAdmin, SignSSHOptions{CertType: "user"}, pub}, expectedAdminOptions, http.StatusOK, false, false}, + {"admin-principals", p3, args{okAdmin, SignSSHOptions{Principals: []string{"root"}}, pub}, + &SignSSHOptions{CertType: "user", Principals: []string{"root"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, - {"admin-options", p3, args{okAdmin, SSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, - &SSHOptions{CertType: "user", Principals: []string{"name"}, + {"admin-options", p3, args{okAdmin, SignSSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, + &SignSSHOptions{CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, - {"admin-host", p3, args{okAdmin, SSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, + {"admin-host", p3, args{okAdmin, SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, - {"fail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true}, - {"fail-user-host", p1, args{t1, SSHOptions{CertType: "host"}, pub}, nil, http.StatusOK, false, true}, - {"fail-user-principals", p1, args{t1, SSHOptions{Principals: []string{"root"}}, pub}, nil, http.StatusOK, false, true}, - {"fail-email", p3, args{failEmail, SSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, - {"fail-getIdentity", p5, args{failGetIdentityToken, SSHOptions{}, pub}, nil, http.StatusInternalServerError, true, false}, - {"fail-sshCA-disabled", p6, args{"foo", SSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, + {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true}, + {"fail-user-host", p1, args{t1, SignSSHOptions{CertType: "host"}, pub}, nil, http.StatusOK, false, true}, + {"fail-user-principals", p1, args{t1, SignSSHOptions{Principals: []string{"root"}}, pub}, nil, http.StatusOK, false, true}, + {"fail-email", p3, args{failEmail, SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, + {"fail-getIdentity", p5, args{failGetIdentityToken, SignSSHOptions{}, pub}, nil, http.StatusInternalServerError, true, false}, + {"fail-sshCA-disabled", p6, args{"foo", SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go new file mode 100644 index 00000000..88e22523 --- /dev/null +++ b/authority/provisioner/options.go @@ -0,0 +1,136 @@ +package provisioner + +import ( + "encoding/json" + "strings" + + "github.com/pkg/errors" + "github.com/smallstep/cli/jose" + "go.step.sm/crypto/x509util" +) + +// CertificateOptions is an interface that returns a list of options passed when +// creating a new certificate. +type CertificateOptions interface { + Options(SignOptions) []x509util.Option +} + +type certificateOptionsFunc func(SignOptions) []x509util.Option + +func (fn certificateOptionsFunc) Options(so SignOptions) []x509util.Option { + return fn(so) +} + +// Options are a collection of custom options that can be added to +// each provisioner. +type Options struct { + X509 *X509Options `json:"x509,omitempty"` +} + +// GetX509Options returns the X.509Options +func (o *Options) GetX509Options() *X509Options { + if o == nil { + return nil + } + return o.X509 +} + +// X509Options contains specific options for X.509 certificates. +type X509Options struct { + // Template contains a X.509 certificate template. It can be a JSON template + // escaped in a string or it can be also encoded in base64. + Template string `json:"template"` + + // TemplateFile points to a file containing a X.509 certificate template. + TemplateFile string `json:"templateFile"` + + // TemplateData is a JSON object with variables that can be used in custom + // templates. + TemplateData json.RawMessage `json:"templateData"` +} + +// HasTemplate returns true if a template is defined in the provisioner options. +func (o *X509Options) HasTemplate() bool { + return o != nil && (o.Template != "" || o.TemplateFile != "") +} + +// TemplateOptions generates a CertificateOptions with the template and data +// defined in the ProvisionerOptions, the provisioner generated data, and the +// user data provided in the request. If no template has been provided, +// x509util.DefaultLeafTemplate will be used. +func TemplateOptions(o *Options, data x509util.TemplateData) (CertificateOptions, error) { + return CustomTemplateOptions(o, data, x509util.DefaultLeafTemplate) +} + +// CustomTemplateOptions generates a CertificateOptions with the template, data +// defined in the ProvisionerOptions, the provisioner generated data and the +// user data provided in the request. If no template has been provided in the +// ProvisionerOptions, the given template will be used. +func CustomTemplateOptions(o *Options, data x509util.TemplateData, defaultTemplate string) (CertificateOptions, error) { + opts := o.GetX509Options() + if data == nil { + data = x509util.NewTemplateData() + } + + if opts != nil { + // Add template data if any. + if len(opts.TemplateData) > 0 { + if err := json.Unmarshal(opts.TemplateData, &data); err != nil { + return nil, errors.Wrap(err, "error unmarshaling template data") + } + } + } + + return certificateOptionsFunc(func(so SignOptions) []x509util.Option { + // We're not provided user data without custom templates. + if !opts.HasTemplate() { + return []x509util.Option{ + x509util.WithTemplate(defaultTemplate, data), + } + } + + // Add user provided data. + if len(so.TemplateData) > 0 { + userObject := make(map[string]interface{}) + if err := json.Unmarshal(so.TemplateData, &userObject); err != nil { + data.SetUserData(map[string]interface{}{}) + } else { + data.SetUserData(userObject) + } + } + + // Load a template from a file if Template is not defined. + if opts.Template == "" && opts.TemplateFile != "" { + return []x509util.Option{ + x509util.WithTemplateFile(opts.TemplateFile, data), + } + } + + // Load a template from the Template fields + // 1. As a JSON in a string. + template := strings.TrimSpace(opts.Template) + if strings.HasPrefix(template, "{") { + return []x509util.Option{ + x509util.WithTemplate(template, data), + } + } + // 2. As a base64 encoded JSON. + return []x509util.Option{ + x509util.WithTemplateBase64(template, data), + } + }), nil +} + +// unsafeParseSigned parses the given token and returns all the claims without +// verifying the signature of the token. +func unsafeParseSigned(s string) (map[string]interface{}, error) { + token, err := jose.ParseSigned(s) + if err != nil { + return nil, err + } + claims := make(map[string]interface{}) + if err = token.UnsafeClaimsWithoutVerification(&claims); err != nil { + return nil, err + } + return claims, nil +} diff --git a/authority/provisioner/options_test.go b/authority/provisioner/options_test.go new file mode 100644 index 00000000..519f1a58 --- /dev/null +++ b/authority/provisioner/options_test.go @@ -0,0 +1,260 @@ +package provisioner + +import ( + "bytes" + "crypto/x509" + "encoding/json" + "reflect" + "testing" + + "github.com/smallstep/cli/crypto/pemutil" + "go.step.sm/crypto/x509util" +) + +func parseCertificateRequest(t *testing.T, filename string) *x509.CertificateRequest { + t.Helper() + v, err := pemutil.Read(filename) + if err != nil { + t.Fatal(err) + } + csr, ok := v.(*x509.CertificateRequest) + if !ok { + t.Fatalf("%s is not a certificate request", filename) + } + return csr +} + +func TestOptions_GetX509Options(t *testing.T) { + type fields struct { + o *Options + } + tests := []struct { + name string + fields fields + want *X509Options + }{ + {"ok", fields{&Options{X509: &X509Options{Template: "foo"}}}, &X509Options{Template: "foo"}}, + {"nil", fields{&Options{}}, nil}, + {"nilOptions", fields{nil}, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fields.o.GetX509Options(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Options.GetX509Options() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvisionerX509Options_HasTemplate(t *testing.T) { + type fields struct { + Template string + TemplateFile string + TemplateData json.RawMessage + } + tests := []struct { + name string + fields fields + want bool + }{ + {"template", fields{Template: "the template"}, true}, + {"templateFile", fields{TemplateFile: "the template file"}, true}, + {"false", fields{}, false}, + {"falseWithTemplateData", fields{TemplateData: []byte(`{"foo":"bar"}`)}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &X509Options{ + Template: tt.fields.Template, + TemplateFile: tt.fields.TemplateFile, + TemplateData: tt.fields.TemplateData, + } + if got := o.HasTemplate(); got != tt.want { + t.Errorf("ProvisionerOptions.HasTemplate() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTemplateOptions(t *testing.T) { + csr := parseCertificateRequest(t, "testdata/certs/ecdsa.csr") + data := x509util.TemplateData{ + x509util.SubjectKey: x509util.Subject{ + CommonName: "foobar", + }, + x509util.SANsKey: []x509util.SubjectAlternativeName{ + {Type: "dns", Value: "foo.com"}, + }, + } + type args struct { + o *Options + data x509util.TemplateData + } + tests := []struct { + name string + args args + want x509util.Options + wantErr bool + }{ + {"ok", args{nil, data}, x509util.Options{ + CertBuffer: bytes.NewBufferString(`{ + "subject": {"commonName":"foobar"}, + "sans": [{"type":"dns","value":"foo.com"}], + "keyUsage": ["digitalSignature"], + "extKeyUsage": ["serverAuth", "clientAuth"] +}`)}, false}, + {"okCustomTemplate", args{&Options{X509: &X509Options{Template: x509util.DefaultIIDLeafTemplate}}, data}, x509util.Options{ + CertBuffer: bytes.NewBufferString(`{ + "subject": {"commonName":"foo"}, + "sans": [{"type":"dns","value":"foo.com"}], + "keyUsage": ["digitalSignature"], + "extKeyUsage": ["serverAuth", "clientAuth"] +}`)}, false}, + {"fail", args{&Options{X509: &X509Options{TemplateData: []byte(`{"badJSON`)}}, data}, x509util.Options{}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cof, err := TemplateOptions(tt.args.o, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("TemplateOptions() error = %v, wantErr %v", err, tt.wantErr) + return + } + var opts x509util.Options + if cof != nil { + for _, fn := range cof.Options(SignOptions{}) { + if err := fn(csr, &opts); err != nil { + t.Errorf("x509util.Options() error = %v", err) + return + } + } + } + if !reflect.DeepEqual(opts, tt.want) { + t.Errorf("x509util.Option = %v, want %v", opts, tt.want) + } + }) + } +} + +func TestCustomTemplateOptions(t *testing.T) { + csr := parseCertificateRequest(t, "testdata/certs/ecdsa.csr") + csrCertificate := `{"version":0,"subject":{"commonName":"foo"},"dnsNames":["foo"],"emailAddresses":null,"ipAddresses":null,"uris":null,"extensions":[{"id":"2.5.29.17","critical":false,"value":"MAWCA2Zvbw=="}]}` + data := x509util.TemplateData{ + x509util.SubjectKey: x509util.Subject{ + CommonName: "foobar", + }, + x509util.SANsKey: []x509util.SubjectAlternativeName{ + {Type: "dns", Value: "foo.com"}, + }, + } + type args struct { + o *Options + data x509util.TemplateData + defaultTemplate string + userOptions SignOptions + } + tests := []struct { + name string + args args + want x509util.Options + wantErr bool + }{ + {"ok", args{nil, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ + CertBuffer: bytes.NewBufferString(`{ + "subject": {"commonName":"foobar"}, + "sans": [{"type":"dns","value":"foo.com"}], + "keyUsage": ["digitalSignature"], + "extKeyUsage": ["serverAuth", "clientAuth"] +}`)}, false}, + {"okIID", args{nil, data, x509util.DefaultIIDLeafTemplate, SignOptions{}}, x509util.Options{ + CertBuffer: bytes.NewBufferString(`{ + "subject": {"commonName":"foo"}, + "sans": [{"type":"dns","value":"foo.com"}], + "keyUsage": ["digitalSignature"], + "extKeyUsage": ["serverAuth", "clientAuth"] +}`)}, false}, + {"okNoData", args{&Options{}, nil, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ + CertBuffer: bytes.NewBufferString(`{ + "subject": null, + "sans": null, + "keyUsage": ["digitalSignature"], + "extKeyUsage": ["serverAuth", "clientAuth"] +}`)}, false}, + {"okTemplateData", args{&Options{X509: &X509Options{TemplateData: []byte(`{"foo":"bar"}`)}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ + CertBuffer: bytes.NewBufferString(`{ + "subject": {"commonName":"foobar"}, + "sans": [{"type":"dns","value":"foo.com"}], + "keyUsage": ["digitalSignature"], + "extKeyUsage": ["serverAuth", "clientAuth"] +}`)}, false}, + {"okTemplate", args{&Options{X509: &X509Options{Template: "{{ toJson .Insecure.CR }}"}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ + CertBuffer: bytes.NewBufferString(csrCertificate)}, false}, + {"okFile", args{&Options{X509: &X509Options{TemplateFile: "./testdata/templates/cr.tpl"}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ + CertBuffer: bytes.NewBufferString(csrCertificate)}, false}, + {"okBase64", args{&Options{X509: &X509Options{Template: "e3sgdG9Kc29uIC5JbnNlY3VyZS5DUiB9fQ=="}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{ + CertBuffer: bytes.NewBufferString(csrCertificate)}, false}, + {"okUserOptions", args{&Options{X509: &X509Options{Template: `{"foo": "{{.Insecure.User.foo}}"}`}}, data, x509util.DefaultLeafTemplate, SignOptions{TemplateData: []byte(`{"foo":"bar"}`)}}, x509util.Options{ + CertBuffer: bytes.NewBufferString(`{"foo": "bar"}`), + }, false}, + {"okBadUserOptions", args{&Options{X509: &X509Options{Template: `{"foo": "{{.Insecure.User.foo}}"}`}}, data, x509util.DefaultLeafTemplate, SignOptions{TemplateData: []byte(`{"badJSON"}`)}}, x509util.Options{ + CertBuffer: bytes.NewBufferString(`{"foo": ""}`), + }, false}, + {"fail", args{&Options{X509: &X509Options{TemplateData: []byte(`{"badJSON`)}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{}, true}, + {"failTemplateData", args{&Options{X509: &X509Options{TemplateData: []byte(`{"badJSON}`)}}, data, x509util.DefaultLeafTemplate, SignOptions{}}, x509util.Options{}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cof, err := CustomTemplateOptions(tt.args.o, tt.args.data, tt.args.defaultTemplate) + if (err != nil) != tt.wantErr { + t.Errorf("CustomTemplateOptions() error = %v, wantErr %v", err, tt.wantErr) + return + } + var opts x509util.Options + if cof != nil { + for _, fn := range cof.Options(tt.args.userOptions) { + if err := fn(csr, &opts); err != nil { + t.Errorf("x509util.Options() error = %v", err) + return + } + } + } + if !reflect.DeepEqual(opts, tt.want) { + t.Errorf("x509util.Option = %v, want %v", opts, tt.want) + } + }) + } +} + +func Test_unsafeParseSigned(t *testing.T) { + okToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYW5lQGRvZS5jb20iLCJpc3MiOiJodHRwczovL2RvZS5jb20iLCJqdGkiOiI4ZmYzMjQ4MS1mZDVmLTRlMmUtOTZkZi05MDhjMTI3Yzg1ZjciLCJpYXQiOjE1OTUzNjAwMjgsImV4cCI6MTU5NTM2MzYyOH0.aid8UuhFucJOFHXaob9zpNtVvhul9ulTGsA52mU6XIw" + type args struct { + s string + } + tests := []struct { + name string + args args + want map[string]interface{} + wantErr bool + }{ + {"ok", args{okToken}, map[string]interface{}{ + "sub": "jane@doe.com", + "iss": "https://doe.com", + "jti": "8ff32481-fd5f-4e2e-96df-908c127c85f7", + "iat": float64(1595360028), + "exp": float64(1595363628), + }, false}, + {"failToken", args{"foobar"}, nil, true}, + {"failPayload", args{"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.ew.aid8UuhFucJOFHXaob9zpNtVvhul9ulTGsA52mU6XIw"}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := unsafeParseSigned(tt.args.s) + if (err != nil) != tt.wantErr { + t.Errorf("unsafeParseSigned() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("unsafeParseSigned() = \n%v, want \n%v", got, tt.want) + } + }) + } +} diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 4d8671e7..c413a100 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -245,7 +245,7 @@ func (l *List) UnmarshalJSON(data []byte) error { continue } if err := json.Unmarshal(data, p); err != nil { - return errors.Errorf("error unmarshaling provisioner") + return errors.Wrap(err, "error unmarshaling provisioner") } *l = append(*l, p) } diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 3da08bec..29d8320e 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -2,65 +2,77 @@ package provisioner import ( "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "encoding/json" "net" "net/url" "reflect" "time" "github.com/pkg/errors" - "github.com/smallstep/cli/crypto/x509util" - "golang.org/x/crypto/ed25519" + "go.step.sm/crypto/x509util" ) -// Options contains the options that can be passed to the Sign method. Backdate +// DefaultCertValidity is the default validity for a certificate if none is specified. +const DefaultCertValidity = 24 * time.Hour + +// SignOptions contains the options that can be passed to the Sign method. Backdate // is automatically filled and can only be configured in the CA. -type Options struct { - NotAfter TimeDuration `json:"notAfter"` - NotBefore TimeDuration `json:"notBefore"` - Backdate time.Duration `json:"-"` +type SignOptions struct { + NotAfter TimeDuration `json:"notAfter"` + NotBefore TimeDuration `json:"notBefore"` + TemplateData json.RawMessage `json:"templateData"` + Backdate time.Duration `json:"-"` } // SignOption is the interface used to collect all extra options used in the // Sign method. type SignOption interface{} -// CertificateValidator is the interface used to validate a X.509 certificate. +// CertificateValidator is an interface used to validate a given X.509 certificate. type CertificateValidator interface { - SignOption - Valid(cert *x509.Certificate, o Options) error + Valid(cert *x509.Certificate, opts SignOptions) error } -// CertificateRequestValidator is the interface used to validate a X.509 -// certificate request. +// CertificateRequestValidator is an interface used to validate a given X.509 certificate request. type CertificateRequestValidator interface { - SignOption - Valid(req *x509.CertificateRequest) error + Valid(cr *x509.CertificateRequest) error } -// ProfileModifier is the interface used to add custom options to the profile -// constructor. The options are used to modify the final certificate. -type ProfileModifier interface { - SignOption - Option(o Options) x509util.WithOption +// CertificateModifier is an interface used to modify a given X.509 certificate. +// Types implementing this interface will be validated with a +// CertificateValidator. +type CertificateModifier interface { + Modify(cert *x509.Certificate, opts SignOptions) error } -// CertificateEnforcer is the interface used to modify a certificate after -// validation. +// CertificateEnforcer is an interface used to modify a given X.509 certificate. +// Types implemented this interface will NOT be validated with a +// CertificateValidator. type CertificateEnforcer interface { - SignOption Enforce(cert *x509.Certificate) error } -// profileWithOption is a wrapper against x509util.WithOption to conform the -// interface. -type profileWithOption x509util.WithOption +// CertificateModifierFunc allows to create simple certificate modifiers just +// with a function. +type CertificateModifierFunc func(cert *x509.Certificate, opts SignOptions) error -func (v profileWithOption) Option(Options) x509util.WithOption { - return x509util.WithOption(v) +// Modify implements CertificateModifier and just calls the defined function. +func (fn CertificateModifierFunc) Modify(cert *x509.Certificate, opts SignOptions) error { + return fn(cert, opts) +} + +// CertificateEnforcerFunc allows to create simple certificate enforcer just +// with a function. +type CertificateEnforcerFunc func(cert *x509.Certificate) error + +// Modify implements CertificateEnforcer and just calls the defined function. +func (fn CertificateEnforcerFunc) Enforce(cert *x509.Certificate) error { + return fn(cert) } // emailOnlyIdentity is a CertificateRequestValidator that checks that the only @@ -254,27 +266,30 @@ func (eee ExtraExtsEnforcer) Enforce(cert *x509.Certificate) error { return nil } -// profileDefaultDuration is a wrapper against x509util.WithOption to conform -// the SignOption interface. +// profileDefaultDuration is a modifier that sets the certificate +// duration. type profileDefaultDuration time.Duration -func (v profileDefaultDuration) Option(so Options) x509util.WithOption { +func (v profileDefaultDuration) Modify(cert *x509.Certificate, so SignOptions) error { var backdate time.Duration notBefore := so.NotBefore.Time() if notBefore.IsZero() { notBefore = now() backdate = -1 * so.Backdate + } notAfter := so.NotAfter.RelativeTime(notBefore) - return func(p x509util.Profile) error { - fn := x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(v)) - if err := fn(p); err != nil { - return err + if notAfter.IsZero() { + if v != 0 { + notAfter = notBefore.Add(time.Duration(v)) + } else { + notAfter = notBefore.Add(DefaultCertValidity) } - crt := p.Subject() - crt.NotBefore = crt.NotBefore.Add(backdate) - return nil } + + cert.NotBefore = notBefore.Add(backdate) + cert.NotAfter = notAfter + return nil } // profileLimitDuration is an x509 profile option that modifies an x509 validity @@ -286,40 +301,37 @@ type profileLimitDuration struct { // Option returns an x509util option that limits the validity period of a // certificate to one that is superficially imposed. -func (v profileLimitDuration) Option(so Options) x509util.WithOption { - return func(p x509util.Profile) error { - var backdate time.Duration - n := now() - notBefore := so.NotBefore.Time() - if notBefore.IsZero() { - notBefore = n - backdate = -1 * so.Backdate - } - if notBefore.Before(v.notBefore) { - return errors.Errorf("requested certificate notBefore (%s) is before "+ - "the active validity window of the provisioning credential (%s)", - notBefore, v.notBefore) - } - - notAfter := so.NotAfter.RelativeTime(notBefore) - if notAfter.After(v.notAfter) { - return errors.Errorf("requested certificate notAfter (%s) is after "+ - "the expiration of the provisioning credential (%s)", - notAfter, v.notAfter) - } - if notAfter.IsZero() { - t := notBefore.Add(v.def) - if t.After(v.notAfter) { - notAfter = v.notAfter - } else { - notAfter = t - } - } - crt := p.Subject() - crt.NotBefore = notBefore.Add(backdate) - crt.NotAfter = notAfter - return nil +func (v profileLimitDuration) Modify(cert *x509.Certificate, so SignOptions) error { + var backdate time.Duration + notBefore := so.NotBefore.Time() + if notBefore.IsZero() { + notBefore = now() + backdate = -1 * so.Backdate } + if notBefore.Before(v.notBefore) { + return errors.Errorf("requested certificate notBefore (%s) is before "+ + "the active validity window of the provisioning credential (%s)", + notBefore, v.notBefore) + } + + notAfter := so.NotAfter.RelativeTime(notBefore) + if notAfter.After(v.notAfter) { + return errors.Errorf("requested certificate notAfter (%s) is after "+ + "the expiration of the provisioning credential (%s)", + notAfter, v.notAfter) + } + if notAfter.IsZero() { + t := notBefore.Add(v.def) + if t.After(v.notAfter) { + notAfter = v.notAfter + } else { + notAfter = t + } + } + + cert.NotBefore = notBefore.Add(backdate) + cert.NotAfter = notAfter + return nil } // validityValidator validates the certificate validity settings. @@ -335,7 +347,7 @@ func newValidityValidator(min, max time.Duration) *validityValidator { // Valid validates the certificate validity settings (notBefore/notAfter) and // and total duration. -func (v *validityValidator) Valid(cert *x509.Certificate, o Options) error { +func (v *validityValidator) Valid(cert *x509.Certificate, o SignOptions) error { var ( na = cert.NotAfter.Truncate(time.Second) nb = cert.NotBefore.Truncate(time.Second) @@ -385,22 +397,21 @@ func newForceCNOption(forceCN bool) *forceCNOption { return &forceCNOption{forceCN} } -func (o *forceCNOption) Option(Options) x509util.WithOption { - return func(p x509util.Profile) error { - if !o.ForceCN { - // Forcing CN is disabled, do nothing to certificate - return nil - } - crt := p.Subject() - if crt.Subject.CommonName == "" { - if len(crt.DNSNames) > 0 { - crt.Subject.CommonName = crt.DNSNames[0] - } else { - return errors.New("Cannot force CN, DNSNames is empty") - } - } +func (o *forceCNOption) Modify(cert *x509.Certificate, _ SignOptions) error { + if !o.ForceCN { + // Forcing CN is disabled, do nothing to certificate return nil } + + if cert.Subject.CommonName == "" { + if len(cert.DNSNames) > 0 { + cert.Subject.CommonName = cert.DNSNames[0] + } else { + return errors.New("Cannot force CN, DNSNames is empty") + } + } + + return nil } type provisionerExtensionOption struct { @@ -419,23 +430,20 @@ func newProvisionerExtensionOption(typ Type, name, credentialID string, keyValue } } -func (o *provisionerExtensionOption) Option(Options) x509util.WithOption { - return func(p x509util.Profile) error { - crt := p.Subject() - ext, err := createProvisionerExtension(o.Type, o.Name, o.CredentialID, o.KeyValuePairs...) - if err != nil { - return err - } - // Prepend the provisioner extension. In the auth.Sign code we will - // force the resulting certificate to only have one extension, the - // first stepOIDProvisioner that is found in the ExtraExtensions. - // A client could pass a csr containing a malicious stepOIDProvisioner - // ExtraExtension. If we were to append (rather than prepend) the correct - // stepOIDProvisioner extension, then the resulting certificate would - // contain the malicious extension, rather than the one applied by step-ca. - crt.ExtraExtensions = append([]pkix.Extension{ext}, crt.ExtraExtensions...) - return nil +func (o *provisionerExtensionOption) Modify(cert *x509.Certificate, _ SignOptions) error { + ext, err := createProvisionerExtension(o.Type, o.Name, o.CredentialID, o.KeyValuePairs...) + if err != nil { + return err } + // Prepend the provisioner extension. In the auth.Sign code we will + // force the resulting certificate to only have one extension, the + // first stepOIDProvisioner that is found in the ExtraExtensions. + // A client could pass a csr containing a malicious stepOIDProvisioner + // ExtraExtension. If we were to append (rather than prepend) the correct + // stepOIDProvisioner extension, then the resulting certificate would + // contain the malicious extension, rather than the one applied by step-ca. + cert.ExtraExtensions = append([]pkix.Extension{ext}, cert.ExtraExtensions...) + return nil } func createProvisionerExtension(typ int, name, credentialID string, keyValuePairs ...string) (pkix.Extension, error) { @@ -454,8 +462,3 @@ func createProvisionerExtension(typ int, name, credentialID string, keyValuePair Value: b, }, nil } - -func init() { - // Avoid dead-code warning in profileWithOption - _ = profileWithOption(nil) -} diff --git a/authority/provisioner/sign_options_test.go b/authority/provisioner/sign_options_test.go index 3e02853a..459455bc 100644 --- a/authority/provisioner/sign_options_test.go +++ b/authority/provisioner/sign_options_test.go @@ -13,7 +13,6 @@ import ( "github.com/pkg/errors" "github.com/smallstep/assert" "github.com/smallstep/cli/crypto/pemutil" - "github.com/smallstep/cli/crypto/x509util" ) func Test_emailOnlyIdentity_Valid(t *testing.T) { @@ -400,7 +399,7 @@ func Test_ExtraExtsEnforcer_Enforce(t *testing.T) { func Test_validityValidator_Valid(t *testing.T) { type test struct { cert *x509.Certificate - opts Options + opts SignOptions vv *validityValidator err error } @@ -409,7 +408,7 @@ func Test_validityValidator_Valid(t *testing.T) { return test{ vv: &validityValidator{5 * time.Minute, 24 * time.Hour}, cert: &x509.Certificate{NotAfter: time.Now().Add(-5 * time.Minute)}, - opts: Options{}, + opts: SignOptions{}, err: errors.New("notAfter cannot be in the past"), } }, @@ -418,7 +417,7 @@ func Test_validityValidator_Valid(t *testing.T) { vv: &validityValidator{5 * time.Minute, 24 * time.Hour}, cert: &x509.Certificate{NotBefore: time.Now().Add(10 * time.Minute), NotAfter: time.Now().Add(5 * time.Minute)}, - opts: Options{}, + opts: SignOptions{}, err: errors.New("notAfter cannot be before notBefore"), } }, @@ -428,7 +427,7 @@ func Test_validityValidator_Valid(t *testing.T) { vv: &validityValidator{5 * time.Minute, 24 * time.Hour}, cert: &x509.Certificate{NotBefore: n, NotAfter: n.Add(3 * time.Minute)}, - opts: Options{}, + opts: SignOptions{}, err: errors.New("is less than the authorized minimum certificate duration of "), } }, @@ -438,7 +437,7 @@ func Test_validityValidator_Valid(t *testing.T) { vv: &validityValidator{5 * time.Minute, 24 * time.Hour}, cert: &x509.Certificate{NotBefore: n, NotAfter: n.Add(5 * time.Minute)}, - opts: Options{}, + opts: SignOptions{}, } }, "fail/duration-too-great": func() test { @@ -465,7 +464,7 @@ func Test_validityValidator_Valid(t *testing.T) { return test{ vv: &validityValidator{5 * time.Minute, 24 * time.Hour}, cert: cert, - opts: Options{Backdate: time.Second}, + opts: SignOptions{Backdate: time.Second}, } }, "ok/duration-exact-max-with-backdate": func() test { @@ -476,7 +475,7 @@ func Test_validityValidator_Valid(t *testing.T) { return test{ vv: &validityValidator{5 * time.Minute, 24 * time.Hour}, cert: cert, - opts: Options{Backdate: backdate}, + opts: SignOptions{Backdate: backdate}, } }, } @@ -497,7 +496,7 @@ func Test_validityValidator_Valid(t *testing.T) { func Test_forceCN_Option(t *testing.T) { type test struct { - so Options + so SignOptions fcn forceCNOption cert *x509.Certificate valid func(*x509.Certificate) @@ -508,7 +507,7 @@ func Test_forceCN_Option(t *testing.T) { "ok/CN-not-forced": func() test { return test{ fcn: forceCNOption{false}, - so: Options{}, + so: SignOptions{}, cert: &x509.Certificate{ Subject: pkix.Name{}, DNSNames: []string{"acme.example.com", "step.example.com"}, @@ -521,7 +520,7 @@ func Test_forceCN_Option(t *testing.T) { "ok/CN-forced-and-set": func() test { return test{ fcn: forceCNOption{true}, - so: Options{}, + so: SignOptions{}, cert: &x509.Certificate{ Subject: pkix.Name{ CommonName: "Some Common Name", @@ -536,7 +535,7 @@ func Test_forceCN_Option(t *testing.T) { "ok/CN-forced-and-not-set": func() test { return test{ fcn: forceCNOption{true}, - so: Options{}, + so: SignOptions{}, cert: &x509.Certificate{ Subject: pkix.Name{}, DNSNames: []string{"acme.example.com", "step.example.com"}, @@ -549,7 +548,7 @@ func Test_forceCN_Option(t *testing.T) { "fail/CN-forced-and-empty-DNSNames": func() test { return test{ fcn: forceCNOption{true}, - so: Options{}, + so: SignOptions{}, cert: &x509.Certificate{ Subject: pkix.Name{}, DNSNames: []string{}, @@ -562,15 +561,13 @@ func Test_forceCN_Option(t *testing.T) { for name, run := range tests { t.Run(name, func(t *testing.T) { tt := run() - prof := &x509util.Leaf{} - prof.SetSubject(tt.cert) - if err := tt.fcn.Option(tt.so)(prof); err != nil { + if err := tt.fcn.Modify(tt.cert, tt.so); err != nil { if assert.NotNil(t, tt.err) { assert.HasPrefix(t, err.Error(), tt.err.Error()) } } else { if assert.Nil(t, tt.err) { - tt.valid(prof.Subject()) + tt.valid(tt.cert) } } }) @@ -579,7 +576,7 @@ func Test_forceCN_Option(t *testing.T) { func Test_profileDefaultDuration_Option(t *testing.T) { type test struct { - so Options + so SignOptions pdd profileDefaultDuration cert *x509.Certificate valid func(*x509.Certificate) @@ -588,7 +585,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) { "ok/notBefore-notAfter-duration-empty": func() test { return test{ pdd: profileDefaultDuration(0), - so: Options{}, + so: SignOptions{}, cert: new(x509.Certificate), valid: func(cert *x509.Certificate) { n := now() @@ -604,7 +601,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) { nb := time.Now().Add(5 * time.Minute).UTC() return test{ pdd: profileDefaultDuration(0), - so: Options{NotBefore: NewTimeDuration(nb)}, + so: SignOptions{NotBefore: NewTimeDuration(nb)}, cert: new(x509.Certificate), valid: func(cert *x509.Certificate) { assert.Equals(t, cert.NotBefore, nb) @@ -616,7 +613,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) { d := 4 * time.Hour return test{ pdd: profileDefaultDuration(d), - so: Options{Backdate: time.Second}, + so: SignOptions{Backdate: time.Second}, cert: new(x509.Certificate), valid: func(cert *x509.Certificate) { n := now() @@ -632,7 +629,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) { na := now().Add(10 * time.Minute).UTC() return test{ pdd: profileDefaultDuration(0), - so: Options{NotAfter: NewTimeDuration(na)}, + so: SignOptions{NotAfter: NewTimeDuration(na)}, cert: new(x509.Certificate), valid: func(cert *x509.Certificate) { n := now() @@ -649,7 +646,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) { d := 4 * time.Hour return test{ pdd: profileDefaultDuration(d), - so: Options{NotBefore: NewTimeDuration(nb), NotAfter: NewTimeDuration(na)}, + so: SignOptions{NotBefore: NewTimeDuration(nb), NotAfter: NewTimeDuration(na)}, cert: new(x509.Certificate), valid: func(cert *x509.Certificate) { assert.Equals(t, cert.NotBefore, nb) @@ -661,10 +658,9 @@ func Test_profileDefaultDuration_Option(t *testing.T) { for name, run := range tests { t.Run(name, func(t *testing.T) { tt := run() - prof := &x509util.Leaf{} - prof.SetSubject(tt.cert) - assert.FatalError(t, tt.pdd.Option(tt.so)(prof), "unexpected error") - tt.valid(prof.Subject()) + assert.FatalError(t, tt.pdd.Modify(tt.cert, tt.so), "unexpected error") + time.Sleep(1 * time.Nanosecond) + tt.valid(tt.cert) }) } } @@ -702,10 +698,8 @@ func Test_newProvisionerExtension_Option(t *testing.T) { for name, run := range tests { t.Run(name, func(t *testing.T) { tt := run() - prof := &x509util.Leaf{} - prof.SetSubject(tt.cert) - assert.FatalError(t, newProvisionerExtensionOption(TypeJWK, "foo", "bar", "baz", "zap").Option(Options{})(prof)) - tt.valid(prof.Subject()) + assert.FatalError(t, newProvisionerExtensionOption(TypeJWK, "foo", "bar", "baz", "zap").Modify(tt.cert, SignOptions{})) + tt.valid(tt.cert) }) } } @@ -716,7 +710,7 @@ func Test_profileLimitDuration_Option(t *testing.T) { type test struct { pld profileLimitDuration - so Options + so SignOptions cert *x509.Certificate valid func(*x509.Certificate) err error @@ -727,7 +721,7 @@ func Test_profileLimitDuration_Option(t *testing.T) { assert.FatalError(t, err) return test{ pld: profileLimitDuration{def: 4 * time.Hour, notBefore: n.Add(8 * time.Hour)}, - so: Options{NotBefore: d}, + so: SignOptions{NotBefore: d}, cert: new(x509.Certificate), err: errors.New("requested certificate notBefore ("), } @@ -737,7 +731,7 @@ func Test_profileLimitDuration_Option(t *testing.T) { assert.FatalError(t, err) return test{ pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)}, - so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), NotAfter: d}, + so: SignOptions{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), NotAfter: d}, cert: new(x509.Certificate), err: errors.New("requested certificate notAfter ("), } @@ -747,7 +741,7 @@ func Test_profileLimitDuration_Option(t *testing.T) { assert.FatalError(t, err) return test{ pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)}, - so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), NotAfter: d, Backdate: 1 * time.Minute}, + so: SignOptions{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), NotAfter: d, Backdate: 1 * time.Minute}, cert: new(x509.Certificate), valid: func(cert *x509.Certificate) { assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour)) @@ -758,7 +752,7 @@ func Test_profileLimitDuration_Option(t *testing.T) { "ok/valid-notAfter-nil-limit-over-default": func() test { return test{ pld: profileLimitDuration{def: 1 * time.Hour, notAfter: n.Add(6 * time.Hour)}, - so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), Backdate: 1 * time.Minute}, + so: SignOptions{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), Backdate: 1 * time.Minute}, cert: new(x509.Certificate), valid: func(cert *x509.Certificate) { assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour)) @@ -769,7 +763,7 @@ func Test_profileLimitDuration_Option(t *testing.T) { "ok/valid-notAfter-nil-limit-under-default": func() test { return test{ pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)}, - so: Options{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), Backdate: 1 * time.Minute}, + so: SignOptions{NotBefore: NewTimeDuration(n.Add(3 * time.Hour)), Backdate: 1 * time.Minute}, cert: new(x509.Certificate), valid: func(cert *x509.Certificate) { assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour)) @@ -780,7 +774,7 @@ func Test_profileLimitDuration_Option(t *testing.T) { "ok/over-limit-with-backdate": func() test { return test{ pld: profileLimitDuration{def: 24 * time.Hour, notAfter: n.Add(6 * time.Hour)}, - so: Options{Backdate: 1 * time.Minute}, + so: SignOptions{Backdate: 1 * time.Minute}, cert: new(x509.Certificate), valid: func(cert *x509.Certificate) { assert.Equals(t, cert.NotBefore, n.Add(-time.Minute)) @@ -791,7 +785,7 @@ func Test_profileLimitDuration_Option(t *testing.T) { "ok/under-limit-with-backdate": func() test { return test{ pld: profileLimitDuration{def: 24 * time.Hour, notAfter: n.Add(30 * time.Hour)}, - so: Options{Backdate: 1 * time.Minute}, + so: SignOptions{Backdate: 1 * time.Minute}, cert: new(x509.Certificate), valid: func(cert *x509.Certificate) { assert.Equals(t, cert.NotBefore, n.Add(-time.Minute)) @@ -803,15 +797,13 @@ func Test_profileLimitDuration_Option(t *testing.T) { for name, run := range tests { t.Run(name, func(t *testing.T) { tt := run() - prof := &x509util.Leaf{} - prof.SetSubject(tt.cert) - if err := tt.pld.Option(tt.so)(prof); err != nil { + if err := tt.pld.Modify(tt.cert, tt.so); err != nil { if assert.NotNil(t, tt.err) { assert.HasPrefix(t, err.Error(), tt.err.Error()) } } else { if assert.Nil(t, tt.err) { - tt.valid(prof.Subject()) + tt.valid(tt.cert) } } }) diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index 34bc069f..e823a7d8 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -30,20 +30,20 @@ type SSHCertModifier interface { // to modify the SSH certificate. type SSHCertOptionModifier interface { SignOption - Option(o SSHOptions) SSHCertModifier + Option(o SignSSHOptions) SSHCertModifier } // SSHCertValidator is the interface used to validate an SSH certificate. type SSHCertValidator interface { SignOption - Valid(cert *ssh.Certificate, opts SSHOptions) error + Valid(cert *ssh.Certificate, opts SignSSHOptions) error } // SSHCertOptionsValidator is the interface used to validate the custom // options used to modify the SSH certificate. type SSHCertOptionsValidator interface { SignOption - Valid(got SSHOptions) error + Valid(got SignSSHOptions) error } // sshModifierFunc is an adapter to allow the use of ordinary functions as SSH @@ -54,8 +54,8 @@ func (f sshModifierFunc) Modify(cert *ssh.Certificate) error { return f(cert) } -// SSHOptions contains the options that can be passed to the SignSSH method. -type SSHOptions struct { +// SignSSHOptions contains the options that can be passed to the SignSSH method. +type SignSSHOptions struct { CertType string `json:"certType"` KeyID string `json:"keyID"` Principals []string `json:"principals"` @@ -65,12 +65,12 @@ type SSHOptions struct { } // Type returns the uint32 representation of the CertType. -func (o SSHOptions) Type() uint32 { +func (o SignSSHOptions) Type() uint32 { return sshCertTypeUInt32(o.CertType) } // Modify implements SSHCertModifier and sets the SSHOption in the ssh.Certificate. -func (o SSHOptions) Modify(cert *ssh.Certificate) error { +func (o SignSSHOptions) Modify(cert *ssh.Certificate) error { switch o.CertType { case "": // ignore case SSHUserCert: @@ -100,7 +100,7 @@ func (o SSHOptions) Modify(cert *ssh.Certificate) error { // match compares two SSHOptions and return an error if they don't match. It // ignores zero values. -func (o SSHOptions) match(got SSHOptions) error { +func (o SignSSHOptions) match(got SignSSHOptions) error { if o.CertType != "" && got.CertType != "" && o.CertType != got.CertType { return errors.Errorf("ssh certificate type does not match - got %v, want %v", got.CertType, o.CertType) } @@ -165,7 +165,7 @@ func (m sshCertValidBeforeModifier) Modify(cert *ssh.Certificate) error { // sshCertDefaultsModifier implements a SSHCertModifier that // modifies the certificate with the given options if they are not set. -type sshCertDefaultsModifier SSHOptions +type sshCertDefaultsModifier SignSSHOptions // Modify implements the SSHCertModifier interface. func (m sshCertDefaultsModifier) Modify(cert *ssh.Certificate) error { @@ -215,7 +215,7 @@ type sshDefaultDuration struct { *Claimer } -func (m *sshDefaultDuration) Option(o SSHOptions) SSHCertModifier { +func (m *sshDefaultDuration) Option(o SignSSHOptions) SSHCertModifier { return sshModifierFunc(func(cert *ssh.Certificate) error { d, err := m.DefaultSSHCertDuration(cert.CertType) if err != nil { @@ -248,7 +248,7 @@ type sshLimitDuration struct { NotAfter time.Time } -func (m *sshLimitDuration) Option(o SSHOptions) SSHCertModifier { +func (m *sshLimitDuration) Option(o SignSSHOptions) SSHCertModifier { if m.NotAfter.IsZero() { defaultDuration := &sshDefaultDuration{m.Claimer} return defaultDuration.Option(o) @@ -297,12 +297,12 @@ func (m *sshLimitDuration) Option(o SSHOptions) SSHCertModifier { // sshCertOptionsValidator validates the user SSHOptions with the ones // usually present in the token. -type sshCertOptionsValidator SSHOptions +type sshCertOptionsValidator SignSSHOptions // Valid implements SSHCertOptionsValidator and returns nil if both // SSHOptions match. -func (v sshCertOptionsValidator) Valid(got SSHOptions) error { - want := SSHOptions(v) +func (v sshCertOptionsValidator) Valid(got SignSSHOptions) error { + want := SignSSHOptions(v) return want.match(got) } @@ -310,7 +310,7 @@ type sshCertValidityValidator struct { *Claimer } -func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SSHOptions) error { +func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOptions) error { switch { case cert.ValidAfter == 0: return errors.New("ssh certificate validAfter cannot be 0") @@ -355,7 +355,7 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SSHOptions) type sshCertDefaultValidator struct{} // Valid returns an error if the given certificate does not contain the necessary fields. -func (v *sshCertDefaultValidator) Valid(cert *ssh.Certificate, o SSHOptions) error { +func (v *sshCertDefaultValidator) Valid(cert *ssh.Certificate, o SignSSHOptions) error { switch { case len(cert.Nonce) == 0: return errors.New("ssh certificate nonce cannot be empty") @@ -390,7 +390,7 @@ func (v *sshCertDefaultValidator) Valid(cert *ssh.Certificate, o SSHOptions) err type sshDefaultPublicKeyValidator struct{} // Valid checks that certificate request common name matches the one configured. -func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate, o SSHOptions) error { +func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate, o SignSSHOptions) error { if cert.Key == nil { return errors.New("ssh certificate key cannot be nil") } @@ -420,7 +420,7 @@ func (v sshDefaultPublicKeyValidator) Valid(cert *ssh.Certificate, o SSHOptions) type sshCertKeyIDValidator string // Valid returns an error if the given certificate does not contain the necessary fields. -func (v sshCertKeyIDValidator) Valid(cert *ssh.Certificate, o SSHOptions) error { +func (v sshCertKeyIDValidator) Valid(cert *ssh.Certificate, o SignSSHOptions) error { if string(v) != cert.KeyId { return errors.Errorf("invalid ssh certificate KeyId; want %s, but got %s", string(v), cert.KeyId) } diff --git a/authority/provisioner/sign_ssh_options_test.go b/authority/provisioner/sign_ssh_options_test.go index 940fc0e2..600fd975 100644 --- a/authority/provisioner/sign_ssh_options_test.go +++ b/authority/provisioner/sign_ssh_options_test.go @@ -28,7 +28,7 @@ func TestSSHOptions_Type(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - o := SSHOptions{ + o := SignSSHOptions{ CertType: tt.fields.CertType, } if got := o.Type(); got != tt.want { @@ -40,7 +40,7 @@ func TestSSHOptions_Type(t *testing.T) { func TestSSHOptions_Modify(t *testing.T) { type test struct { - so *SSHOptions + so *SignSSHOptions cert *ssh.Certificate valid func(*ssh.Certificate) err error @@ -48,21 +48,21 @@ func TestSSHOptions_Modify(t *testing.T) { tests := map[string](func() test){ "fail/unexpected-cert-type": func() test { return test{ - so: &SSHOptions{CertType: "foo"}, + so: &SignSSHOptions{CertType: "foo"}, cert: new(ssh.Certificate), err: errors.Errorf("ssh certificate has an unknown type - foo"), } }, "fail/validAfter-greater-validBefore": func() test { return test{ - so: &SSHOptions{CertType: "user"}, + so: &SignSSHOptions{CertType: "user"}, cert: &ssh.Certificate{ValidAfter: uint64(15), ValidBefore: uint64(10)}, err: errors.Errorf("ssh certificate valid after cannot be greater than valid before"), } }, "ok/user-cert": func() test { return test{ - so: &SSHOptions{CertType: "user"}, + so: &SignSSHOptions{CertType: "user"}, cert: new(ssh.Certificate), valid: func(cert *ssh.Certificate) { assert.Equals(t, cert.CertType, uint32(ssh.UserCert)) @@ -71,7 +71,7 @@ func TestSSHOptions_Modify(t *testing.T) { }, "ok/host-cert": func() test { return test{ - so: &SSHOptions{CertType: "host"}, + so: &SignSSHOptions{CertType: "host"}, cert: new(ssh.Certificate), valid: func(cert *ssh.Certificate) { assert.Equals(t, cert.CertType, uint32(ssh.HostCert)) @@ -81,7 +81,7 @@ func TestSSHOptions_Modify(t *testing.T) { "ok": func() test { va := time.Now().Add(5 * time.Minute) vb := time.Now().Add(1 * time.Hour) - so := &SSHOptions{CertType: "host", KeyID: "foo", Principals: []string{"foo", "bar"}, + so := &SignSSHOptions{CertType: "host", KeyID: "foo", Principals: []string{"foo", "bar"}, ValidAfter: NewTimeDuration(va), ValidBefore: NewTimeDuration(vb)} return test{ so: so, @@ -114,43 +114,43 @@ func TestSSHOptions_Modify(t *testing.T) { func TestSSHOptions_Match(t *testing.T) { type test struct { - so SSHOptions - cmp SSHOptions + so SignSSHOptions + cmp SignSSHOptions err error } tests := map[string](func() test){ "fail/cert-type": func() test { return test{ - so: SSHOptions{CertType: "foo"}, - cmp: SSHOptions{CertType: "bar"}, + so: SignSSHOptions{CertType: "foo"}, + cmp: SignSSHOptions{CertType: "bar"}, err: errors.Errorf("ssh certificate type does not match - got bar, want foo"), } }, "fail/pricipals": func() test { return test{ - so: SSHOptions{Principals: []string{"foo"}}, - cmp: SSHOptions{Principals: []string{"bar"}}, + so: SignSSHOptions{Principals: []string{"foo"}}, + cmp: SignSSHOptions{Principals: []string{"bar"}}, err: errors.Errorf("ssh certificate principals does not match - got [bar], want [foo]"), } }, "fail/validAfter": func() test { return test{ - so: SSHOptions{ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute))}, - cmp: SSHOptions{ValidAfter: NewTimeDuration(time.Now().Add(5 * time.Minute))}, + so: SignSSHOptions{ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute))}, + cmp: SignSSHOptions{ValidAfter: NewTimeDuration(time.Now().Add(5 * time.Minute))}, err: errors.Errorf("ssh certificate valid after does not match"), } }, "fail/validBefore": func() test { return test{ - so: SSHOptions{ValidBefore: NewTimeDuration(time.Now().Add(1 * time.Minute))}, - cmp: SSHOptions{ValidBefore: NewTimeDuration(time.Now().Add(5 * time.Minute))}, + so: SignSSHOptions{ValidBefore: NewTimeDuration(time.Now().Add(1 * time.Minute))}, + cmp: SignSSHOptions{ValidBefore: NewTimeDuration(time.Now().Add(5 * time.Minute))}, err: errors.Errorf("ssh certificate valid before does not match"), } }, "ok/original-empty": func() test { return test{ - so: SSHOptions{}, - cmp: SSHOptions{ + so: SignSSHOptions{}, + cmp: SignSSHOptions{ CertType: "foo", Principals: []string{"foo"}, ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute)), @@ -160,8 +160,8 @@ func TestSSHOptions_Match(t *testing.T) { }, "ok/cmp-empty": func() test { return test{ - cmp: SSHOptions{}, - so: SSHOptions{ + cmp: SignSSHOptions{}, + so: SignSSHOptions{ CertType: "foo", Principals: []string{"foo"}, ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute)), @@ -174,13 +174,13 @@ func TestSSHOptions_Match(t *testing.T) { va := NewTimeDuration(n.Add(1 * time.Minute)) vb := NewTimeDuration(n.Add(5 * time.Minute)) return test{ - cmp: SSHOptions{ + cmp: SignSSHOptions{ CertType: "foo", Principals: []string{"foo"}, ValidAfter: va, ValidBefore: vb, }, - so: SSHOptions{ + so: SignSSHOptions{ CertType: "foo", Principals: []string{"foo"}, ValidAfter: va, @@ -330,7 +330,7 @@ func Test_sshCertDefaultsModifier_Modify(t *testing.T) { n := time.Now() va := NewTimeDuration(n.Add(1 * time.Minute)) vb := NewTimeDuration(n.Add(5 * time.Minute)) - so := SSHOptions{ + so := SignSSHOptions{ Principals: []string{"foo", "bar"}, CertType: "host", ValidAfter: va, @@ -349,7 +349,7 @@ func Test_sshCertDefaultsModifier_Modify(t *testing.T) { }, "ok/no-changes": func() test { n := time.Now() - so := SSHOptions{ + so := SignSSHOptions{ Principals: []string{"foo", "bar"}, CertType: "host", ValidAfter: NewTimeDuration(n.Add(15 * time.Minute)), @@ -659,7 +659,7 @@ func Test_sshCertDefaultValidator_Valid(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := v.Valid(tt.cert, SSHOptions{}); err != nil { + if err := v.Valid(tt.cert, SignSSHOptions{}); err != nil { if assert.NotNil(t, tt.err) { assert.HasPrefix(t, err.Error(), tt.err.Error()) } @@ -678,31 +678,31 @@ func Test_sshCertValidityValidator(t *testing.T) { tests := []struct { name string cert *ssh.Certificate - opts SSHOptions + opts SignSSHOptions err error }{ { "fail/validAfter-0", &ssh.Certificate{CertType: ssh.UserCert}, - SSHOptions{}, + SignSSHOptions{}, errors.New("ssh certificate validAfter cannot be 0"), }, { "fail/validBefore-in-past", &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(-time.Minute).Unix())}, - SSHOptions{}, + SignSSHOptions{}, errors.New("ssh certificate validBefore cannot be in the past"), }, { "fail/validBefore-before-validAfter", &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: uint64(now().Add(5 * time.Minute).Unix()), ValidBefore: uint64(now().Add(3 * time.Minute).Unix())}, - SSHOptions{}, + SignSSHOptions{}, errors.New("ssh certificate validBefore cannot be before validAfter"), }, { "fail/cert-type-not-set", &ssh.Certificate{ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(10 * time.Minute).Unix())}, - SSHOptions{}, + SignSSHOptions{}, errors.New("ssh certificate type has not been set"), }, { @@ -712,7 +712,7 @@ func Test_sshCertValidityValidator(t *testing.T) { ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(10 * time.Minute).Unix()), }, - SSHOptions{}, + SignSSHOptions{}, errors.New("unknown ssh certificate type 3"), }, { @@ -722,7 +722,7 @@ func Test_sshCertValidityValidator(t *testing.T) { ValidAfter: uint64(n.Unix()), ValidBefore: uint64(n.Add(4 * time.Minute).Unix()), }, - SSHOptions{Backdate: time.Second}, + SignSSHOptions{Backdate: time.Second}, errors.New("requested duration of 4m0s is less than minimum accepted duration for selected provisioner of 5m0s"), }, { @@ -732,7 +732,7 @@ func Test_sshCertValidityValidator(t *testing.T) { ValidAfter: uint64(n.Unix()), ValidBefore: uint64(n.Add(5 * time.Minute).Unix()), }, - SSHOptions{Backdate: time.Second}, + SignSSHOptions{Backdate: time.Second}, nil, }, { @@ -742,7 +742,7 @@ func Test_sshCertValidityValidator(t *testing.T) { ValidAfter: uint64(n.Unix()), ValidBefore: uint64(n.Add(48 * time.Hour).Unix()), }, - SSHOptions{Backdate: time.Second}, + SignSSHOptions{Backdate: time.Second}, errors.New("requested duration of 48h0m0s is greater than maximum accepted duration for selected provisioner of 24h0m1s"), }, { @@ -752,7 +752,7 @@ func Test_sshCertValidityValidator(t *testing.T) { ValidAfter: uint64(n.Unix()), ValidBefore: uint64(n.Add(24*time.Hour + time.Second).Unix()), }, - SSHOptions{Backdate: time.Second}, + SignSSHOptions{Backdate: time.Second}, nil, }, { @@ -762,7 +762,7 @@ func Test_sshCertValidityValidator(t *testing.T) { ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(8 * time.Hour).Unix()), }, - SSHOptions{Backdate: time.Second}, + SignSSHOptions{Backdate: time.Second}, nil, }, } @@ -908,7 +908,7 @@ func Test_sshValidityModifier(t *testing.T) { for name, run := range tests { t.Run(name, func(t *testing.T) { tt := run() - if err := tt.svm.Option(SSHOptions{}).Modify(tt.cert); err != nil { + if err := tt.svm.Option(SignSSHOptions{}).Modify(tt.cert); err != nil { if assert.NotNil(t, tt.err) { assert.HasPrefix(t, err.Error(), tt.err.Error()) } @@ -962,7 +962,7 @@ func Test_sshDefaultDuration_Option(t *testing.T) { Claimer *Claimer } type args struct { - o SSHOptions + o SignSSHOptions cert *ssh.Certificate } tests := []struct { @@ -972,26 +972,26 @@ func Test_sshDefaultDuration_Option(t *testing.T) { want *ssh.Certificate wantErr bool }{ - {"user", fields{newClaimer(nil)}, args{SSHOptions{}, &ssh.Certificate{CertType: ssh.UserCert}}, + {"user", fields{newClaimer(nil)}, args{SignSSHOptions{}, &ssh.Certificate{CertType: ssh.UserCert}}, &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(0), ValidBefore: unix(16 * time.Hour)}, false}, - {"host", fields{newClaimer(nil)}, args{SSHOptions{}, &ssh.Certificate{CertType: ssh.HostCert}}, + {"host", fields{newClaimer(nil)}, args{SignSSHOptions{}, &ssh.Certificate{CertType: ssh.HostCert}}, &ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(0), ValidBefore: unix(30 * 24 * time.Hour)}, false}, - {"user claim", fields{newClaimer(&Claims{DefaultUserSSHDur: &Duration{1 * time.Hour}})}, args{SSHOptions{}, &ssh.Certificate{CertType: ssh.UserCert}}, + {"user claim", fields{newClaimer(&Claims{DefaultUserSSHDur: &Duration{1 * time.Hour}})}, args{SignSSHOptions{}, &ssh.Certificate{CertType: ssh.UserCert}}, &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(0), ValidBefore: unix(1 * time.Hour)}, false}, - {"host claim", fields{newClaimer(&Claims{DefaultHostSSHDur: &Duration{1 * time.Hour}})}, args{SSHOptions{}, &ssh.Certificate{CertType: ssh.HostCert}}, + {"host claim", fields{newClaimer(&Claims{DefaultHostSSHDur: &Duration{1 * time.Hour}})}, args{SignSSHOptions{}, &ssh.Certificate{CertType: ssh.HostCert}}, &ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(0), ValidBefore: unix(1 * time.Hour)}, false}, - {"user backdate", fields{newClaimer(nil)}, args{SSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.UserCert}}, + {"user backdate", fields{newClaimer(nil)}, args{SignSSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.UserCert}}, &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(-1 * time.Minute), ValidBefore: unix(16 * time.Hour)}, false}, - {"host backdate", fields{newClaimer(nil)}, args{SSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.HostCert}}, + {"host backdate", fields{newClaimer(nil)}, args{SignSSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.HostCert}}, &ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(-1 * time.Minute), ValidBefore: unix(30 * 24 * time.Hour)}, false}, - {"user validAfter", fields{newClaimer(nil)}, args{SSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(1 * time.Hour)}}, + {"user validAfter", fields{newClaimer(nil)}, args{SignSSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(1 * time.Hour)}}, &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(time.Hour), ValidBefore: unix(17 * time.Hour)}, false}, - {"user validBefore", fields{newClaimer(nil)}, args{SSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.UserCert, ValidBefore: unix(1 * time.Hour)}}, + {"user validBefore", fields{newClaimer(nil)}, args{SignSSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.UserCert, ValidBefore: unix(1 * time.Hour)}}, &ssh.Certificate{CertType: ssh.UserCert, ValidAfter: unix(-1 * time.Minute), ValidBefore: unix(time.Hour)}, false}, - {"host validAfter validBefore", fields{newClaimer(nil)}, args{SSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(1 * time.Minute), ValidBefore: unix(2 * time.Minute)}}, + {"host validAfter validBefore", fields{newClaimer(nil)}, args{SignSSHOptions{Backdate: 1 * time.Minute}, &ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(1 * time.Minute), ValidBefore: unix(2 * time.Minute)}}, &ssh.Certificate{CertType: ssh.HostCert, ValidAfter: unix(1 * time.Minute), ValidBefore: unix(2 * time.Minute)}, false}, - {"fail zero", fields{newClaimer(nil)}, args{SSHOptions{}, &ssh.Certificate{}}, &ssh.Certificate{}, true}, - {"fail type", fields{newClaimer(nil)}, args{SSHOptions{}, &ssh.Certificate{CertType: 3}}, &ssh.Certificate{CertType: 3}, true}, + {"fail zero", fields{newClaimer(nil)}, args{SignSSHOptions{}, &ssh.Certificate{}}, &ssh.Certificate{}, true}, + {"fail type", fields{newClaimer(nil)}, args{SignSSHOptions{}, &ssh.Certificate{CertType: 3}}, &ssh.Certificate{CertType: 3}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1015,7 +1015,7 @@ func Test_sshLimitDuration_Option(t *testing.T) { NotAfter time.Time } type args struct { - o SSHOptions + o SignSSHOptions } tests := []struct { name string diff --git a/authority/provisioner/ssh_test.go b/authority/provisioner/ssh_test.go index be102a1a..c08ddc3e 100644 --- a/authority/provisioner/ssh_test.go +++ b/authority/provisioner/ssh_test.go @@ -10,7 +10,7 @@ import ( "golang.org/x/crypto/ssh" ) -func validateSSHCertificate(cert *ssh.Certificate, opts *SSHOptions) error { +func validateSSHCertificate(cert *ssh.Certificate, opts *SignSSHOptions) error { switch { case cert == nil: return fmt.Errorf("certificate is nil") @@ -39,7 +39,7 @@ func validateSSHCertificate(cert *ssh.Certificate, opts *SSHOptions) error { } } -func signSSHCertificate(key crypto.PublicKey, opts SSHOptions, signOpts []SignOption, signKey crypto.Signer) (*ssh.Certificate, error) { +func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []SignOption, signKey crypto.Signer) (*ssh.Certificate, error) { pub, err := ssh.NewPublicKey(key) if err != nil { return nil, err diff --git a/authority/provisioner/testdata/templates/cr.tpl b/authority/provisioner/testdata/templates/cr.tpl new file mode 100644 index 00000000..4a14aafe --- /dev/null +++ b/authority/provisioner/testdata/templates/cr.tpl @@ -0,0 +1 @@ +{{ toJson .Insecure.CR }} \ No newline at end of file diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index 52fb470a..a279cf8a 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -825,20 +825,20 @@ func generateK8sSAToken(jwk *jose.JSONWebKey, claims *k8sSAPayload, tokOpts ...t } func generateSimpleSSHUserToken(iss, aud string, jwk *jose.JSONWebKey) (string, error) { - return generateSSHToken("subject@localhost", iss, aud, time.Now(), &SSHOptions{ + return generateSSHToken("subject@localhost", iss, aud, time.Now(), &SignSSHOptions{ CertType: "user", Principals: []string{"name"}, }, jwk) } func generateSimpleSSHHostToken(iss, aud string, jwk *jose.JSONWebKey) (string, error) { - return generateSSHToken("subject@localhost", iss, aud, time.Now(), &SSHOptions{ + return generateSSHToken("subject@localhost", iss, aud, time.Now(), &SignSSHOptions{ CertType: "host", Principals: []string{"smallstep.com"}, }, jwk) } -func generateSSHToken(sub, iss, aud string, iat time.Time, sshOpts *SSHOptions, jwk *jose.JSONWebKey) (string, error) { +func generateSSHToken(sub, iss, aud string, iat time.Time, sshOpts *SignSSHOptions, jwk *jose.JSONWebKey) (string, error) { sig, err := jose.NewSigner( jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID), diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 6f7d0a5f..02abf684 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/x509util" ) // x5cPayload extends jwt.Claims with step attributes. @@ -24,10 +25,11 @@ type x5cPayload struct { // signature requests. type X5C struct { *base - Type string `json:"type"` - Name string `json:"name"` - Roots []byte `json:"roots"` - Claims *Claims `json:"claims,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + Roots []byte `json:"roots"` + Claims *Claims `json:"claims,omitempty"` + Options *Options `json:"options,omitempty"` claimer *Claimer audiences Audiences rootPool *x509.CertPool @@ -193,7 +195,19 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er claims.SANs = []string{claims.Subject} } + // Certificate templates + data := x509util.CreateTemplateData(claims.Subject, claims.SANs) + if v, err := unsafeParseSigned(token); err == nil { + data.SetToken(v) + } + + templateOptions, err := TemplateOptions(p.Options, data) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "jwk.AuthorizeSign") + } + return []SignOption{ + templateOptions, // modifiers / withOptions newProvisionerExtensionOption(TypeX5C, p.Name, ""), profileLimitDuration{p.claimer.DefaultTLSCertDuration(), diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index 24f3e71e..73a3ba9f 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -463,9 +463,10 @@ func TestX5C_AuthorizeSign(t *testing.T) { } else { if assert.Nil(t, tc.err) { if assert.NotNil(t, opts) { - assert.Equals(t, len(opts), 6) + assert.Equals(t, len(opts), 7) for _, o := range opts { switch v := o.(type) { + case certificateOptionsFunc: case *provisionerExtensionOption: assert.Equals(t, v.Type, int(TypeX5C)) assert.Equals(t, v.Name, tc.p.GetName()) @@ -695,7 +696,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)), Audience: []string{testAudiences.SSHSign[0]}, }, - Step: &stepPayload{SSH: &SSHOptions{ + Step: &stepPayload{SSH: &SignSSHOptions{ CertType: SSHHostCert, Principals: []string{"max", "mariano", "alan"}, ValidAfter: TimeDuration{d: 5 * time.Minute}, @@ -727,7 +728,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)), Audience: []string{testAudiences.SSHSign[0]}, }, - Step: &stepPayload{SSH: &SSHOptions{}}, + Step: &stepPayload{SSH: &SignSSHOptions{}}, } tok, err := generateX5CSSHToken(x5cJWK, claims, withX5CHdr(x5cCerts)) assert.FatalError(t, err) @@ -758,7 +759,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { case sshCertOptionsValidator: tc.claims.Step.SSH.ValidAfter.t = time.Time{} tc.claims.Step.SSH.ValidBefore.t = time.Time{} - assert.Equals(t, SSHOptions(v), *tc.claims.Step.SSH) + assert.Equals(t, SignSSHOptions(v), *tc.claims.Step.SSH) case sshCertKeyIDModifier: assert.Equals(t, string(v), "foo") case sshCertTypeModifier: @@ -770,7 +771,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { case sshCertValidBeforeModifier: assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix()) case sshCertDefaultsModifier: - assert.Equals(t, SSHOptions(v), SSHOptions{CertType: SSHUserCert}) + assert.Equals(t, SignSSHOptions(v), SignSSHOptions{CertType: SSHUserCert}) case *sshLimitDuration: assert.Equals(t, v.Claimer, tc.p.claimer) assert.Equals(t, v.NotAfter, x5cCerts[0].NotAfter) diff --git a/authority/ssh.go b/authority/ssh.go index 70a15649..05b899e6 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -204,7 +204,7 @@ 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.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { +func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { var mods []provisioner.SSHCertModifier var validators []provisioner.SSHCertValidator @@ -453,7 +453,7 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub // Apply validators from provisioner. for _, v := range validators { - if err := v.Valid(cert, provisioner.SSHOptions{Backdate: backdate}); err != nil { + if err := v.Valid(cert, provisioner.SignSSHOptions{Backdate: backdate}); err != nil { return nil, errs.Wrap(http.StatusForbidden, err, "rekeySSH") } } diff --git a/authority/ssh_test.go b/authority/ssh_test.go index 2756f099..8e64b108 100644 --- a/authority/ssh_test.go +++ b/authority/ssh_test.go @@ -62,7 +62,7 @@ func (m sshTestCertModifier) Modify(cert *ssh.Certificate) error { type sshTestCertValidator string -func (v sshTestCertValidator) Valid(crt *ssh.Certificate, opts provisioner.SSHOptions) error { +func (v sshTestCertValidator) Valid(crt *ssh.Certificate, opts provisioner.SignSSHOptions) error { if v == "" { return nil } @@ -71,7 +71,7 @@ func (v sshTestCertValidator) Valid(crt *ssh.Certificate, opts provisioner.SSHOp type sshTestOptionsValidator string -func (v sshTestOptionsValidator) Valid(opts provisioner.SSHOptions) error { +func (v sshTestOptionsValidator) Valid(opts provisioner.SignSSHOptions) error { if v == "" { return nil } @@ -80,7 +80,7 @@ func (v sshTestOptionsValidator) Valid(opts provisioner.SSHOptions) error { type sshTestOptionsModifier string -func (m sshTestOptionsModifier) Option(opts provisioner.SSHOptions) provisioner.SSHCertModifier { +func (m sshTestOptionsModifier) Option(opts provisioner.SignSSHOptions) provisioner.SSHCertModifier { return sshTestCertModifier(string(m)) } @@ -109,7 +109,7 @@ func TestAuthority_SignSSH(t *testing.T) { } type args struct { key ssh.PublicKey - opts provisioner.SSHOptions + opts provisioner.SignSSHOptions signOpts []provisioner.SignOption } type want struct { @@ -125,27 +125,27 @@ func TestAuthority_SignSSH(t *testing.T) { want want wantErr bool }{ - {"ok-user", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions}}, want{CertType: ssh.UserCert}, false}, - {"ok-host", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{hostOptions}}, want{CertType: ssh.HostCert}, false}, - {"ok-opts-type-user", fields{signer, signer}, args{pub, provisioner.SSHOptions{CertType: "user"}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert}, false}, - {"ok-opts-type-host", fields{signer, signer}, args{pub, provisioner.SSHOptions{CertType: "host"}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert}, false}, - {"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SSHOptions{CertType: "user", Principals: []string{"user"}}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert, Principals: []string{"user"}}, false}, - {"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SSHOptions{CertType: "host", Principals: []string{"foo.test.com", "bar.test.com"}}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert, Principals: []string{"foo.test.com", "bar.test.com"}}, false}, - {"ok-opts-valid-after", fields{signer, signer}, args{pub, provisioner.SSHOptions{CertType: "user", ValidAfter: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert, ValidAfter: uint64(now.Unix())}, false}, - {"ok-opts-valid-before", fields{signer, signer}, args{pub, provisioner.SSHOptions{CertType: "host", ValidBefore: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert, ValidBefore: uint64(now.Unix())}, false}, - {"ok-cert-validator", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("")}}, want{CertType: ssh.UserCert}, false}, - {"ok-cert-modifier", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("")}}, want{CertType: ssh.UserCert}, false}, - {"ok-opts-validator", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("")}}, want{CertType: ssh.UserCert}, false}, - {"ok-opts-modifier", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("")}}, want{CertType: ssh.UserCert}, false}, - {"fail-opts-type", fields{signer, signer}, args{pub, provisioner.SSHOptions{CertType: "foo"}, []provisioner.SignOption{}}, want{}, true}, - {"fail-cert-validator", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("an error")}}, want{}, true}, - {"fail-cert-modifier", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("an error")}}, want{}, true}, - {"fail-opts-validator", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("an error")}}, want{}, true}, - {"fail-opts-modifier", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("an error")}}, want{}, true}, - {"fail-bad-sign-options", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions, "wrong type"}}, want{}, true}, - {"fail-no-user-key", fields{nil, signer}, args{pub, provisioner.SSHOptions{CertType: "user"}, []provisioner.SignOption{}}, want{}, true}, - {"fail-no-host-key", fields{signer, nil}, args{pub, provisioner.SSHOptions{CertType: "host"}, []provisioner.SignOption{}}, want{}, true}, - {"fail-bad-type", fields{signer, nil}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{sshTestModifier{CertType: 0}}}, want{}, true}, + {"ok-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions}}, want{CertType: ssh.UserCert}, false}, + {"ok-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{hostOptions}}, want{CertType: ssh.HostCert}, false}, + {"ok-opts-type-user", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert}, false}, + {"ok-opts-type-host", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert}, false}, + {"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", Principals: []string{"user"}}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert, Principals: []string{"user"}}, false}, + {"ok-opts-principals", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host", Principals: []string{"foo.test.com", "bar.test.com"}}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert, Principals: []string{"foo.test.com", "bar.test.com"}}, false}, + {"ok-opts-valid-after", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user", ValidAfter: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert, ValidAfter: uint64(now.Unix())}, false}, + {"ok-opts-valid-before", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "host", ValidBefore: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert, ValidBefore: uint64(now.Unix())}, false}, + {"ok-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("")}}, want{CertType: ssh.UserCert}, false}, + {"ok-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("")}}, want{CertType: ssh.UserCert}, false}, + {"ok-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("")}}, want{CertType: ssh.UserCert}, false}, + {"ok-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("")}}, want{CertType: ssh.UserCert}, false}, + {"fail-opts-type", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{CertType: "foo"}, []provisioner.SignOption{}}, want{}, true}, + {"fail-cert-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("an error")}}, want{}, true}, + {"fail-cert-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("an error")}}, want{}, true}, + {"fail-opts-validator", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("an error")}}, want{}, true}, + {"fail-opts-modifier", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("an error")}}, want{}, true}, + {"fail-bad-sign-options", fields{signer, signer}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userOptions, "wrong type"}}, want{}, true}, + {"fail-no-user-key", fields{nil, signer}, args{pub, provisioner.SignSSHOptions{CertType: "user"}, []provisioner.SignOption{}}, want{}, true}, + {"fail-no-host-key", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{CertType: "host"}, []provisioner.SignOption{}}, want{}, true}, + {"fail-bad-type", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{sshTestModifier{CertType: 0}}}, want{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authority/tls.go b/authority/tls.go index 7a8d1947..76edf360 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -18,8 +18,9 @@ import ( "github.com/smallstep/certificates/errs" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/tlsutil" - "github.com/smallstep/cli/crypto/x509util" + x509legacy "github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/cli/jose" + "go.step.sm/crypto/x509util" ) // GetTLSOptions returns the tls options configured. @@ -30,12 +31,11 @@ func (a *Authority) GetTLSOptions() *tlsutil.TLSOptions { var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35} var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14} -func withDefaultASN1DN(def *x509util.ASN1DN) x509util.WithOption { - return func(p x509util.Profile) error { +func withDefaultASN1DN(def *x509legacy.ASN1DN) provisioner.CertificateModifierFunc { + return func(crt *x509.Certificate, opts provisioner.SignOptions) error { if def == nil { return errors.New("default ASN1DN template cannot be nil") } - crt := p.Subject() if len(crt.Subject.Country) == 0 && def.Country != "" { crt.Subject.Country = append(crt.Subject.Country, def.Country) @@ -61,67 +61,95 @@ func withDefaultASN1DN(def *x509util.ASN1DN) x509util.WithOption { } // Sign creates a signed certificate from a certificate signing request. -func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { +func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { var ( - opts = []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} - mods = []x509util.WithOption{withDefaultASN1DN(a.config.AuthorityConfig.Template)} - certValidators = []provisioner.CertificateValidator{} - forcedModifiers = []provisioner.CertificateEnforcer{provisioner.ExtraExtsEnforcer{}} + certOptions []x509util.Option + certValidators []provisioner.CertificateValidator + certModifiers []provisioner.CertificateModifier + certEnforcers []provisioner.CertificateEnforcer ) + opts := []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} + if err := csr.CheckSignature(); err != nil { + return nil, errs.Wrap(http.StatusBadRequest, err, "authority.Sign; invalid certificate request", opts...) + } + // Set backdate with the configured value signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration for _, op := range extraOpts { switch k := op.(type) { - case provisioner.CertificateValidator: - certValidators = append(certValidators, k) + // Adds new options to NewCertificate + case provisioner.CertificateOptions: + certOptions = append(certOptions, k.Options(signOpts)...) + + // Validate tie given certificate request. case provisioner.CertificateRequestValidator: if err := k.Valid(csr); err != nil { return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) } - case provisioner.ProfileModifier: - mods = append(mods, k.Option(signOpts)) + + // Validates the unsigned certificate template. + case provisioner.CertificateValidator: + certValidators = append(certValidators, k) + + // Modifies a certificate before validating it. + case provisioner.CertificateModifier: + certModifiers = append(certModifiers, k) + + // Modifies a certificate after validating it. case provisioner.CertificateEnforcer: - forcedModifiers = append(forcedModifiers, k) + certEnforcers = append(certEnforcers, k) + default: return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...) } } - if err := csr.CheckSignature(); err != nil { - return nil, errs.Wrap(http.StatusBadRequest, err, "authority.Sign; invalid certificate request", opts...) - } - - leaf, err := x509util.NewLeafProfileWithCSR(csr, a.x509Issuer, a.x509Signer, mods...) + cert, err := x509util.NewCertificate(csr, certOptions...) if err != nil { + if _, ok := err.(*x509util.TemplateError); ok { + return nil, errs.NewErr(http.StatusBadRequest, err, + errs.WithMessage(err.Error()), + errs.WithKeyVal("csr", csr), + errs.WithKeyVal("signOptions", signOpts), + ) + } return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...) } - // Certificate validation + // Certificate modifiers before validation + leaf := cert.GetCertificate() + + // Set default subject + if err := withDefaultASN1DN(a.config.AuthorityConfig.Template).Modify(leaf, signOpts); err != nil { + return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) + } + + for _, m := range certModifiers { + if err := m.Modify(leaf, signOpts); err != nil { + return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) + } + } + + // Certificate validation. for _, v := range certValidators { - if err := v.Valid(leaf.Subject(), signOpts); err != nil { + if err := v.Valid(leaf, signOpts); err != nil { return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) } } // Certificate modifiers after validation - for _, m := range forcedModifiers { - if err := m.Enforce(leaf.Subject()); err != nil { + for _, m := range certEnforcers { + if err := m.Enforce(leaf); err != nil { return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) } } - crtBytes, err := leaf.CreateCertificate() + serverCert, err := x509util.CreateCertificate(leaf, a.x509Issuer, csr.PublicKey, a.x509Signer) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, - "authority.Sign; error creating new leaf certificate", opts...) - } - - serverCert, err := x509.ParseCertificate(crtBytes) - if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, - "authority.Sign; error parsing new leaf certificate", opts...) + "authority.Sign; error creating certificate", opts...) } if err = a.db.StoreCertificate(serverCert); err != nil { @@ -217,7 +245,7 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5 newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext) } - leaf, err := x509util.NewLeafProfileWithTemplate(newCert, a.x509Issuer, a.x509Signer) + leaf, err := x509legacy.NewLeafProfileWithTemplate(newCert, a.x509Issuer, a.x509Signer) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...) } @@ -343,8 +371,8 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error // GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server. func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { - profile, err := x509util.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer, - x509util.WithHosts(strings.Join(a.config.DNSNames, ","))) + profile, err := x509legacy.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer, + x509legacy.WithHosts(strings.Join(a.config.DNSNames, ","))) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") } diff --git a/authority/tls_test.go b/authority/tls_test.go index 2e44c414..90dc7075 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -95,6 +95,22 @@ func setExtraExtsCSR(exts []pkix.Extension) func(*x509.CertificateRequest) { } } +func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { + b, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return nil, errors.Wrap(err, "error marshaling public key") + } + info := struct { + Algorithm pkix.AlgorithmIdentifier + SubjectPublicKey asn1.BitString + }{} + if _, err = asn1.Unmarshal(b, &info); err != nil { + return nil, errors.Wrap(err, "error unmarshaling public key") + } + hash := sha1.Sum(info.SubjectPublicKey.Bytes) + return hash[:], nil +} + type basicConstraints struct { IsCA bool `asn1:"optional"` MaxPathLen int `asn1:"optional,default:-1"` @@ -116,9 +132,10 @@ func TestAuthority_Sign(t *testing.T) { } nb := time.Now() - signOpts := provisioner.Options{ + signOpts := provisioner.SignOptions{ NotBefore: provisioner.NewTimeDuration(nb), NotAfter: provisioner.NewTimeDuration(nb.Add(time.Minute * 5)), + Backdate: 1 * time.Minute, } // Create a token to get test extra opts. @@ -134,7 +151,7 @@ func TestAuthority_Sign(t *testing.T) { type signTest struct { auth *Authority csr *x509.CertificateRequest - signOpts provisioner.Options + signOpts provisioner.SignOptions extraOpts []provisioner.SignOption notBefore time.Time notAfter time.Time @@ -176,7 +193,7 @@ func TestAuthority_Sign(t *testing.T) { extraOpts: extraOpts, signOpts: signOpts, err: errors.New("authority.Sign: default ASN1DN template cannot be nil"), - code: http.StatusInternalServerError, + code: http.StatusUnauthorized, } }, "fail create cert": func(t *testing.T) *signTest { @@ -188,13 +205,13 @@ func TestAuthority_Sign(t *testing.T) { csr: csr, extraOpts: extraOpts, signOpts: signOpts, - err: errors.New("authority.Sign; error creating new leaf certificate"), + err: errors.New("authority.Sign; error creating certificate"), code: http.StatusInternalServerError, } }, "fail provisioner duration claim": func(t *testing.T) *signTest { csr := getCSR(t, priv) - _signOpts := provisioner.Options{ + _signOpts := provisioner.SignOptions{ NotBefore: provisioner.NewTimeDuration(nb), NotAfter: provisioner.NewTimeDuration(nb.Add(time.Hour * 25)), } @@ -263,6 +280,33 @@ ZYtQ9Ot36qc= code: http.StatusInternalServerError, } }, + "fail custom template": func(t *testing.T) *signTest { + csr := getCSR(t, priv) + testAuthority := testAuthority(t) + p, ok := testAuthority.provisioners.Load("step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + if !ok { + t.Fatal("provisioner not found") + } + p.(*provisioner.JWK).Options = &provisioner.Options{ + X509: &provisioner.X509Options{Template: `{{ fail "fail message" }}`}, + } + testExtraOpts, err := testAuthority.Authorize(ctx, token) + assert.FatalError(t, err) + testAuthority.db = &db.MockAuthDB{ + MStoreCertificate: func(crt *x509.Certificate) error { + assert.Equals(t, crt.Subject.CommonName, "smallstep test") + return nil + }, + } + return &signTest{ + auth: testAuthority, + csr: csr, + extraOpts: testExtraOpts, + signOpts: signOpts, + err: errors.New("fail message"), + code: http.StatusBadRequest, + } + }, "ok": func(t *testing.T) *signTest { csr := getCSR(t, priv) _a := testAuthority(t) @@ -313,6 +357,39 @@ ZYtQ9Ot36qc= notAfter: now.Add(365 * 24 * time.Hour).Truncate(time.Second), } }, + "ok with custom template": func(t *testing.T) *signTest { + csr := getCSR(t, priv) + testAuthority := testAuthority(t) + testAuthority.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template + p, ok := testAuthority.provisioners.Load("step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + if !ok { + t.Fatal("provisioner not found") + } + p.(*provisioner.JWK).Options = &provisioner.Options{ + X509: &provisioner.X509Options{Template: `{ + "subject": {{toJson .Subject}}, + "dnsNames": {{ toJson .Insecure.CR.DNSNames }}, + "keyUsage": ["digitalSignature"], + "extKeyUsage": ["serverAuth","clientAuth"] + }`}, + } + testExtraOpts, err := testAuthority.Authorize(ctx, token) + assert.FatalError(t, err) + testAuthority.db = &db.MockAuthDB{ + MStoreCertificate: func(crt *x509.Certificate) error { + assert.Equals(t, crt.Subject.CommonName, "smallstep test") + return nil + }, + } + return &signTest{ + auth: testAuthority, + csr: csr, + extraOpts: testExtraOpts, + signOpts: signOpts, + notBefore: signOpts.NotBefore.Time().Truncate(time.Second), + notAfter: signOpts.NotAfter.Time().Truncate(time.Second), + } + }, } for name, genTestCase := range tests { @@ -357,9 +434,9 @@ ZYtQ9Ot36qc= []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"}) - kid, err := generateSubjectKeyID(pub) + subjectKeyID, err := generateSubjectKeyID(pub) assert.FatalError(t, err) - assert.Equals(t, leaf.SubjectKeyId, kid) + assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) assert.Equals(t, leaf.AuthorityKeyId, a.x509Issuer.SubjectKeyId) @@ -396,31 +473,6 @@ ZYtQ9Ot36qc= } } -// subjectPublicKeyInfo is a PKIX public key structure defined in RFC 5280. -type subjectPublicKeyInfo struct { - Algorithm pkix.AlgorithmIdentifier - SubjectPublicKey asn1.BitString -} - -// generateSubjectKeyID generates the key identifier according the the RFC 5280 -// section 4.2.1.2. -// -// The keyIdentifier is composed of the 160-bit SHA-1 hash of the value of the -// BIT STRING subjectPublicKey (excluding the tag, length, and number of unused -// bits). -func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { - b, err := x509.MarshalPKIXPublicKey(pub) - if err != nil { - return nil, errors.Wrap(err, "error marshaling public key") - } - var info subjectPublicKeyInfo - if _, err = asn1.Unmarshal(b, &info); err != nil { - return nil, errors.Wrap(err, "error unmarshaling public key") - } - hash := sha1.Sum(info.SubjectPublicKey.Bytes) - return hash[:], nil -} - func TestAuthority_Renew(t *testing.T) { pub, _, err := keys.GenerateDefaultKeyPair() assert.FatalError(t, err) @@ -435,17 +487,24 @@ func TestAuthority_Renew(t *testing.T) { CommonName: "renew", } + certModToWithOptions := func(m provisioner.CertificateModifierFunc) x509util.WithOption { + return func(p x509util.Profile) error { + crt := p.Subject() + return m.Modify(crt, provisioner.SignOptions{}) + } + } + now := time.Now().UTC() nb1 := now.Add(-time.Minute * 7) na1 := now - so := &provisioner.Options{ + so := &provisioner.SignOptions{ NotBefore: provisioner.NewTimeDuration(nb1), NotAfter: provisioner.NewTimeDuration(na1), } leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer, x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), - withDefaultASN1DN(a.config.AuthorityConfig.Template), + certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)), x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"), withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID)) assert.FatalError(t, err) @@ -456,7 +515,7 @@ func TestAuthority_Renew(t *testing.T) { leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer, x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), - withDefaultASN1DN(a.config.AuthorityConfig.Template), + certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)), x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"), withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID), ) @@ -576,12 +635,9 @@ func TestAuthority_Renew(t *testing.T) { []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) - // Test Public Key and SubjectKeyId - assert.Equals(t, leaf.PublicKey, cert.PublicKey) - kid, err := generateSubjectKeyID(cert.PublicKey) + subjectKeyID, err := generateSubjectKeyID(pub) assert.FatalError(t, err) - assert.Equals(t, leaf.SubjectKeyId, kid) - assert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId) + assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) // We did not change the intermediate before renewing. if a.x509Issuer.SerialNumber == tc.auth.x509Issuer.SerialNumber { @@ -658,17 +714,24 @@ func TestAuthority_Rekey(t *testing.T) { CommonName: "renew", } + certModToWithOptions := func(m provisioner.CertificateModifierFunc) x509util.WithOption { + return func(p x509util.Profile) error { + crt := p.Subject() + return m.Modify(crt, provisioner.SignOptions{}) + } + } + now := time.Now().UTC() nb1 := now.Add(-time.Minute * 7) na1 := now - so := &provisioner.Options{ + so := &provisioner.SignOptions{ NotBefore: provisioner.NewTimeDuration(nb1), NotAfter: provisioner.NewTimeDuration(na1), } leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer, x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), - withDefaultASN1DN(a.config.AuthorityConfig.Template), + certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)), x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"), withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID)) assert.FatalError(t, err) @@ -679,7 +742,7 @@ func TestAuthority_Rekey(t *testing.T) { leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer, x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), - withDefaultASN1DN(a.config.AuthorityConfig.Template), + certModToWithOptions(withDefaultASN1DN(a.config.AuthorityConfig.Template)), x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"), withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID), ) @@ -814,9 +877,9 @@ func TestAuthority_Rekey(t *testing.T) { } assert.Equals(t, leaf.PublicKey, expectedPK) - kid, err := generateSubjectKeyID(expectedPK) + subjectKeyID, err := generateSubjectKeyID(expectedPK) assert.FatalError(t, err) - assert.Equals(t, leaf.SubjectKeyId, kid) + assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) if tc.pk == nil { assert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId) } diff --git a/ca/ca_test.go b/ca/ca_test.go index 568e7917..15e4d42a 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -34,31 +34,6 @@ import ( "gopkg.in/square/go-jose.v2/jwt" ) -// subjectPublicKeyInfo is a PKIX public key structure defined in RFC 5280. -type subjectPublicKeyInfo struct { - Algorithm pkix.AlgorithmIdentifier - SubjectPublicKey asn1.BitString -} - -// generateSubjectKeyID generates the key identifier according the the RFC 5280 -// section 4.2.1.2. -// -// The keyIdentifier is composed of the 160-bit SHA-1 hash of the value of the -// BIT STRING subjectPublicKey (excluding the tag, length, and number of unused -// bits). -func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { - b, err := x509.MarshalPKIXPublicKey(pub) - if err != nil { - return nil, errors.Wrap(err, "error marshaling public key") - } - var info subjectPublicKeyInfo - if _, err = asn1.Unmarshal(b, &info); err != nil { - return nil, errors.Wrap(err, "error unmarshaling public key") - } - hash := sha1.Sum(info.SubjectPublicKey.Bytes) - return hash[:], nil -} - type ClosingBuffer struct { *bytes.Buffer } @@ -79,6 +54,22 @@ func getCSR(priv interface{}) (*x509.CertificateRequest, error) { return x509.ParseCertificateRequest(csrBytes) } +func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { + b, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return nil, errors.Wrap(err, "error marshaling public key") + } + info := struct { + Algorithm pkix.AlgorithmIdentifier + SubjectPublicKey asn1.BitString + }{} + if _, err = asn1.Unmarshal(b, &info); err != nil { + return nil, errors.Wrap(err, "error unmarshaling public key") + } + hash := sha1.Sum(info.SubjectPublicKey.Bytes) + return hash[:], nil +} + func TestMain(m *testing.M) { DisableIdentity = true os.Exit(m.Run()) @@ -326,9 +317,9 @@ ZEp7knvU2psWRw== []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"}) - kid, err := generateSubjectKeyID(pub) + subjectKeyID, err := generateSubjectKeyID(pub) assert.FatalError(t, err) - assert.Equals(t, leaf.SubjectKeyId, kid) + assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) assert.Equals(t, leaf.AuthorityKeyId, intermediateIdentity.Crt.SubjectKeyId) @@ -667,10 +658,9 @@ func TestCARenew(t *testing.T) { []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) assert.Equals(t, leaf.DNSNames, []string{"funk"}) - kid, err := generateSubjectKeyID(pub) + subjectKeyID, err := generateSubjectKeyID(pub) assert.FatalError(t, err) - assert.Equals(t, leaf.SubjectKeyId, kid) - + assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) assert.Equals(t, leaf.AuthorityKeyId, intermediateIdentity.Crt.SubjectKeyId) realIntermediate, err := x509.ParseCertificate(intermediateIdentity.Crt.Raw) diff --git a/ca/provisioner.go b/ca/provisioner.go index 1b7067e0..d5dfd648 100644 --- a/ca/provisioner.go +++ b/ca/provisioner.go @@ -133,7 +133,7 @@ func (p *Provisioner) SSHToken(certType, keyID string, principals []string) (str token.WithIssuer(p.name), token.WithAudience(p.sshAudience), token.WithValidity(notBefore, notAfter), - token.WithSSH(provisioner.SSHOptions{ + token.WithSSH(provisioner.SignSSHOptions{ CertType: certType, Principals: principals, KeyID: keyID, diff --git a/ca/tls.go b/ca/tls.go index ffae68e5..20a5e504 100644 --- a/ca/tls.go +++ b/ca/tls.go @@ -56,7 +56,7 @@ func (c *Client) getClientTLSConfig(ctx context.Context, sign *api.SignResponse, return nil, nil, err } // Use mutable tls.Config on renew - tr.DialTLS = c.buildDialTLS(tlsCtx) //nolint:deprecated + tr.DialTLS = c.buildDialTLS(tlsCtx) // nolint:staticcheck // tr.DialTLSContext = c.buildDialTLSContext(tlsCtx) renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk) @@ -108,7 +108,7 @@ func (c *Client) GetServerTLSConfig(ctx context.Context, sign *api.SignResponse, return nil, err } // Use mutable tls.Config on renew - tr.DialTLS = c.buildDialTLS(tlsCtx) //nolint:deprecated + tr.DialTLS = c.buildDialTLS(tlsCtx) // nolint:staticcheck // tr.DialTLSContext = c.buildDialTLSContext(tlsCtx) renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk) diff --git a/go.mod b/go.mod index 96dfd661..fe5082ac 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,11 @@ go 1.13 require ( cloud.google.com/go v0.51.0 - github.com/Masterminds/sprig/v3 v3.0.0 + github.com/Masterminds/sprig/v3 v3.1.0 github.com/aws/aws-sdk-go v1.30.29 github.com/go-chi/chi v4.0.2+incompatible github.com/go-piv/piv-go v1.5.0 github.com/googleapis/gax-go/v2 v2.0.5 - github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect - github.com/lunixbochs/vtclean v1.0.0 // indirect github.com/newrelic/go-agent v2.15.0+incompatible github.com/pkg/errors v0.9.1 github.com/rs/xid v1.2.1 @@ -19,7 +17,8 @@ require ( github.com/smallstep/cli v0.14.7-rc.1.0.20200727165646-eb4e97335f2d github.com/smallstep/nosql v0.3.0 github.com/urfave/cli v1.22.2 - golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 + go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0 + golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 golang.org/x/net v0.0.0-20200202094626-16171245cfb2 google.golang.org/api v0.15.0 google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb diff --git a/go.sum b/go.sum index 264f7505..4a29e13f 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,12 @@ github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITg github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk= github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig/v3 v3.0.0 h1:KSQz7Nb08/3VU9E4ns29dDxcczhOD1q7O1UfM4G3t3g= github.com/Masterminds/sprig/v3 v3.0.0/go.mod h1:NEUY/Qq8Gdm2xgYA+NwJM6wmfdRV9xkh8h/Rld20R0U= +github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y= +github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= @@ -274,10 +278,14 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -472,10 +480,7 @@ github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/certificates v0.14.5/go.mod h1:zzpB8wMz967gL8FmK6zvCNB4pDVwFDKjPg1diTVc1h8= github.com/smallstep/certinfo v1.3.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU= -github.com/smallstep/cli v0.14.5 h1:avA6q9h2aIbAQa/vTFV5psjJ1mg8NQliKC+RsFIC778= github.com/smallstep/cli v0.14.5/go.mod h1:mRFuqC3cGwQESBGJvog4o76jZZZ7bMjkE+hAnq2QyR8= -github.com/smallstep/cli v0.14.6 h1:xc9rawDKB70Vgvg10gfQAh9EpDWS3k1O002J5bApqUk= -github.com/smallstep/cli v0.14.6/go.mod h1:Gs9mXwk5tIBlSISt1tvCAMso7eEAiJRyNgR/6JsoojI= github.com/smallstep/cli v0.14.7-rc.1.0.20200727165646-eb4e97335f2d h1:bDnvzyEXzAMO5in8QHRrQCEUopiXjFVKHiR8c0m7Iww= github.com/smallstep/cli v0.14.7-rc.1.0.20200727165646-eb4e97335f2d/go.mod h1:7aWHk7WwJMpEP4PYyav86FMpaI9vuA0uJRliUAqCwxg= github.com/smallstep/nosql v0.3.0 h1:V1X5vfDsDt89499h3jZFUlR4VnnsYYs5tXaQZ0w8z5U= @@ -486,69 +491,51 @@ github.com/smallstep/zcrypto v0.0.0-20200203191936-fbc32cf76bce/go.mod h1:+F24VU github.com/smallstep/zlint v0.0.0-20180727184541-d84eaafe274f/go.mod h1:GeHHT7sJDI9ti3oEaFnvx1F4N8n3ZSw2YM1+sbEoxc4= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk= github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= -github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q= github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= -github.com/tommy-muehle/go-mnd v1.1.1 h1:4D0wuPKjOTiK2garzuPGGvm4zZ/wLYDOH8TJSABC7KU= github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbdo= github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg= github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs= github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= @@ -556,7 +543,6 @@ github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOV github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= @@ -568,24 +554,21 @@ github.com/zmap/zlint v0.0.0-20190516161541-9047d02cf65a/go.mod h1:xwLbce0UzBXp4 go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= -go.etcd.io/etcd v3.3.18+incompatible h1:5aomL5mqoKHxw6NG+oYgsowk8tU8aOalo2IdZxdWHkw= go.etcd.io/etcd v3.3.18+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0 h1:FymMl8TrXGxFf80BWpO0CnkSfLnw0BkDdRrhbMGf5zE= +go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0/go.mod h1:8VYxmvSKt5yOTBx3MGsD2Gk4F1Es/3FIxrjnfeYWE8U= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM= go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -593,15 +576,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -616,7 +597,6 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -640,11 +620,9 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -659,7 +637,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -672,7 +649,6 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -681,7 +657,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -689,14 +664,12 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e h1:LwyF2AFISC9nVbS6MgzsaQNSUsRXI49GS+YQ5KX/QH0= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -736,7 +709,6 @@ golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc h1:MR2F33ipDGog0C4eMhU6u9o3q6c3dvYis2aG6Jl12Wg= golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -750,7 +722,6 @@ google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= @@ -781,13 +752,11 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A= @@ -795,7 +764,6 @@ gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -806,21 +774,15 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY= mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= -mvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2 h1:K7wru2CfJGumS5hkiguQ0Rb9ebKM2Jo8s5d4Jm9lFaM= mvdan.cc/unparam v0.0.0-20191111180625-960b1ec0f2c2/go.mod h1:rCqoQrfAmpTX/h2APczwM7UymU/uvaOluiVPIYCSY/k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= -sourcegraph.com/sqs/pbtypes v1.0.0 h1:f7lAwqviDEGvON4kRv0o5V7FT/IQK+tbkF664XMbP3o= sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4=