Merge pull request #312 from smallstep/cert-templates

Certificate flexibility
This commit is contained in:
Mariano Cano 2020-08-14 11:23:11 -07:00 committed by GitHub
commit 93b532ecff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 1274 additions and 669 deletions

View file

@ -14,7 +14,7 @@ SRC=$(shell find . -type f -name '*.go' -not -path "./vendor/*")
GOOS_OVERRIDE ?= GOOS_OVERRIDE ?=
OUTPUT_ROOT=output/ OUTPUT_ROOT=output/
all: lint build test all: lint test build
.PHONY: all .PHONY: all

View file

@ -18,6 +18,7 @@ type Provisioner interface {
AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error)
GetName() string GetName() string
DefaultTLSCertDuration() time.Duration DefaultTLSCertDuration() time.Duration
GetOptions() *provisioner.Options
} }
// MockProvisioner for testing // MockProvisioner for testing
@ -27,6 +28,7 @@ type MockProvisioner struct {
MgetName func() string MgetName func() string
MauthorizeSign func(ctx context.Context, ott string) ([]provisioner.SignOption, error) MauthorizeSign func(ctx context.Context, ott string) ([]provisioner.SignOption, error)
MdefaultTLSCertDuration func() time.Duration MdefaultTLSCertDuration func() time.Duration
MgetOptions func() *provisioner.Options
} }
// GetName mock // GetName mock
@ -53,6 +55,13 @@ func (m *MockProvisioner) DefaultTLSCertDuration() time.Duration {
return m.Mret1.(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 // ContextKey is the key type for storing and searching for ACME request
// essentials in the context of a request. // essentials in the context of a request.
type ContextKey string type ContextKey string
@ -125,7 +134,7 @@ func ProvisionerFromContext(ctx context.Context) (Provisioner, error) {
// SignAuthority is the interface implemented by a CA authority. // SignAuthority is the interface implemented by a CA authority.
type SignAuthority interface { 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) LoadProvisionerByID(string) (provisioner.Interface, error)
} }

View file

@ -11,6 +11,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"go.step.sm/crypto/x509util"
) )
var defaultOrderExpiry = time.Hour * 24 var defaultOrderExpiry = time.Hour * 24
@ -299,23 +300,23 @@ func (o *order) finalize(db nosql.DB, csr *x509.CertificateRequest, auth SignAut
orderNames = uniqueLowerNames(orderNames) orderNames = uniqueLowerNames(orderNames)
// Validate identifier names against CSR alternative names. // 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) { 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)) 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 { for i := range csr.DNSNames {
if csr.DNSNames[i] != orderNames[i] { 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)) return nil, BadCSRErr(errors.Errorf("CSR names do not match identifiers exactly: CSR names = %v, Order names = %v", csr.DNSNames, orderNames))
} }
} sans[i] = x509util.SubjectAlternativeName{
Type: x509util.DNSType,
if len(csr.IPAddresses) > 0 { Value: csr.DNSNames[i],
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"))
} }
// Get authorizations from the ACME provisioner. // 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")) 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. // 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), NotBefore: provisioner.NewTimeDuration(o.NotBefore),
NotAfter: provisioner.NewTimeDuration(o.NotAfter), NotAfter: provisioner.NewTimeDuration(o.NotAfter),
}, signOps...) }, signOps...)

View file

@ -895,13 +895,13 @@ func TestOrderUpdateStatus(t *testing.T) {
} }
type mockSignAuth struct { 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) loadProvisionerByID func(string) (provisioner.Interface, error)
ret1, ret2 interface{} ret1, ret2 interface{}
err error 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 { if m.sign != nil {
return m.sign(csr, signOpts, extraOpts...) return m.sign(csr, signOpts, extraOpts...)
} else if m.err != nil { } else if m.err != nil {
@ -1066,13 +1066,13 @@ func TestOrderFinalize(t *testing.T) {
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: "", 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")}, IPAddresses: []net.IP{net.ParseIP("1.1.1.1")},
} }
return test{ return test{
o: o, o: o,
csr: csr, 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 { "fail/ready/no-emailAddresses": func(t *testing.T) test {
@ -1084,13 +1084,13 @@ func TestOrderFinalize(t *testing.T) {
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: "", 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"}, EmailAddresses: []string{"max@smallstep.com", "mariano@smallstep.com"},
} }
return test{ return test{
o: o, o: o,
csr: csr, 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 { "fail/ready/no-URIs": func(t *testing.T) test {
@ -1104,13 +1104,13 @@ func TestOrderFinalize(t *testing.T) {
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: "", CommonName: "",
}, },
DNSNames: []string{"acme.example.com", "step.example.com"}, // DNSNames: []string{"acme.example.com", "step.example.com"},
URIs: []*url.URL{u}, URIs: []*url.URL{u},
} }
return test{ return test{
o: o, o: o,
csr: csr, 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 { "fail/ready/provisioner-auth-sign-error": func(t *testing.T) test {
@ -1262,8 +1262,8 @@ func TestOrderFinalize(t *testing.T) {
res: clone, res: clone,
csr: csr, csr: csr,
sa: &mockSignAuth{ sa: &mockSignAuth{
sign: func(csr *x509.CertificateRequest, pops provisioner.Options, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { sign: func(csr *x509.CertificateRequest, pops provisioner.SignOptions, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, len(signOps), 5) assert.Equals(t, len(signOps), 6)
return []*x509.Certificate{crt, inter}, nil return []*x509.Certificate{crt, inter}, nil
}, },
}, },
@ -1311,8 +1311,8 @@ func TestOrderFinalize(t *testing.T) {
res: &clone, res: &clone,
csr: csr, csr: csr,
sa: &mockSignAuth{ sa: &mockSignAuth{
sign: func(csr *x509.CertificateRequest, pops provisioner.Options, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { sign: func(csr *x509.CertificateRequest, pops provisioner.SignOptions, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, len(signOps), 5) assert.Equals(t, len(signOps), 6)
return []*x509.Certificate{crt, inter}, nil return []*x509.Certificate{crt, inter}, nil
}, },
}, },
@ -1358,8 +1358,8 @@ func TestOrderFinalize(t *testing.T) {
res: &clone, res: &clone,
csr: csr, csr: csr,
sa: &mockSignAuth{ sa: &mockSignAuth{
sign: func(csr *x509.CertificateRequest, pops provisioner.Options, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) { sign: func(csr *x509.CertificateRequest, pops provisioner.SignOptions, signOps ...provisioner.SignOption) ([]*x509.Certificate, error) {
assert.Equals(t, len(signOps), 5) assert.Equals(t, len(signOps), 6)
return []*x509.Certificate{crt, inter}, nil return []*x509.Certificate{crt, inter}, nil
}, },
}, },

View file

@ -34,7 +34,7 @@ type Authority interface {
AuthorizeSign(ott string) ([]provisioner.SignOption, error) AuthorizeSign(ott string) ([]provisioner.SignOption, error)
GetTLSOptions() *tlsutil.TLSOptions GetTLSOptions() *tlsutil.TLSOptions
Root(shasum string) (*x509.Certificate, error) 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) Renew(peer *x509.Certificate) ([]*x509.Certificate, error)
Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error) LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error)

View file

@ -550,7 +550,7 @@ type mockAuthority struct {
authorizeSign func(ott string) ([]provisioner.SignOption, error) authorizeSign func(ott string) ([]provisioner.SignOption, error)
getTLSOptions func() *tlsutil.TLSOptions getTLSOptions func() *tlsutil.TLSOptions
root func(shasum string) (*x509.Certificate, error) 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) renew func(cert *x509.Certificate) ([]*x509.Certificate, error)
rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error) loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error)
@ -560,7 +560,7 @@ type mockAuthority struct {
getEncryptedKey func(kid string) (string, error) getEncryptedKey func(kid string) (string, error)
getRoots func() ([]*x509.Certificate, error) getRoots func() ([]*x509.Certificate, error)
getFederation 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) 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) 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) 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 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 { if m.sign != nil {
return m.sign(cr, opts, signOpts...) return m.sign(cr, opts, signOpts...)
} }
@ -669,7 +669,7 @@ func (m *mockAuthority) GetFederation() ([]*x509.Certificate, error) {
return m.ret1.([]*x509.Certificate), m.err 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 { if m.signSSH != nil {
return m.signSSH(ctx, key, opts, signOpts...) return m.signSSH(ctx, key, opts, signOpts...)
} }

View file

@ -2,6 +2,7 @@ package api
import ( import (
"crypto/tls" "crypto/tls"
"encoding/json"
"net/http" "net/http"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
@ -11,10 +12,11 @@ import (
// SignRequest is the request body for a certificate signature request. // SignRequest is the request body for a certificate signature request.
type SignRequest struct { type SignRequest struct {
CsrPEM CertificateRequest `json:"csr"` CsrPEM CertificateRequest `json:"csr"`
OTT string `json:"ott"` OTT string `json:"ott"`
NotAfter TimeDuration `json:"notAfter"` NotAfter TimeDuration `json:"notAfter"`
NotBefore TimeDuration `json:"notBefore"` NotBefore TimeDuration `json:"notBefore"`
TemplateData json.RawMessage `json:"templateData"`
} }
// Validate checks the fields of the SignRequest and returns nil if they are ok // 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 return
} }
opts := provisioner.Options{ opts := provisioner.SignOptions{
NotBefore: body.NotBefore, NotBefore: body.NotBefore,
NotAfter: body.NotAfter, NotAfter: body.NotAfter,
TemplateData: body.TemplateData,
} }
signOpts, err := h.Authority.AuthorizeSign(body.OTT) signOpts, err := h.Authority.AuthorizeSign(body.OTT)

View file

@ -19,7 +19,7 @@ import (
// SSHAuthority is the interface implemented by a SSH CA authority. // SSHAuthority is the interface implemented by a SSH CA authority.
type SSHAuthority interface { 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) 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) 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) 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, CertType: body.CertType,
KeyID: body.KeyID, KeyID: body.KeyID,
Principals: body.Principals, Principals: body.Principals,
@ -322,7 +322,7 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) {
NotAfter: time.Unix(int64(cert.ValidBefore), 0), 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 { if err != nil {
WriteError(w, errs.ForbiddenErr(err)) WriteError(w, errs.ForbiddenErr(err))
return return

View file

@ -319,13 +319,13 @@ func Test_caHandler_SSHSign(t *testing.T) {
authorizeSign: func(ott string) ([]provisioner.SignOption, error) { authorizeSign: func(ott string) ([]provisioner.SignOption, error) {
return []provisioner.SignOption{}, tt.authErr 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 return tt.signCert, tt.signErr
}, },
signSSHAddUser: func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) { signSSHAddUser: func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) {
return tt.addUserCert, tt.addUserErr 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 return tt.tlsSignCerts, tt.tlsSignErr
}, },
}).(*caHandler) }).(*caHandler)

View file

@ -283,7 +283,7 @@ func TestNewEmbedded_Sign(t *testing.T) {
csr, err := x509.ParseCertificateRequest(cr) csr, err := x509.ParseCertificateRequest(cr)
assert.FatalError(t, err) assert.FatalError(t, err)
cert, err := a.Sign(csr, provisioner.Options{}) cert, err := a.Sign(csr, provisioner.SignOptions{})
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, []string{"foo.bar.zar"}, cert[0].DNSNames) assert.Equals(t, []string{"foo.bar.zar"}, cert[0].DNSNames)
assert.Equals(t, crt, cert[1]) assert.Equals(t, crt, cert[1])

View file

@ -449,7 +449,7 @@ func TestAuthority_authorizeSign(t *testing.T) {
} }
} else { } else {
if assert.Nil(t, tc.err) { 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) { 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", CertType: "user",
Principals: []string{"name"}, Principals: []string{"name"},
}, jwk) }, jwk)
} }
type stepPayload struct { 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( sig, err := jose.NewSigner(
jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID), new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID),

View file

@ -13,10 +13,11 @@ import (
// provisioning flow. // provisioning flow.
type ACME struct { type ACME struct {
*base *base
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Claims *Claims `json:"claims,omitempty"` ForceCN bool `json:"forceCN,omitempty"`
ForceCN bool `json:"forceCN,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
claimer *Claimer claimer *Claimer
} }
@ -45,6 +46,11 @@ func (p *ACME) GetEncryptedKey() (string, string, bool) {
return "", "", false 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 // DefaultTLSCertDuration returns the default TLS cert duration enforced by
// the provisioner. // the provisioner.
func (p *ACME) DefaultTLSCertDuration() time.Duration { func (p *ACME) DefaultTLSCertDuration() time.Duration {

View file

@ -18,6 +18,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "github.com/smallstep/cli/jose"
"go.step.sm/crypto/x509util"
) )
// awsIssuer is the string used as issuer in the generated tokens. // awsIssuer is the string used as issuer in the generated tokens.
@ -150,6 +151,7 @@ type AWS struct {
IMDSVersions []string `json:"imdsVersions"` IMDSVersions []string `json:"imdsVersions"`
InstanceAge Duration `json:"instanceAge,omitempty"` InstanceAge Duration `json:"instanceAge,omitempty"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
claimer *Claimer claimer *Claimer
config *awsConfig config *awsConfig
audiences Audiences audiences Audiences
@ -310,22 +312,38 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
} }
doc := payload.document 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. // Enforce known CN and default DNS and IP if configured.
// By default we'll accept the CN and SANs in the CSR. // By default we'll accept the CN and SANs in the CSR.
// There's no way to trust them other than TOFU. // There's no way to trust them other than TOFU.
var so []SignOption var so []SignOption
if p.DisableCustomSANs { if p.DisableCustomSANs {
so = append(so, dnsNamesValidator([]string{ dnsName := fmt.Sprintf("ip-%s.%s.compute.internal", strings.Replace(doc.PrivateIP, ".", "-", -1), doc.Region)
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{ so = append(so, ipAddressesValidator([]net.IP{
net.ParseIP(doc.PrivateIP), net.ParseIP(doc.PrivateIP),
})) }))
so = append(so, emailAddressesValidator(nil)) so = append(so, emailAddressesValidator(nil))
so = append(so, urisValidator(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, return append(so,
templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID), newProvisionerExtensionOption(TypeAWS, p.Name, doc.AccountID, "InstanceID", doc.InstanceID),
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
@ -577,7 +595,7 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
} }
// Default to cert type to host // Default to cert type to host
defaults := SSHOptions{ defaults := SignSSHOptions{
CertType: SSHHostCert, CertType: SSHHostCert,
Principals: principals, Principals: principals,
} }

View file

@ -595,11 +595,11 @@ func TestAWS_AuthorizeSign(t *testing.T) {
code int code int
wantErr bool wantErr bool
}{ }{
{"ok", p1, args{t1, "foo.local"}, 5, http.StatusOK, false}, {"ok", p1, args{t1, "foo.local"}, 6, http.StatusOK, false},
{"ok", p2, args{t2, "instance-id"}, 9, 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"}, 9, 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"}, 9, http.StatusOK, false}, {"ok", p2, args{t2PrivateIP, "127.0.0.1"}, 10, http.StatusOK, false},
{"ok", p1, args{t4, "instance-id"}, 5, http.StatusOK, false}, {"ok", p1, args{t4, "instance-id"}, 6, http.StatusOK, false},
{"fail account", p3, args{token: t3}, 0, http.StatusUnauthorized, true}, {"fail account", p3, args{token: t3}, 0, http.StatusUnauthorized, true},
{"fail token", p1, args{token: "token"}, 0, http.StatusUnauthorized, true}, {"fail token", p1, args{token: "token"}, 0, http.StatusUnauthorized, true},
{"fail subject", p1, args{token: failSubject}, 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) assert.Len(t, tt.wantLen, got)
for _, o := range got { for _, o := range got {
switch v := o.(type) { switch v := o.(type) {
case certificateOptionsFunc:
case *provisionerExtensionOption: case *provisionerExtensionOption:
assert.Equals(t, v.Type, int(TypeAWS)) assert.Equals(t, v.Type, int(TypeAWS))
assert.Equals(t, v.Name, tt.aws.GetName()) assert.Equals(t, v.Name, tt.aws.GetName())
@ -701,51 +702,51 @@ func TestAWS_AuthorizeSSHSign(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
hostDuration := p1.claimer.DefaultHostSSHCertDuration() 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"}, 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)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
expectedHostOptionsIP := &SSHOptions{ expectedHostOptionsIP := &SignSSHOptions{
CertType: "host", Principals: []string{"127.0.0.1"}, CertType: "host", Principals: []string{"127.0.0.1"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), 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"}, CertType: "host", Principals: []string{"ip-127-0-0-1.us-west-1.compute.internal"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
expectedCustomOptions := &SSHOptions{ expectedCustomOptions := &SignSSHOptions{
CertType: "host", Principals: []string{"foo.local"}, CertType: "host", Principals: []string{"foo.local"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
type args struct { type args struct {
token string token string
sshOpts SSHOptions sshOpts SignSSHOptions
key interface{} key interface{}
} }
tests := []struct { tests := []struct {
name string name string
aws *AWS aws *AWS
args args args args
expected *SSHOptions expected *SignSSHOptions
code int code int
wantErr bool wantErr bool
wantSignErr bool wantSignErr bool
}{ }{
{"ok", p1, args{t1, SSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"ok", p1, args{t1, SignSSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false},
{"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false}, {"ok-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false},
{"ok-type", p1, args{t1, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"ok-type", p1, args{t1, SignSSHOptions{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-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, SSHOptions{Principals: []string{"127.0.0.1"}}, pub}, expectedHostOptionsIP, 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, SSHOptions{Principals: []string{"ip-127-0-0-1.us-west-1.compute.internal"}}, pub}, expectedHostOptionsHostname, 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, 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-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, SSHOptions{Principals: []string{"foo.local"}}, pub}, expectedCustomOptions, 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, SSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true}, {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true},
{"fail-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, nil, http.StatusOK, false, true}, {"fail-type", p1, args{t1, SignSSHOptions{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-principal", p1, args{t1, SignSSHOptions{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-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", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, {"fail-sshCA-disabled", p3, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false},
{"fail-invalid-token", p1, args{"foo", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, {"fail-invalid-token", p1, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -15,6 +15,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "github.com/smallstep/cli/jose"
"go.step.sm/crypto/x509util"
) )
// azureOIDCBaseURL is the base discovery url for Microsoft Azure tokens. // azureOIDCBaseURL is the base discovery url for Microsoft Azure tokens.
@ -90,6 +91,7 @@ type Azure struct {
DisableCustomSANs bool `json:"disableCustomSANs"` DisableCustomSANs bool `json:"disableCustomSANs"`
DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
claimer *Claimer claimer *Claimer
config *azureConfig config *azureConfig
oidcConfig openIDConfiguration 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. // Enforce known common name and default DNS if configured.
// By default we'll accept the CN and SANs in the CSR. // By default we'll accept the CN and SANs in the CSR.
// There's no way to trust them other than TOFU. // 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, ipAddressesValidator(nil))
so = append(so, emailAddressesValidator(nil)) so = append(so, emailAddressesValidator(nil))
so = append(so, urisValidator(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, return append(so,
templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeAzure, p.Name, p.TenantID), newProvisionerExtensionOption(TypeAzure, p.Name, p.TenantID),
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
@ -332,7 +350,7 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio
} }
// Default to host + known hostnames // Default to host + known hostnames
defaults := SSHOptions{ defaults := SignSSHOptions{
CertType: SSHHostCert, CertType: SSHHostCert,
Principals: principals, Principals: principals,
} }

View file

@ -431,9 +431,9 @@ func TestAzure_AuthorizeSign(t *testing.T) {
code int code int
wantErr bool wantErr bool
}{ }{
{"ok", p1, args{t1}, 4, http.StatusOK, false}, {"ok", p1, args{t1}, 5, http.StatusOK, false},
{"ok", p2, args{t2}, 9, http.StatusOK, false}, {"ok", p2, args{t2}, 10, http.StatusOK, false},
{"ok", p1, args{t11}, 4, http.StatusOK, false}, {"ok", p1, args{t11}, 5, http.StatusOK, false},
{"fail tenant", p3, args{t3}, 0, http.StatusUnauthorized, true}, {"fail tenant", p3, args{t3}, 0, http.StatusUnauthorized, true},
{"fail resource group", p4, args{t4}, 0, http.StatusUnauthorized, true}, {"fail resource group", p4, args{t4}, 0, http.StatusUnauthorized, true},
{"fail token", p1, args{"token"}, 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) assert.Len(t, tt.wantLen, got)
for _, o := range got { for _, o := range got {
switch v := o.(type) { switch v := o.(type) {
case certificateOptionsFunc:
case *provisionerExtensionOption: case *provisionerExtensionOption:
assert.Equals(t, v.Type, int(TypeAzure)) assert.Equals(t, v.Type, int(TypeAzure))
assert.Equals(t, v.Name, tt.azure.GetName()) assert.Equals(t, v.Name, tt.azure.GetName())
@ -570,41 +571,41 @@ func TestAzure_AuthorizeSSHSign(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
hostDuration := p1.claimer.DefaultHostSSHCertDuration() hostDuration := p1.claimer.DefaultHostSSHCertDuration()
expectedHostOptions := &SSHOptions{ expectedHostOptions := &SignSSHOptions{
CertType: "host", Principals: []string{"virtualMachine"}, CertType: "host", Principals: []string{"virtualMachine"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
expectedCustomOptions := &SSHOptions{ expectedCustomOptions := &SignSSHOptions{
CertType: "host", Principals: []string{"foo.bar"}, CertType: "host", Principals: []string{"foo.bar"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
type args struct { type args struct {
token string token string
sshOpts SSHOptions sshOpts SignSSHOptions
key interface{} key interface{}
} }
tests := []struct { tests := []struct {
name string name string
azure *Azure azure *Azure
args args args args
expected *SSHOptions expected *SignSSHOptions
code int code int
wantErr bool wantErr bool
wantSignErr bool wantSignErr bool
}{ }{
{"ok", p1, args{t1, SSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"ok", p1, args{t1, SignSSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false},
{"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false}, {"ok-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false},
{"ok-type", p1, args{t1, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"ok-type", p1, args{t1, SignSSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false},
{"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"virtualMachine"}}, 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, SSHOptions{CertType: "host", 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, SSHOptions{Principals: []string{"foo.bar"}}, pub}, expectedCustomOptions, 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, SSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true}, {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true},
{"fail-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, nil, http.StatusOK, false, true}, {"fail-type", p1, args{t1, SignSSHOptions{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-principal", p1, args{t1, SignSSHOptions{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-extra-principal", p1, args{t1, SignSSHOptions{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-sshCA-disabled", p3, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false},
{"fail-invalid-token", p1, args{"foo", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, {"fail-invalid-token", p1, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -16,6 +16,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "github.com/smallstep/cli/jose"
"go.step.sm/crypto/x509util"
) )
// gcpCertsURL is the url that serves Google OAuth2 public keys. // gcpCertsURL is the url that serves Google OAuth2 public keys.
@ -84,6 +85,7 @@ type GCP struct {
DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"`
InstanceAge Duration `json:"instanceAge,omitempty"` InstanceAge Duration `json:"instanceAge,omitempty"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
claimer *Claimer claimer *Claimer
config *gcpConfig config *gcpConfig
keyStore *keyStore keyStore *keyStore
@ -215,6 +217,14 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
} }
ce := claims.Google.ComputeEngine 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. // Enforce known common name and default DNS if configured.
// By default we we'll accept the CN and SANs in the CSR. // By default we we'll accept the CN and SANs in the CSR.
// There's no way to trust them other than TOFU. // 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, ipAddressesValidator(nil))
so = append(so, emailAddressesValidator(nil)) so = append(so, emailAddressesValidator(nil))
so = append(so, urisValidator(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, return append(so,
templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeGCP, p.Name, claims.Subject, "InstanceID", ce.InstanceID, "InstanceName", ce.InstanceName), newProvisionerExtensionOption(TypeGCP, p.Name, claims.Subject, "InstanceID", ce.InstanceID, "InstanceName", ce.InstanceName),
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),
@ -375,7 +394,7 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption,
} }
// Default to host + known hostnames // Default to host + known hostnames
defaults := SSHOptions{ defaults := SignSSHOptions{
CertType: SSHHostCert, CertType: SSHHostCert,
Principals: principals, Principals: principals,
} }

View file

@ -515,9 +515,9 @@ func TestGCP_AuthorizeSign(t *testing.T) {
code int code int
wantErr bool wantErr bool
}{ }{
{"ok", p1, args{t1}, 4, http.StatusOK, false}, {"ok", p1, args{t1}, 5, http.StatusOK, false},
{"ok", p2, args{t2}, 9, http.StatusOK, false}, {"ok", p2, args{t2}, 10, http.StatusOK, false},
{"ok", p3, args{t3}, 4, http.StatusOK, false}, {"ok", p3, args{t3}, 5, http.StatusOK, false},
{"fail token", p1, args{"token"}, 0, http.StatusUnauthorized, true}, {"fail token", p1, args{"token"}, 0, http.StatusUnauthorized, true},
{"fail key", p1, args{failKey}, 0, http.StatusUnauthorized, true}, {"fail key", p1, args{failKey}, 0, http.StatusUnauthorized, true},
{"fail iss", p1, args{failIss}, 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) assert.Len(t, tt.wantLen, got)
for _, o := range got { for _, o := range got {
switch v := o.(type) { switch v := o.(type) {
case certificateOptionsFunc:
case *provisionerExtensionOption: case *provisionerExtensionOption:
assert.Equals(t, v.Type, int(TypeGCP)) assert.Equals(t, v.Type, int(TypeGCP))
assert.Equals(t, v.Name, tt.gcp.GetName()) assert.Equals(t, v.Name, tt.gcp.GetName())
@ -622,51 +623,51 @@ func TestGCP_AuthorizeSSHSign(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
hostDuration := p1.claimer.DefaultHostSSHCertDuration() 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"}, 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)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
expectedHostOptionsPrincipal1 := &SSHOptions{ expectedHostOptionsPrincipal1 := &SignSSHOptions{
CertType: "host", Principals: []string{"instance-name.c.project-id.internal"}, CertType: "host", Principals: []string{"instance-name.c.project-id.internal"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
expectedHostOptionsPrincipal2 := &SSHOptions{ expectedHostOptionsPrincipal2 := &SignSSHOptions{
CertType: "host", Principals: []string{"instance-name.zone.c.project-id.internal"}, CertType: "host", Principals: []string{"instance-name.zone.c.project-id.internal"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
expectedCustomOptions := &SSHOptions{ expectedCustomOptions := &SignSSHOptions{
CertType: "host", Principals: []string{"foo.bar", "bar.foo"}, CertType: "host", Principals: []string{"foo.bar", "bar.foo"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
type args struct { type args struct {
token string token string
sshOpts SSHOptions sshOpts SignSSHOptions
key interface{} key interface{}
} }
tests := []struct { tests := []struct {
name string name string
gcp *GCP gcp *GCP
args args args args
expected *SSHOptions expected *SignSSHOptions
code int code int
wantErr bool wantErr bool
wantSignErr bool wantSignErr bool
}{ }{
{"ok", p1, args{t1, SSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"ok", p1, args{t1, SignSSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false},
{"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false}, {"ok-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false},
{"ok-type", p1, args{t1, SSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"ok-type", p1, args{t1, SignSSHOptions{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-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, SSHOptions{Principals: []string{"instance-name.c.project-id.internal"}}, pub}, expectedHostOptionsPrincipal1, 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, SSHOptions{Principals: []string{"instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptionsPrincipal2, 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, 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-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, SSHOptions{Principals: []string{"foo.bar", "bar.foo"}}, pub}, expectedCustomOptions, 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, SSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true}, {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true},
{"fail-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, nil, http.StatusOK, false, true}, {"fail-type", p1, args{t1, SignSSHOptions{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-principal", p1, args{t1, SignSSHOptions{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-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", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, {"fail-sshCA-disabled", p3, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false},
{"fail-invalid-token", p1, args{"foo", SSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, {"fail-invalid-token", p1, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -9,6 +9,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "github.com/smallstep/cli/jose"
"go.step.sm/crypto/x509util"
) )
// jwtPayload extends jwt.Claims with step attributes. // jwtPayload extends jwt.Claims with step attributes.
@ -19,7 +20,7 @@ type jwtPayload struct {
} }
type stepPayload 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 // 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"` Key *jose.JSONWebKey `json:"key"`
EncryptedKey string `json:"encryptedKey,omitempty"` EncryptedKey string `json:"encryptedKey,omitempty"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
claimer *Claimer claimer *Claimer
audiences Audiences audiences Audiences
} }
@ -151,7 +153,19 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
claims.SANs = []string{claims.Subject} 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{ return []SignOption{
templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID), newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID),
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),

View file

@ -295,9 +295,10 @@ func TestJWK_AuthorizeSign(t *testing.T) {
} }
} else { } else {
if assert.NotNil(t, got) { if assert.NotNil(t, got) {
assert.Len(t, 6, got) assert.Len(t, 7, got)
for _, o := range got { for _, o := range got {
switch v := o.(type) { switch v := o.(type) {
case certificateOptionsFunc:
case *provisionerExtensionOption: case *provisionerExtensionOption:
assert.Equals(t, v.Type, int(TypeJWK)) assert.Equals(t, v.Type, int(TypeJWK))
assert.Equals(t, v.Name, tt.prov.GetName()) assert.Equals(t, v.Name, tt.prov.GetName())
@ -403,41 +404,41 @@ func TestJWK_AuthorizeSSHSign(t *testing.T) {
userDuration := p1.claimer.DefaultUserSSHCertDuration() userDuration := p1.claimer.DefaultUserSSHCertDuration()
hostDuration := p1.claimer.DefaultHostSSHCertDuration() hostDuration := p1.claimer.DefaultHostSSHCertDuration()
expectedUserOptions := &SSHOptions{ expectedUserOptions := &SignSSHOptions{
CertType: "user", Principals: []string{"name"}, CertType: "user", Principals: []string{"name"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)),
} }
expectedHostOptions := &SSHOptions{ expectedHostOptions := &SignSSHOptions{
CertType: "host", Principals: []string{"smallstep.com"}, CertType: "host", Principals: []string{"smallstep.com"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
type args struct { type args struct {
token string token string
sshOpts SSHOptions sshOpts SignSSHOptions
key interface{} key interface{}
} }
tests := []struct { tests := []struct {
name string name string
prov *JWK prov *JWK
args args args args
expected *SSHOptions expected *SignSSHOptions
code int code int
wantErr bool wantErr bool
wantSignErr bool wantSignErr bool
}{ }{
{"user", p1, args{t1, SSHOptions{}, pub}, expectedUserOptions, http.StatusOK, false, false}, {"user", p1, args{t1, SignSSHOptions{}, pub}, expectedUserOptions, http.StatusOK, false, false},
{"user-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedUserOptions, http.StatusOK, false, false}, {"user-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedUserOptions, http.StatusOK, false, false},
{"user-type", p1, args{t1, SSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false}, {"user-type", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false},
{"user-principals", p1, args{t1, SSHOptions{Principals: []string{"name"}}, 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, SSHOptions{CertType: "user", 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, SSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"host", p1, args{t2, SignSSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false},
{"host-type", p1, args{t2, SSHOptions{CertType: "host"}, 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, SSHOptions{Principals: []string{"smallstep.com"}}, 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, SSHOptions{CertType: "host", 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", SSHOptions{}, pub}, expectedUserOptions, http.StatusUnauthorized, true, false}, {"fail-sshCA-disabled", p2, args{"foo", SignSSHOptions{}, pub}, expectedUserOptions, http.StatusUnauthorized, true, false},
{"fail-signature", p1, args{failSig, SSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, {"fail-signature", p1, args{failSig, SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false},
{"rail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true}, {"rail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -486,72 +487,72 @@ func TestJWK_AuthorizeSign_SSHOptions(t *testing.T) {
userDuration := p1.claimer.DefaultUserSSHCertDuration() userDuration := p1.claimer.DefaultUserSSHCertDuration()
hostDuration := p1.claimer.DefaultHostSSHCertDuration() hostDuration := p1.claimer.DefaultHostSSHCertDuration()
expectedUserOptions := &SSHOptions{ expectedUserOptions := &SignSSHOptions{
CertType: "user", Principals: []string{"name"}, CertType: "user", Principals: []string{"name"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)),
} }
expectedHostOptions := &SSHOptions{ expectedHostOptions := &SignSSHOptions{
CertType: "host", Principals: []string{"smallstep.com"}, CertType: "host", Principals: []string{"smallstep.com"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
type args struct { type args struct {
sub, iss, aud string sub, iss, aud string
iat time.Time iat time.Time
tokSSHOpts *SSHOptions tokSSHOpts *SignSSHOptions
userSSHOpts *SSHOptions userSSHOpts *SignSSHOptions
jwk *jose.JSONWebKey jwk *jose.JSONWebKey
} }
tests := []struct { tests := []struct {
name string name string
prov *JWK prov *JWK
args args args args
expected *SSHOptions expected *SignSSHOptions
wantErr bool wantErr bool
wantSignErr bool wantSignErr bool
}{ }{
{"ok-user", p1, args{sub, iss, aud, iat, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, jwk}, expectedUserOptions, false, false}, {"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, &SSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, &SSHOptions{}, jwk}, expectedHostOptions, 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, &SSHOptions{}, &SSHOptions{CertType: "user", Principals: []string{"name"}}, jwk}, expectedUserOptions, 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, &SSHOptions{}, &SSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, jwk}, expectedHostOptions, 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, &SSHOptions{CertType: "user"}, &SSHOptions{Principals: []string{"name"}}, jwk}, expectedUserOptions, 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, &SSHOptions{Principals: []string{"smallstep.com"}}, &SSHOptions{CertType: "host"}, jwk}, expectedHostOptions, 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, &SSHOptions{ {"ok-user-validAfter", p1, args{sub, iss, aud, iat, &SignSSHOptions{
CertType: "user", Principals: []string{"name"}, CertType: "user", Principals: []string{"name"},
}, &SSHOptions{ }, &SignSSHOptions{
ValidAfter: NewTimeDuration(tm.Add(-time.Hour)), 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)), CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm.Add(-time.Hour)), ValidBefore: NewTimeDuration(tm.Add(userDuration - time.Hour)),
}, false, false}, }, 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"}, CertType: "user", Principals: []string{"name"},
}, &SSHOptions{ }, &SignSSHOptions{
ValidBefore: NewTimeDuration(tm.Add(time.Hour)), ValidBefore: NewTimeDuration(tm.Add(time.Hour)),
}, jwk}, &SSHOptions{ }, jwk}, &SignSSHOptions{
CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(time.Hour)), CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(time.Hour)),
}, false, false}, }, 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"}, CertType: "user", Principals: []string{"name"},
}, &SSHOptions{ }, &SignSSHOptions{
ValidAfter: NewTimeDuration(tm.Add(10 * time.Minute)), ValidBefore: NewTimeDuration(tm.Add(time.Hour)), 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)), CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm.Add(10 * time.Minute)), ValidBefore: NewTimeDuration(tm.Add(time.Hour)),
}, false, false}, }, 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)), 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)), 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)), CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(time.Hour)),
}, false, false}, }, false, false},
{"fail-certType", p1, args{sub, iss, aud, iat, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{CertType: "host"}, jwk}, nil, false, true}, {"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, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{Principals: []string{"root"}}, 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, &SSHOptions{CertType: "user", Principals: []string{"name"}, ValidAfter: NewTimeDuration(tm)}, &SSHOptions{ValidAfter: NewTimeDuration(tm.Add(time.Hour))}, 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, &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-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, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, jwk}, nil, true, false}, {"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, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, 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, &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, 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), &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, 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), &SSHOptions{CertType: "user", Principals: []string{"name"}}, &SSHOptions{}, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 { if got, err := tt.prov.AuthorizeSSHSign(context.Background(), token); (err != nil) != tt.wantErr {
t.Errorf("JWK.AuthorizeSSHSign() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("JWK.AuthorizeSSHSign() error = %v, wantErr %v", err, tt.wantErr)
} else if !tt.wantErr && assert.NotNil(t, got) { } else if !tt.wantErr && assert.NotNil(t, got) {
var opts SSHOptions var opts SignSSHOptions
if tt.args.userSSHOpts != nil { if tt.args.userSSHOpts != nil {
opts = *tt.args.userSSHOpts opts = *tt.args.userSSHOpts
} }

View file

@ -3,6 +3,7 @@ package provisioner
import ( import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
@ -12,7 +13,7 @@ import (
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/jose" "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 // 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. // entity trusted to make signature requests.
type K8sSA struct { type K8sSA struct {
*base *base
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Claims *Claims `json:"claims,omitempty"` PubKeys []byte `json:"publicKeys,omitempty"`
PubKeys []byte `json:"publicKeys,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
claimer *Claimer claimer *Claimer
audiences Audiences audiences Audiences
//kauthn kauthn.AuthenticationV1Interface //kauthn kauthn.AuthenticationV1Interface
@ -204,11 +206,27 @@ func (p *K8sSA) AuthorizeRevoke(ctx context.Context, token string) error {
// AuthorizeSign validates the given token. // AuthorizeSign validates the given token.
func (p *K8sSA) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) { 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 nil, errs.Wrap(http.StatusInternalServerError, err, "k8ssa.AuthorizeSign")
} }
return []SignOption{ return []SignOption{
templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeK8sSA, p.Name, ""), newProvisionerExtensionOption(TypeK8sSA, p.Name, ""),
profileDefaultDuration(p.claimer.DefaultTLSCertDuration()), profileDefaultDuration(p.claimer.DefaultTLSCertDuration()),

View file

@ -274,6 +274,7 @@ func TestK8sSA_AuthorizeSign(t *testing.T) {
tot := 0 tot := 0
for _, o := range opts { for _, o := range opts {
switch v := o.(type) { switch v := o.(type) {
case certificateOptionsFunc:
case *provisionerExtensionOption: case *provisionerExtensionOption:
assert.Equals(t, v.Type, int(TypeK8sSA)) assert.Equals(t, v.Type, int(TypeK8sSA))
assert.Equals(t, v.Name, tc.p.GetName()) assert.Equals(t, v.Name, tc.p.GetName())
@ -290,7 +291,7 @@ func TestK8sSA_AuthorizeSign(t *testing.T) {
} }
tot++ tot++
} }
assert.Equals(t, tot, 4) assert.Equals(t, tot, 5)
} }
} }
} }

View file

@ -14,6 +14,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "github.com/smallstep/cli/jose"
"go.step.sm/crypto/x509util"
) )
// openIDConfiguration contains the necessary properties in the // openIDConfiguration contains the necessary properties in the
@ -63,6 +64,7 @@ type OIDC struct {
Groups []string `json:"groups,omitempty"` Groups []string `json:"groups,omitempty"`
ListenAddress string `json:"listenAddress,omitempty"` ListenAddress string `json:"listenAddress,omitempty"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
configuration openIDConfiguration configuration openIDConfiguration
keyStore *keyStore keyStore *keyStore
claimer *Claimer claimer *Claimer
@ -72,10 +74,12 @@ type OIDC struct {
// IsAdmin returns true if the given email is in the Admins allowlist, false // IsAdmin returns true if the given email is in the Admins allowlist, false
// otherwise. // otherwise.
func (o *OIDC) IsAdmin(email string) bool { func (o *OIDC) IsAdmin(email string) bool {
email = sanitizeEmail(email) if email != "" {
for _, e := range o.Admins { email = sanitizeEmail(email)
if email == sanitizeEmail(e) { for _, e := range o.Admins {
return true if email == sanitizeEmail(e) {
return true
}
} }
} }
return false 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") 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) // 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) email := sanitizeEmail(p.Email)
var found bool var found bool
for _, d := range o.Domains { 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") 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 // modifiers / withOptions
newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID), newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID),
profileDefaultDuration(o.claimer.DefaultTLSCertDuration()), profileDefaultDuration(o.claimer.DefaultTLSCertDuration()),
// validators // validators
defaultPublicKeyValidator{}, defaultPublicKeyValidator{},
newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()), newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()),
} }, nil
// Admins should be able to authorize any SAN
if o.IsAdmin(claims.Email) {
return so, nil
}
return append(so, emailOnlyIdentity(claims.Email)), nil
} }
// AuthorizeRenew returns an error if the renewal is disabled. // 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 { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSSHSign") 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{ signOptions := []SignOption{
// set the key id to the token email // set the key id to the token email
sshCertKeyIDModifier(claims.Email), sshCertKeyIDModifier(claims.Email),
@ -348,7 +380,7 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSSHSign") return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSSHSign")
} }
defaults := SSHOptions{ defaults := SignSSHOptions{
CertType: SSHUserCert, CertType: SSHUserCert,
Principals: iden.Usernames, Principals: iden.Usernames,
} }

View file

@ -179,12 +179,12 @@ func TestOIDC_authorizeToken(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
t4, err := generateToken("subject", issuer, p3.ClientID, "foo@smallstep.com", []string{}, time.Now(), &keys.Keys[2]) t4, err := generateToken("subject", issuer, p3.ClientID, "foo@smallstep.com", []string{}, time.Now(), &keys.Keys[2])
assert.FatalError(t, err) assert.FatalError(t, err)
// Invalid email t5, err := generateToken("subject", issuer, p3.ClientID, "", []string{}, time.Now(), &keys.Keys[2])
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])
assert.FatalError(t, err) 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 // Invalid tokens
parts := strings.Split(t1, ".") parts := strings.Split(t1, ".")
key, err := generateJSONWebKey() key, err := generateJSONWebKey()
@ -226,7 +226,7 @@ func TestOIDC_authorizeToken(t *testing.T) {
{"ok tenantid", p2, args{t2}, http.StatusOK, tenantIssuer, false}, {"ok tenantid", p2, args{t2}, http.StatusOK, tenantIssuer, false},
{"ok admin", p3, args{t3}, http.StatusOK, issuer, false}, {"ok admin", p3, args{t3}, http.StatusOK, issuer, false},
{"ok domain", p3, args{t4}, 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-domain", p3, args{failDomain}, http.StatusUnauthorized, "", true},
{"fail-key", p1, args{failKey}, http.StatusUnauthorized, "", true}, {"fail-key", p1, args{failKey}, http.StatusUnauthorized, "", true},
{"fail-token", p1, args{failTok}, 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 // 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]) okAdmin, err := generateToken("subject", "the-issuer", p3.ClientID, "root@example.com", []string{"test.smallstep.com"}, time.Now(), &keys.Keys[0])
assert.FatalError(t, err) assert.FatalError(t, err)
// Invalid email // No email
failEmail, err := generateToken("subject", "the-issuer", p3.ClientID, "", []string{}, time.Now(), &keys.Keys[0]) noEmail, err := generateToken("subject", "the-issuer", p3.ClientID, "", []string{}, time.Now(), &keys.Keys[0])
assert.FatalError(t, err) assert.FatalError(t, err)
type args struct { type args struct {
@ -306,7 +306,8 @@ func TestOIDC_AuthorizeSign(t *testing.T) {
}{ }{
{"ok1", p1, args{t1}, http.StatusOK, false}, {"ok1", p1, args{t1}, http.StatusOK, false},
{"admin", p3, args{okAdmin}, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -323,12 +324,13 @@ func TestOIDC_AuthorizeSign(t *testing.T) {
} else { } else {
if assert.NotNil(t, got) { if assert.NotNil(t, got) {
if tt.name == "admin" { if tt.name == "admin" {
assert.Len(t, 4, got) assert.Len(t, 5, got)
} else { } else {
assert.Len(t, 5, got) assert.Len(t, 5, got)
} }
for _, o := range got { for _, o := range got {
switch v := o.(type) { switch v := o.(type) {
case certificateOptionsFunc:
case *provisionerExtensionOption: case *provisionerExtensionOption:
assert.Equals(t, v.Type, int(TypeOIDC)) assert.Equals(t, v.Type, int(TypeOIDC))
assert.Equals(t, v.Name, tt.prov.GetName()) assert.Equals(t, v.Name, tt.prov.GetName())
@ -514,7 +516,7 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) {
// Admin email not in domains // Admin email not in domains
okAdmin, err := generateToken("subject", "the-issuer", p3.ClientID, "root@example.com", []string{}, time.Now(), &keys.Keys[0]) okAdmin, err := generateToken("subject", "the-issuer", p3.ClientID, "root@example.com", []string{}, time.Now(), &keys.Keys[0])
assert.FatalError(t, err) assert.FatalError(t, err)
// Invalid email // Empty email
failEmail, err := generateToken("subject", "the-issuer", p3.ClientID, "", []string{}, time.Now(), &keys.Keys[0]) failEmail, err := generateToken("subject", "the-issuer", p3.ClientID, "", []string{}, time.Now(), &keys.Keys[0])
assert.FatalError(t, err) assert.FatalError(t, err)
@ -532,64 +534,64 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) {
userDuration := p1.claimer.DefaultUserSSHCertDuration() userDuration := p1.claimer.DefaultUserSSHCertDuration()
hostDuration := p1.claimer.DefaultHostSSHCertDuration() hostDuration := p1.claimer.DefaultHostSSHCertDuration()
expectedUserOptions := &SSHOptions{ expectedUserOptions := &SignSSHOptions{
CertType: "user", Principals: []string{"name", "name@smallstep.com"}, CertType: "user", Principals: []string{"name", "name@smallstep.com"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)),
} }
expectedAdminOptions := &SSHOptions{ expectedAdminOptions := &SignSSHOptions{
CertType: "user", Principals: []string{"root", "root@example.com"}, CertType: "user", Principals: []string{"root", "root@example.com"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration)),
} }
expectedHostOptions := &SSHOptions{ expectedHostOptions := &SignSSHOptions{
CertType: "host", Principals: []string{"smallstep.com"}, CertType: "host", Principals: []string{"smallstep.com"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)),
} }
type args struct { type args struct {
token string token string
sshOpts SSHOptions sshOpts SignSSHOptions
key interface{} key interface{}
} }
tests := []struct { tests := []struct {
name string name string
prov *OIDC prov *OIDC
args args args args
expected *SSHOptions expected *SignSSHOptions
code int code int
wantErr bool wantErr bool
wantSignErr bool wantSignErr bool
}{ }{
{"ok", p1, args{t1, SSHOptions{}, pub}, expectedUserOptions, http.StatusOK, false, false}, {"ok", p1, args{t1, SignSSHOptions{}, pub}, expectedUserOptions, http.StatusOK, false, false},
{"ok-rsa2048", p1, args{t1, SSHOptions{}, rsa2048.Public()}, expectedUserOptions, http.StatusOK, false, false}, {"ok-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedUserOptions, http.StatusOK, false, false},
{"ok-user", p1, args{t1, SSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false}, {"ok-user", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false},
{"ok-principals", p1, args{t1, SSHOptions{Principals: []string{"name"}}, pub}, {"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"name"}}, pub},
&SSHOptions{CertType: "user", Principals: []string{"name"}, &SignSSHOptions{CertType: "user", Principals: []string{"name"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"ok-principals-getIdentity", p4, args{okGetIdentityToken, SSHOptions{Principals: []string{"mariano"}}, pub}, {"ok-principals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{Principals: []string{"mariano"}}, pub},
&SSHOptions{CertType: "user", Principals: []string{"mariano"}, &SignSSHOptions{CertType: "user", Principals: []string{"mariano"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"ok-emptyPrincipals-getIdentity", p4, args{okGetIdentityToken, SSHOptions{}, pub}, {"ok-emptyPrincipals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{}, pub},
&SSHOptions{CertType: "user", Principals: []string{"max", "mariano"}, &SignSSHOptions{CertType: "user", Principals: []string{"max", "mariano"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"ok-options", p1, args{t1, SSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, {"ok-options", p1, args{t1, SignSSHOptions{CertType: "user", Principals: []string{"name"}}, pub},
&SSHOptions{CertType: "user", Principals: []string{"name"}, &SignSSHOptions{CertType: "user", Principals: []string{"name"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"admin", p3, args{okAdmin, SSHOptions{}, pub}, expectedAdminOptions, http.StatusOK, false, false}, {"admin", p3, args{okAdmin, SignSSHOptions{}, pub}, expectedAdminOptions, http.StatusOK, false, false},
{"admin-user", p3, args{okAdmin, SSHOptions{CertType: "user"}, 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, SSHOptions{Principals: []string{"root"}}, pub}, {"admin-principals", p3, args{okAdmin, SignSSHOptions{Principals: []string{"root"}}, pub},
&SSHOptions{CertType: "user", Principals: []string{"root"}, &SignSSHOptions{CertType: "user", Principals: []string{"root"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"admin-options", p3, args{okAdmin, SSHOptions{CertType: "user", Principals: []string{"name"}}, pub}, {"admin-options", p3, args{okAdmin, SignSSHOptions{CertType: "user", Principals: []string{"name"}}, pub},
&SSHOptions{CertType: "user", Principals: []string{"name"}, &SignSSHOptions{CertType: "user", Principals: []string{"name"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, 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}, expectedHostOptions, http.StatusOK, false, false},
{"fail-rsa1024", p1, args{t1, SSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true}, {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true},
{"fail-user-host", p1, args{t1, SSHOptions{CertType: "host"}, pub}, nil, 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, SSHOptions{Principals: []string{"root"}}, 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, SSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, {"fail-email", p3, args{failEmail, SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false},
{"fail-getIdentity", p5, args{failGetIdentityToken, SSHOptions{}, pub}, nil, http.StatusInternalServerError, true, false}, {"fail-getIdentity", p5, args{failGetIdentityToken, SignSSHOptions{}, pub}, nil, http.StatusInternalServerError, true, false},
{"fail-sshCA-disabled", p6, args{"foo", SSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, {"fail-sshCA-disabled", p6, args{"foo", SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -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
}

View file

@ -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": "<no value>"}`),
}, 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)
}
})
}
}

View file

@ -245,7 +245,7 @@ func (l *List) UnmarshalJSON(data []byte) error {
continue continue
} }
if err := json.Unmarshal(data, p); err != nil { 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) *l = append(*l, p)
} }

View file

@ -2,65 +2,77 @@ package provisioner
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"encoding/json"
"net" "net"
"net/url" "net/url"
"reflect" "reflect"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/crypto/x509util" "go.step.sm/crypto/x509util"
"golang.org/x/crypto/ed25519"
) )
// 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. // is automatically filled and can only be configured in the CA.
type Options struct { type SignOptions struct {
NotAfter TimeDuration `json:"notAfter"` NotAfter TimeDuration `json:"notAfter"`
NotBefore TimeDuration `json:"notBefore"` NotBefore TimeDuration `json:"notBefore"`
Backdate time.Duration `json:"-"` TemplateData json.RawMessage `json:"templateData"`
Backdate time.Duration `json:"-"`
} }
// SignOption is the interface used to collect all extra options used in the // SignOption is the interface used to collect all extra options used in the
// Sign method. // Sign method.
type SignOption interface{} 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 { type CertificateValidator interface {
SignOption Valid(cert *x509.Certificate, opts SignOptions) error
Valid(cert *x509.Certificate, o Options) error
} }
// CertificateRequestValidator is the interface used to validate a X.509 // CertificateRequestValidator is an interface used to validate a given X.509 certificate request.
// certificate request.
type CertificateRequestValidator interface { type CertificateRequestValidator interface {
SignOption Valid(cr *x509.CertificateRequest) error
Valid(req *x509.CertificateRequest) error
} }
// ProfileModifier is the interface used to add custom options to the profile // CertificateModifier is an interface used to modify a given X.509 certificate.
// constructor. The options are used to modify the final certificate. // Types implementing this interface will be validated with a
type ProfileModifier interface { // CertificateValidator.
SignOption type CertificateModifier interface {
Option(o Options) x509util.WithOption Modify(cert *x509.Certificate, opts SignOptions) error
} }
// CertificateEnforcer is the interface used to modify a certificate after // CertificateEnforcer is an interface used to modify a given X.509 certificate.
// validation. // Types implemented this interface will NOT be validated with a
// CertificateValidator.
type CertificateEnforcer interface { type CertificateEnforcer interface {
SignOption
Enforce(cert *x509.Certificate) error Enforce(cert *x509.Certificate) error
} }
// profileWithOption is a wrapper against x509util.WithOption to conform the // CertificateModifierFunc allows to create simple certificate modifiers just
// interface. // with a function.
type profileWithOption x509util.WithOption type CertificateModifierFunc func(cert *x509.Certificate, opts SignOptions) error
func (v profileWithOption) Option(Options) x509util.WithOption { // Modify implements CertificateModifier and just calls the defined function.
return x509util.WithOption(v) 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 // emailOnlyIdentity is a CertificateRequestValidator that checks that the only
@ -254,27 +266,30 @@ func (eee ExtraExtsEnforcer) Enforce(cert *x509.Certificate) error {
return nil return nil
} }
// profileDefaultDuration is a wrapper against x509util.WithOption to conform // profileDefaultDuration is a modifier that sets the certificate
// the SignOption interface. // duration.
type profileDefaultDuration time.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 var backdate time.Duration
notBefore := so.NotBefore.Time() notBefore := so.NotBefore.Time()
if notBefore.IsZero() { if notBefore.IsZero() {
notBefore = now() notBefore = now()
backdate = -1 * so.Backdate backdate = -1 * so.Backdate
} }
notAfter := so.NotAfter.RelativeTime(notBefore) notAfter := so.NotAfter.RelativeTime(notBefore)
return func(p x509util.Profile) error { if notAfter.IsZero() {
fn := x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(v)) if v != 0 {
if err := fn(p); err != nil { notAfter = notBefore.Add(time.Duration(v))
return err } 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 // 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 // Option returns an x509util option that limits the validity period of a
// certificate to one that is superficially imposed. // certificate to one that is superficially imposed.
func (v profileLimitDuration) Option(so Options) x509util.WithOption { func (v profileLimitDuration) Modify(cert *x509.Certificate, so SignOptions) error {
return func(p x509util.Profile) error { var backdate time.Duration
var backdate time.Duration notBefore := so.NotBefore.Time()
n := now() if notBefore.IsZero() {
notBefore := so.NotBefore.Time() notBefore = now()
if notBefore.IsZero() { backdate = -1 * so.Backdate
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
} }
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. // 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 // Valid validates the certificate validity settings (notBefore/notAfter) and
// and total duration. // and total duration.
func (v *validityValidator) Valid(cert *x509.Certificate, o Options) error { func (v *validityValidator) Valid(cert *x509.Certificate, o SignOptions) error {
var ( var (
na = cert.NotAfter.Truncate(time.Second) na = cert.NotAfter.Truncate(time.Second)
nb = cert.NotBefore.Truncate(time.Second) nb = cert.NotBefore.Truncate(time.Second)
@ -385,22 +397,21 @@ func newForceCNOption(forceCN bool) *forceCNOption {
return &forceCNOption{forceCN} return &forceCNOption{forceCN}
} }
func (o *forceCNOption) Option(Options) x509util.WithOption { func (o *forceCNOption) Modify(cert *x509.Certificate, _ SignOptions) error {
return func(p x509util.Profile) error { if !o.ForceCN {
if !o.ForceCN { // Forcing CN is disabled, do nothing to certificate
// 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")
}
}
return nil 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 { type provisionerExtensionOption struct {
@ -419,23 +430,20 @@ func newProvisionerExtensionOption(typ Type, name, credentialID string, keyValue
} }
} }
func (o *provisionerExtensionOption) Option(Options) x509util.WithOption { func (o *provisionerExtensionOption) Modify(cert *x509.Certificate, _ SignOptions) error {
return func(p x509util.Profile) error { ext, err := createProvisionerExtension(o.Type, o.Name, o.CredentialID, o.KeyValuePairs...)
crt := p.Subject() if err != nil {
ext, err := createProvisionerExtension(o.Type, o.Name, o.CredentialID, o.KeyValuePairs...) return err
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
} }
// 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) { 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, Value: b,
}, nil }, nil
} }
func init() {
// Avoid dead-code warning in profileWithOption
_ = profileWithOption(nil)
}

View file

@ -13,7 +13,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/x509util"
) )
func Test_emailOnlyIdentity_Valid(t *testing.T) { 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) { func Test_validityValidator_Valid(t *testing.T) {
type test struct { type test struct {
cert *x509.Certificate cert *x509.Certificate
opts Options opts SignOptions
vv *validityValidator vv *validityValidator
err error err error
} }
@ -409,7 +408,7 @@ func Test_validityValidator_Valid(t *testing.T) {
return test{ return test{
vv: &validityValidator{5 * time.Minute, 24 * time.Hour}, vv: &validityValidator{5 * time.Minute, 24 * time.Hour},
cert: &x509.Certificate{NotAfter: time.Now().Add(-5 * time.Minute)}, cert: &x509.Certificate{NotAfter: time.Now().Add(-5 * time.Minute)},
opts: Options{}, opts: SignOptions{},
err: errors.New("notAfter cannot be in the past"), 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}, vv: &validityValidator{5 * time.Minute, 24 * time.Hour},
cert: &x509.Certificate{NotBefore: time.Now().Add(10 * time.Minute), cert: &x509.Certificate{NotBefore: time.Now().Add(10 * time.Minute),
NotAfter: time.Now().Add(5 * time.Minute)}, NotAfter: time.Now().Add(5 * time.Minute)},
opts: Options{}, opts: SignOptions{},
err: errors.New("notAfter cannot be before notBefore"), 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}, vv: &validityValidator{5 * time.Minute, 24 * time.Hour},
cert: &x509.Certificate{NotBefore: n, cert: &x509.Certificate{NotBefore: n,
NotAfter: n.Add(3 * time.Minute)}, NotAfter: n.Add(3 * time.Minute)},
opts: Options{}, opts: SignOptions{},
err: errors.New("is less than the authorized minimum certificate duration of "), 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}, vv: &validityValidator{5 * time.Minute, 24 * time.Hour},
cert: &x509.Certificate{NotBefore: n, cert: &x509.Certificate{NotBefore: n,
NotAfter: n.Add(5 * time.Minute)}, NotAfter: n.Add(5 * time.Minute)},
opts: Options{}, opts: SignOptions{},
} }
}, },
"fail/duration-too-great": func() test { "fail/duration-too-great": func() test {
@ -465,7 +464,7 @@ func Test_validityValidator_Valid(t *testing.T) {
return test{ return test{
vv: &validityValidator{5 * time.Minute, 24 * time.Hour}, vv: &validityValidator{5 * time.Minute, 24 * time.Hour},
cert: cert, cert: cert,
opts: Options{Backdate: time.Second}, opts: SignOptions{Backdate: time.Second},
} }
}, },
"ok/duration-exact-max-with-backdate": func() test { "ok/duration-exact-max-with-backdate": func() test {
@ -476,7 +475,7 @@ func Test_validityValidator_Valid(t *testing.T) {
return test{ return test{
vv: &validityValidator{5 * time.Minute, 24 * time.Hour}, vv: &validityValidator{5 * time.Minute, 24 * time.Hour},
cert: cert, 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) { func Test_forceCN_Option(t *testing.T) {
type test struct { type test struct {
so Options so SignOptions
fcn forceCNOption fcn forceCNOption
cert *x509.Certificate cert *x509.Certificate
valid func(*x509.Certificate) valid func(*x509.Certificate)
@ -508,7 +507,7 @@ func Test_forceCN_Option(t *testing.T) {
"ok/CN-not-forced": func() test { "ok/CN-not-forced": func() test {
return test{ return test{
fcn: forceCNOption{false}, fcn: forceCNOption{false},
so: Options{}, so: SignOptions{},
cert: &x509.Certificate{ cert: &x509.Certificate{
Subject: pkix.Name{}, Subject: pkix.Name{},
DNSNames: []string{"acme.example.com", "step.example.com"}, 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 { "ok/CN-forced-and-set": func() test {
return test{ return test{
fcn: forceCNOption{true}, fcn: forceCNOption{true},
so: Options{}, so: SignOptions{},
cert: &x509.Certificate{ cert: &x509.Certificate{
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: "Some Common Name", CommonName: "Some Common Name",
@ -536,7 +535,7 @@ func Test_forceCN_Option(t *testing.T) {
"ok/CN-forced-and-not-set": func() test { "ok/CN-forced-and-not-set": func() test {
return test{ return test{
fcn: forceCNOption{true}, fcn: forceCNOption{true},
so: Options{}, so: SignOptions{},
cert: &x509.Certificate{ cert: &x509.Certificate{
Subject: pkix.Name{}, Subject: pkix.Name{},
DNSNames: []string{"acme.example.com", "step.example.com"}, 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 { "fail/CN-forced-and-empty-DNSNames": func() test {
return test{ return test{
fcn: forceCNOption{true}, fcn: forceCNOption{true},
so: Options{}, so: SignOptions{},
cert: &x509.Certificate{ cert: &x509.Certificate{
Subject: pkix.Name{}, Subject: pkix.Name{},
DNSNames: []string{}, DNSNames: []string{},
@ -562,15 +561,13 @@ func Test_forceCN_Option(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tt := run() tt := run()
prof := &x509util.Leaf{} if err := tt.fcn.Modify(tt.cert, tt.so); err != nil {
prof.SetSubject(tt.cert)
if err := tt.fcn.Option(tt.so)(prof); err != nil {
if assert.NotNil(t, tt.err) { if assert.NotNil(t, tt.err) {
assert.HasPrefix(t, err.Error(), tt.err.Error()) assert.HasPrefix(t, err.Error(), tt.err.Error())
} }
} else { } else {
if assert.Nil(t, tt.err) { 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) { func Test_profileDefaultDuration_Option(t *testing.T) {
type test struct { type test struct {
so Options so SignOptions
pdd profileDefaultDuration pdd profileDefaultDuration
cert *x509.Certificate cert *x509.Certificate
valid func(*x509.Certificate) valid func(*x509.Certificate)
@ -588,7 +585,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) {
"ok/notBefore-notAfter-duration-empty": func() test { "ok/notBefore-notAfter-duration-empty": func() test {
return test{ return test{
pdd: profileDefaultDuration(0), pdd: profileDefaultDuration(0),
so: Options{}, so: SignOptions{},
cert: new(x509.Certificate), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
n := now() n := now()
@ -604,7 +601,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) {
nb := time.Now().Add(5 * time.Minute).UTC() nb := time.Now().Add(5 * time.Minute).UTC()
return test{ return test{
pdd: profileDefaultDuration(0), pdd: profileDefaultDuration(0),
so: Options{NotBefore: NewTimeDuration(nb)}, so: SignOptions{NotBefore: NewTimeDuration(nb)},
cert: new(x509.Certificate), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
assert.Equals(t, cert.NotBefore, nb) assert.Equals(t, cert.NotBefore, nb)
@ -616,7 +613,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) {
d := 4 * time.Hour d := 4 * time.Hour
return test{ return test{
pdd: profileDefaultDuration(d), pdd: profileDefaultDuration(d),
so: Options{Backdate: time.Second}, so: SignOptions{Backdate: time.Second},
cert: new(x509.Certificate), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
n := now() n := now()
@ -632,7 +629,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) {
na := now().Add(10 * time.Minute).UTC() na := now().Add(10 * time.Minute).UTC()
return test{ return test{
pdd: profileDefaultDuration(0), pdd: profileDefaultDuration(0),
so: Options{NotAfter: NewTimeDuration(na)}, so: SignOptions{NotAfter: NewTimeDuration(na)},
cert: new(x509.Certificate), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
n := now() n := now()
@ -649,7 +646,7 @@ func Test_profileDefaultDuration_Option(t *testing.T) {
d := 4 * time.Hour d := 4 * time.Hour
return test{ return test{
pdd: profileDefaultDuration(d), pdd: profileDefaultDuration(d),
so: Options{NotBefore: NewTimeDuration(nb), NotAfter: NewTimeDuration(na)}, so: SignOptions{NotBefore: NewTimeDuration(nb), NotAfter: NewTimeDuration(na)},
cert: new(x509.Certificate), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
assert.Equals(t, cert.NotBefore, nb) assert.Equals(t, cert.NotBefore, nb)
@ -661,10 +658,9 @@ func Test_profileDefaultDuration_Option(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tt := run() tt := run()
prof := &x509util.Leaf{} assert.FatalError(t, tt.pdd.Modify(tt.cert, tt.so), "unexpected error")
prof.SetSubject(tt.cert) time.Sleep(1 * time.Nanosecond)
assert.FatalError(t, tt.pdd.Option(tt.so)(prof), "unexpected error") tt.valid(tt.cert)
tt.valid(prof.Subject())
}) })
} }
} }
@ -702,10 +698,8 @@ func Test_newProvisionerExtension_Option(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tt := run() tt := run()
prof := &x509util.Leaf{} assert.FatalError(t, newProvisionerExtensionOption(TypeJWK, "foo", "bar", "baz", "zap").Modify(tt.cert, SignOptions{}))
prof.SetSubject(tt.cert) tt.valid(tt.cert)
assert.FatalError(t, newProvisionerExtensionOption(TypeJWK, "foo", "bar", "baz", "zap").Option(Options{})(prof))
tt.valid(prof.Subject())
}) })
} }
} }
@ -716,7 +710,7 @@ func Test_profileLimitDuration_Option(t *testing.T) {
type test struct { type test struct {
pld profileLimitDuration pld profileLimitDuration
so Options so SignOptions
cert *x509.Certificate cert *x509.Certificate
valid func(*x509.Certificate) valid func(*x509.Certificate)
err error err error
@ -727,7 +721,7 @@ func Test_profileLimitDuration_Option(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
return test{ return test{
pld: profileLimitDuration{def: 4 * time.Hour, notBefore: n.Add(8 * time.Hour)}, pld: profileLimitDuration{def: 4 * time.Hour, notBefore: n.Add(8 * time.Hour)},
so: Options{NotBefore: d}, so: SignOptions{NotBefore: d},
cert: new(x509.Certificate), cert: new(x509.Certificate),
err: errors.New("requested certificate notBefore ("), err: errors.New("requested certificate notBefore ("),
} }
@ -737,7 +731,7 @@ func Test_profileLimitDuration_Option(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
return test{ return test{
pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)}, 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), cert: new(x509.Certificate),
err: errors.New("requested certificate notAfter ("), err: errors.New("requested certificate notAfter ("),
} }
@ -747,7 +741,7 @@ func Test_profileLimitDuration_Option(t *testing.T) {
assert.FatalError(t, err) assert.FatalError(t, err)
return test{ return test{
pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)}, 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), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour)) 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 { "ok/valid-notAfter-nil-limit-over-default": func() test {
return test{ return test{
pld: profileLimitDuration{def: 1 * time.Hour, notAfter: n.Add(6 * time.Hour)}, 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), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour)) 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 { "ok/valid-notAfter-nil-limit-under-default": func() test {
return test{ return test{
pld: profileLimitDuration{def: 4 * time.Hour, notAfter: n.Add(6 * time.Hour)}, 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), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
assert.Equals(t, cert.NotBefore, n.Add(3*time.Hour)) 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 { "ok/over-limit-with-backdate": func() test {
return test{ return test{
pld: profileLimitDuration{def: 24 * time.Hour, notAfter: n.Add(6 * time.Hour)}, 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), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
assert.Equals(t, cert.NotBefore, n.Add(-time.Minute)) 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 { "ok/under-limit-with-backdate": func() test {
return test{ return test{
pld: profileLimitDuration{def: 24 * time.Hour, notAfter: n.Add(30 * time.Hour)}, 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), cert: new(x509.Certificate),
valid: func(cert *x509.Certificate) { valid: func(cert *x509.Certificate) {
assert.Equals(t, cert.NotBefore, n.Add(-time.Minute)) 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 { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tt := run() tt := run()
prof := &x509util.Leaf{} if err := tt.pld.Modify(tt.cert, tt.so); err != nil {
prof.SetSubject(tt.cert)
if err := tt.pld.Option(tt.so)(prof); err != nil {
if assert.NotNil(t, tt.err) { if assert.NotNil(t, tt.err) {
assert.HasPrefix(t, err.Error(), tt.err.Error()) assert.HasPrefix(t, err.Error(), tt.err.Error())
} }
} else { } else {
if assert.Nil(t, tt.err) { if assert.Nil(t, tt.err) {
tt.valid(prof.Subject()) tt.valid(tt.cert)
} }
} }
}) })

View file

@ -30,20 +30,20 @@ type SSHCertModifier interface {
// to modify the SSH certificate. // to modify the SSH certificate.
type SSHCertOptionModifier interface { type SSHCertOptionModifier interface {
SignOption SignOption
Option(o SSHOptions) SSHCertModifier Option(o SignSSHOptions) SSHCertModifier
} }
// SSHCertValidator is the interface used to validate an SSH certificate. // SSHCertValidator is the interface used to validate an SSH certificate.
type SSHCertValidator interface { type SSHCertValidator interface {
SignOption SignOption
Valid(cert *ssh.Certificate, opts SSHOptions) error Valid(cert *ssh.Certificate, opts SignSSHOptions) error
} }
// SSHCertOptionsValidator is the interface used to validate the custom // SSHCertOptionsValidator is the interface used to validate the custom
// options used to modify the SSH certificate. // options used to modify the SSH certificate.
type SSHCertOptionsValidator interface { type SSHCertOptionsValidator interface {
SignOption SignOption
Valid(got SSHOptions) error Valid(got SignSSHOptions) error
} }
// sshModifierFunc is an adapter to allow the use of ordinary functions as SSH // 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) return f(cert)
} }
// SSHOptions contains the options that can be passed to the SignSSH method. // SignSSHOptions contains the options that can be passed to the SignSSH method.
type SSHOptions struct { type SignSSHOptions struct {
CertType string `json:"certType"` CertType string `json:"certType"`
KeyID string `json:"keyID"` KeyID string `json:"keyID"`
Principals []string `json:"principals"` Principals []string `json:"principals"`
@ -65,12 +65,12 @@ type SSHOptions struct {
} }
// Type returns the uint32 representation of the CertType. // Type returns the uint32 representation of the CertType.
func (o SSHOptions) Type() uint32 { func (o SignSSHOptions) Type() uint32 {
return sshCertTypeUInt32(o.CertType) return sshCertTypeUInt32(o.CertType)
} }
// Modify implements SSHCertModifier and sets the SSHOption in the ssh.Certificate. // 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 { switch o.CertType {
case "": // ignore case "": // ignore
case SSHUserCert: 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 // match compares two SSHOptions and return an error if they don't match. It
// ignores zero values. // 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 { 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) 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 // sshCertDefaultsModifier implements a SSHCertModifier that
// modifies the certificate with the given options if they are not set. // modifies the certificate with the given options if they are not set.
type sshCertDefaultsModifier SSHOptions type sshCertDefaultsModifier SignSSHOptions
// Modify implements the SSHCertModifier interface. // Modify implements the SSHCertModifier interface.
func (m sshCertDefaultsModifier) Modify(cert *ssh.Certificate) error { func (m sshCertDefaultsModifier) Modify(cert *ssh.Certificate) error {
@ -215,7 +215,7 @@ type sshDefaultDuration struct {
*Claimer *Claimer
} }
func (m *sshDefaultDuration) Option(o SSHOptions) SSHCertModifier { func (m *sshDefaultDuration) Option(o SignSSHOptions) SSHCertModifier {
return sshModifierFunc(func(cert *ssh.Certificate) error { return sshModifierFunc(func(cert *ssh.Certificate) error {
d, err := m.DefaultSSHCertDuration(cert.CertType) d, err := m.DefaultSSHCertDuration(cert.CertType)
if err != nil { if err != nil {
@ -248,7 +248,7 @@ type sshLimitDuration struct {
NotAfter time.Time NotAfter time.Time
} }
func (m *sshLimitDuration) Option(o SSHOptions) SSHCertModifier { func (m *sshLimitDuration) Option(o SignSSHOptions) SSHCertModifier {
if m.NotAfter.IsZero() { if m.NotAfter.IsZero() {
defaultDuration := &sshDefaultDuration{m.Claimer} defaultDuration := &sshDefaultDuration{m.Claimer}
return defaultDuration.Option(o) return defaultDuration.Option(o)
@ -297,12 +297,12 @@ func (m *sshLimitDuration) Option(o SSHOptions) SSHCertModifier {
// sshCertOptionsValidator validates the user SSHOptions with the ones // sshCertOptionsValidator validates the user SSHOptions with the ones
// usually present in the token. // usually present in the token.
type sshCertOptionsValidator SSHOptions type sshCertOptionsValidator SignSSHOptions
// Valid implements SSHCertOptionsValidator and returns nil if both // Valid implements SSHCertOptionsValidator and returns nil if both
// SSHOptions match. // SSHOptions match.
func (v sshCertOptionsValidator) Valid(got SSHOptions) error { func (v sshCertOptionsValidator) Valid(got SignSSHOptions) error {
want := SSHOptions(v) want := SignSSHOptions(v)
return want.match(got) return want.match(got)
} }
@ -310,7 +310,7 @@ type sshCertValidityValidator struct {
*Claimer *Claimer
} }
func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SSHOptions) error { func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOptions) error {
switch { switch {
case cert.ValidAfter == 0: case cert.ValidAfter == 0:
return errors.New("ssh certificate validAfter cannot be 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{} type sshCertDefaultValidator struct{}
// Valid returns an error if the given certificate does not contain the necessary fields. // 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 { switch {
case len(cert.Nonce) == 0: case len(cert.Nonce) == 0:
return errors.New("ssh certificate nonce cannot be empty") 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{} type sshDefaultPublicKeyValidator struct{}
// Valid checks that certificate request common name matches the one configured. // 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 { if cert.Key == nil {
return errors.New("ssh certificate key cannot be 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 type sshCertKeyIDValidator string
// Valid returns an error if the given certificate does not contain the necessary fields. // 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 { if string(v) != cert.KeyId {
return errors.Errorf("invalid ssh certificate KeyId; want %s, but got %s", string(v), cert.KeyId) return errors.Errorf("invalid ssh certificate KeyId; want %s, but got %s", string(v), cert.KeyId)
} }

View file

@ -28,7 +28,7 @@ func TestSSHOptions_Type(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
o := SSHOptions{ o := SignSSHOptions{
CertType: tt.fields.CertType, CertType: tt.fields.CertType,
} }
if got := o.Type(); got != tt.want { if got := o.Type(); got != tt.want {
@ -40,7 +40,7 @@ func TestSSHOptions_Type(t *testing.T) {
func TestSSHOptions_Modify(t *testing.T) { func TestSSHOptions_Modify(t *testing.T) {
type test struct { type test struct {
so *SSHOptions so *SignSSHOptions
cert *ssh.Certificate cert *ssh.Certificate
valid func(*ssh.Certificate) valid func(*ssh.Certificate)
err error err error
@ -48,21 +48,21 @@ func TestSSHOptions_Modify(t *testing.T) {
tests := map[string](func() test){ tests := map[string](func() test){
"fail/unexpected-cert-type": func() test { "fail/unexpected-cert-type": func() test {
return test{ return test{
so: &SSHOptions{CertType: "foo"}, so: &SignSSHOptions{CertType: "foo"},
cert: new(ssh.Certificate), cert: new(ssh.Certificate),
err: errors.Errorf("ssh certificate has an unknown type - foo"), err: errors.Errorf("ssh certificate has an unknown type - foo"),
} }
}, },
"fail/validAfter-greater-validBefore": func() test { "fail/validAfter-greater-validBefore": func() test {
return test{ return test{
so: &SSHOptions{CertType: "user"}, so: &SignSSHOptions{CertType: "user"},
cert: &ssh.Certificate{ValidAfter: uint64(15), ValidBefore: uint64(10)}, cert: &ssh.Certificate{ValidAfter: uint64(15), ValidBefore: uint64(10)},
err: errors.Errorf("ssh certificate valid after cannot be greater than valid before"), err: errors.Errorf("ssh certificate valid after cannot be greater than valid before"),
} }
}, },
"ok/user-cert": func() test { "ok/user-cert": func() test {
return test{ return test{
so: &SSHOptions{CertType: "user"}, so: &SignSSHOptions{CertType: "user"},
cert: new(ssh.Certificate), cert: new(ssh.Certificate),
valid: func(cert *ssh.Certificate) { valid: func(cert *ssh.Certificate) {
assert.Equals(t, cert.CertType, uint32(ssh.UserCert)) assert.Equals(t, cert.CertType, uint32(ssh.UserCert))
@ -71,7 +71,7 @@ func TestSSHOptions_Modify(t *testing.T) {
}, },
"ok/host-cert": func() test { "ok/host-cert": func() test {
return test{ return test{
so: &SSHOptions{CertType: "host"}, so: &SignSSHOptions{CertType: "host"},
cert: new(ssh.Certificate), cert: new(ssh.Certificate),
valid: func(cert *ssh.Certificate) { valid: func(cert *ssh.Certificate) {
assert.Equals(t, cert.CertType, uint32(ssh.HostCert)) assert.Equals(t, cert.CertType, uint32(ssh.HostCert))
@ -81,7 +81,7 @@ func TestSSHOptions_Modify(t *testing.T) {
"ok": func() test { "ok": func() test {
va := time.Now().Add(5 * time.Minute) va := time.Now().Add(5 * time.Minute)
vb := time.Now().Add(1 * time.Hour) 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)} ValidAfter: NewTimeDuration(va), ValidBefore: NewTimeDuration(vb)}
return test{ return test{
so: so, so: so,
@ -114,43 +114,43 @@ func TestSSHOptions_Modify(t *testing.T) {
func TestSSHOptions_Match(t *testing.T) { func TestSSHOptions_Match(t *testing.T) {
type test struct { type test struct {
so SSHOptions so SignSSHOptions
cmp SSHOptions cmp SignSSHOptions
err error err error
} }
tests := map[string](func() test){ tests := map[string](func() test){
"fail/cert-type": func() test { "fail/cert-type": func() test {
return test{ return test{
so: SSHOptions{CertType: "foo"}, so: SignSSHOptions{CertType: "foo"},
cmp: SSHOptions{CertType: "bar"}, cmp: SignSSHOptions{CertType: "bar"},
err: errors.Errorf("ssh certificate type does not match - got bar, want foo"), err: errors.Errorf("ssh certificate type does not match - got bar, want foo"),
} }
}, },
"fail/pricipals": func() test { "fail/pricipals": func() test {
return test{ return test{
so: SSHOptions{Principals: []string{"foo"}}, so: SignSSHOptions{Principals: []string{"foo"}},
cmp: SSHOptions{Principals: []string{"bar"}}, cmp: SignSSHOptions{Principals: []string{"bar"}},
err: errors.Errorf("ssh certificate principals does not match - got [bar], want [foo]"), err: errors.Errorf("ssh certificate principals does not match - got [bar], want [foo]"),
} }
}, },
"fail/validAfter": func() test { "fail/validAfter": func() test {
return test{ return test{
so: SSHOptions{ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute))}, so: SignSSHOptions{ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute))},
cmp: SSHOptions{ValidAfter: NewTimeDuration(time.Now().Add(5 * time.Minute))}, cmp: SignSSHOptions{ValidAfter: NewTimeDuration(time.Now().Add(5 * time.Minute))},
err: errors.Errorf("ssh certificate valid after does not match"), err: errors.Errorf("ssh certificate valid after does not match"),
} }
}, },
"fail/validBefore": func() test { "fail/validBefore": func() test {
return test{ return test{
so: SSHOptions{ValidBefore: NewTimeDuration(time.Now().Add(1 * time.Minute))}, so: SignSSHOptions{ValidBefore: NewTimeDuration(time.Now().Add(1 * time.Minute))},
cmp: SSHOptions{ValidBefore: NewTimeDuration(time.Now().Add(5 * time.Minute))}, cmp: SignSSHOptions{ValidBefore: NewTimeDuration(time.Now().Add(5 * time.Minute))},
err: errors.Errorf("ssh certificate valid before does not match"), err: errors.Errorf("ssh certificate valid before does not match"),
} }
}, },
"ok/original-empty": func() test { "ok/original-empty": func() test {
return test{ return test{
so: SSHOptions{}, so: SignSSHOptions{},
cmp: SSHOptions{ cmp: SignSSHOptions{
CertType: "foo", CertType: "foo",
Principals: []string{"foo"}, Principals: []string{"foo"},
ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute)), ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute)),
@ -160,8 +160,8 @@ func TestSSHOptions_Match(t *testing.T) {
}, },
"ok/cmp-empty": func() test { "ok/cmp-empty": func() test {
return test{ return test{
cmp: SSHOptions{}, cmp: SignSSHOptions{},
so: SSHOptions{ so: SignSSHOptions{
CertType: "foo", CertType: "foo",
Principals: []string{"foo"}, Principals: []string{"foo"},
ValidAfter: NewTimeDuration(time.Now().Add(1 * time.Minute)), 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)) va := NewTimeDuration(n.Add(1 * time.Minute))
vb := NewTimeDuration(n.Add(5 * time.Minute)) vb := NewTimeDuration(n.Add(5 * time.Minute))
return test{ return test{
cmp: SSHOptions{ cmp: SignSSHOptions{
CertType: "foo", CertType: "foo",
Principals: []string{"foo"}, Principals: []string{"foo"},
ValidAfter: va, ValidAfter: va,
ValidBefore: vb, ValidBefore: vb,
}, },
so: SSHOptions{ so: SignSSHOptions{
CertType: "foo", CertType: "foo",
Principals: []string{"foo"}, Principals: []string{"foo"},
ValidAfter: va, ValidAfter: va,
@ -330,7 +330,7 @@ func Test_sshCertDefaultsModifier_Modify(t *testing.T) {
n := time.Now() n := time.Now()
va := NewTimeDuration(n.Add(1 * time.Minute)) va := NewTimeDuration(n.Add(1 * time.Minute))
vb := NewTimeDuration(n.Add(5 * time.Minute)) vb := NewTimeDuration(n.Add(5 * time.Minute))
so := SSHOptions{ so := SignSSHOptions{
Principals: []string{"foo", "bar"}, Principals: []string{"foo", "bar"},
CertType: "host", CertType: "host",
ValidAfter: va, ValidAfter: va,
@ -349,7 +349,7 @@ func Test_sshCertDefaultsModifier_Modify(t *testing.T) {
}, },
"ok/no-changes": func() test { "ok/no-changes": func() test {
n := time.Now() n := time.Now()
so := SSHOptions{ so := SignSSHOptions{
Principals: []string{"foo", "bar"}, Principals: []string{"foo", "bar"},
CertType: "host", CertType: "host",
ValidAfter: NewTimeDuration(n.Add(15 * time.Minute)), ValidAfter: NewTimeDuration(n.Add(15 * time.Minute)),
@ -659,7 +659,7 @@ func Test_sshCertDefaultValidator_Valid(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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) { if assert.NotNil(t, tt.err) {
assert.HasPrefix(t, err.Error(), tt.err.Error()) assert.HasPrefix(t, err.Error(), tt.err.Error())
} }
@ -678,31 +678,31 @@ func Test_sshCertValidityValidator(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
cert *ssh.Certificate cert *ssh.Certificate
opts SSHOptions opts SignSSHOptions
err error err error
}{ }{
{ {
"fail/validAfter-0", "fail/validAfter-0",
&ssh.Certificate{CertType: ssh.UserCert}, &ssh.Certificate{CertType: ssh.UserCert},
SSHOptions{}, SignSSHOptions{},
errors.New("ssh certificate validAfter cannot be 0"), errors.New("ssh certificate validAfter cannot be 0"),
}, },
{ {
"fail/validBefore-in-past", "fail/validBefore-in-past",
&ssh.Certificate{CertType: ssh.UserCert, ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(-time.Minute).Unix())}, &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"), errors.New("ssh certificate validBefore cannot be in the past"),
}, },
{ {
"fail/validBefore-before-validAfter", "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())}, &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"), errors.New("ssh certificate validBefore cannot be before validAfter"),
}, },
{ {
"fail/cert-type-not-set", "fail/cert-type-not-set",
&ssh.Certificate{ValidAfter: uint64(now().Unix()), ValidBefore: uint64(now().Add(10 * time.Minute).Unix())}, &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"), errors.New("ssh certificate type has not been set"),
}, },
{ {
@ -712,7 +712,7 @@ func Test_sshCertValidityValidator(t *testing.T) {
ValidAfter: uint64(now().Unix()), ValidAfter: uint64(now().Unix()),
ValidBefore: uint64(now().Add(10 * time.Minute).Unix()), ValidBefore: uint64(now().Add(10 * time.Minute).Unix()),
}, },
SSHOptions{}, SignSSHOptions{},
errors.New("unknown ssh certificate type 3"), errors.New("unknown ssh certificate type 3"),
}, },
{ {
@ -722,7 +722,7 @@ func Test_sshCertValidityValidator(t *testing.T) {
ValidAfter: uint64(n.Unix()), ValidAfter: uint64(n.Unix()),
ValidBefore: uint64(n.Add(4 * time.Minute).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"), 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()), ValidAfter: uint64(n.Unix()),
ValidBefore: uint64(n.Add(5 * time.Minute).Unix()), ValidBefore: uint64(n.Add(5 * time.Minute).Unix()),
}, },
SSHOptions{Backdate: time.Second}, SignSSHOptions{Backdate: time.Second},
nil, nil,
}, },
{ {
@ -742,7 +742,7 @@ func Test_sshCertValidityValidator(t *testing.T) {
ValidAfter: uint64(n.Unix()), ValidAfter: uint64(n.Unix()),
ValidBefore: uint64(n.Add(48 * time.Hour).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"), 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()), ValidAfter: uint64(n.Unix()),
ValidBefore: uint64(n.Add(24*time.Hour + time.Second).Unix()), ValidBefore: uint64(n.Add(24*time.Hour + time.Second).Unix()),
}, },
SSHOptions{Backdate: time.Second}, SignSSHOptions{Backdate: time.Second},
nil, nil,
}, },
{ {
@ -762,7 +762,7 @@ func Test_sshCertValidityValidator(t *testing.T) {
ValidAfter: uint64(now().Unix()), ValidAfter: uint64(now().Unix()),
ValidBefore: uint64(now().Add(8 * time.Hour).Unix()), ValidBefore: uint64(now().Add(8 * time.Hour).Unix()),
}, },
SSHOptions{Backdate: time.Second}, SignSSHOptions{Backdate: time.Second},
nil, nil,
}, },
} }
@ -908,7 +908,7 @@ func Test_sshValidityModifier(t *testing.T) {
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tt := run() 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) { if assert.NotNil(t, tt.err) {
assert.HasPrefix(t, err.Error(), tt.err.Error()) assert.HasPrefix(t, err.Error(), tt.err.Error())
} }
@ -962,7 +962,7 @@ func Test_sshDefaultDuration_Option(t *testing.T) {
Claimer *Claimer Claimer *Claimer
} }
type args struct { type args struct {
o SSHOptions o SignSSHOptions
cert *ssh.Certificate cert *ssh.Certificate
} }
tests := []struct { tests := []struct {
@ -972,26 +972,26 @@ func Test_sshDefaultDuration_Option(t *testing.T) {
want *ssh.Certificate want *ssh.Certificate
wantErr bool 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}, &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}, &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}, &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}, &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}, &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}, &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}, &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}, &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}, &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 zero", fields{newClaimer(nil)}, args{SignSSHOptions{}, &ssh.Certificate{}}, &ssh.Certificate{}, true},
{"fail type", fields{newClaimer(nil)}, args{SSHOptions{}, &ssh.Certificate{CertType: 3}}, &ssh.Certificate{CertType: 3}, true}, {"fail type", fields{newClaimer(nil)}, args{SignSSHOptions{}, &ssh.Certificate{CertType: 3}}, &ssh.Certificate{CertType: 3}, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -1015,7 +1015,7 @@ func Test_sshLimitDuration_Option(t *testing.T) {
NotAfter time.Time NotAfter time.Time
} }
type args struct { type args struct {
o SSHOptions o SignSSHOptions
} }
tests := []struct { tests := []struct {
name string name string

View file

@ -10,7 +10,7 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
func validateSSHCertificate(cert *ssh.Certificate, opts *SSHOptions) error { func validateSSHCertificate(cert *ssh.Certificate, opts *SignSSHOptions) error {
switch { switch {
case cert == nil: case cert == nil:
return fmt.Errorf("certificate is 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) pub, err := ssh.NewPublicKey(key)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -0,0 +1 @@
{{ toJson .Insecure.CR }}

View file

@ -825,20 +825,20 @@ func generateK8sSAToken(jwk *jose.JSONWebKey, claims *k8sSAPayload, tokOpts ...t
} }
func generateSimpleSSHUserToken(iss, aud string, jwk *jose.JSONWebKey) (string, error) { 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", CertType: "user",
Principals: []string{"name"}, Principals: []string{"name"},
}, jwk) }, jwk)
} }
func generateSimpleSSHHostToken(iss, aud string, jwk *jose.JSONWebKey) (string, error) { 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", CertType: "host",
Principals: []string{"smallstep.com"}, Principals: []string{"smallstep.com"},
}, jwk) }, 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( sig, err := jose.NewSigner(
jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID), new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID),

View file

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/jose" "github.com/smallstep/cli/jose"
"go.step.sm/crypto/x509util"
) )
// x5cPayload extends jwt.Claims with step attributes. // x5cPayload extends jwt.Claims with step attributes.
@ -24,10 +25,11 @@ type x5cPayload struct {
// signature requests. // signature requests.
type X5C struct { type X5C struct {
*base *base
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Roots []byte `json:"roots"` Roots []byte `json:"roots"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
Options *Options `json:"options,omitempty"`
claimer *Claimer claimer *Claimer
audiences Audiences audiences Audiences
rootPool *x509.CertPool rootPool *x509.CertPool
@ -193,7 +195,19 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
claims.SANs = []string{claims.Subject} 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{ return []SignOption{
templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeX5C, p.Name, ""), newProvisionerExtensionOption(TypeX5C, p.Name, ""),
profileLimitDuration{p.claimer.DefaultTLSCertDuration(), profileLimitDuration{p.claimer.DefaultTLSCertDuration(),

View file

@ -463,9 +463,10 @@ func TestX5C_AuthorizeSign(t *testing.T) {
} else { } else {
if assert.Nil(t, tc.err) { if assert.Nil(t, tc.err) {
if assert.NotNil(t, opts) { if assert.NotNil(t, opts) {
assert.Equals(t, len(opts), 6) assert.Equals(t, len(opts), 7)
for _, o := range opts { for _, o := range opts {
switch v := o.(type) { switch v := o.(type) {
case certificateOptionsFunc:
case *provisionerExtensionOption: case *provisionerExtensionOption:
assert.Equals(t, v.Type, int(TypeX5C)) assert.Equals(t, v.Type, int(TypeX5C))
assert.Equals(t, v.Name, tc.p.GetName()) 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)), Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
Audience: []string{testAudiences.SSHSign[0]}, Audience: []string{testAudiences.SSHSign[0]},
}, },
Step: &stepPayload{SSH: &SSHOptions{ Step: &stepPayload{SSH: &SignSSHOptions{
CertType: SSHHostCert, CertType: SSHHostCert,
Principals: []string{"max", "mariano", "alan"}, Principals: []string{"max", "mariano", "alan"},
ValidAfter: TimeDuration{d: 5 * time.Minute}, ValidAfter: TimeDuration{d: 5 * time.Minute},
@ -727,7 +728,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)), Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
Audience: []string{testAudiences.SSHSign[0]}, Audience: []string{testAudiences.SSHSign[0]},
}, },
Step: &stepPayload{SSH: &SSHOptions{}}, Step: &stepPayload{SSH: &SignSSHOptions{}},
} }
tok, err := generateX5CSSHToken(x5cJWK, claims, withX5CHdr(x5cCerts)) tok, err := generateX5CSSHToken(x5cJWK, claims, withX5CHdr(x5cCerts))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -758,7 +759,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
case sshCertOptionsValidator: case sshCertOptionsValidator:
tc.claims.Step.SSH.ValidAfter.t = time.Time{} tc.claims.Step.SSH.ValidAfter.t = time.Time{}
tc.claims.Step.SSH.ValidBefore.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: case sshCertKeyIDModifier:
assert.Equals(t, string(v), "foo") assert.Equals(t, string(v), "foo")
case sshCertTypeModifier: case sshCertTypeModifier:
@ -770,7 +771,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
case sshCertValidBeforeModifier: case sshCertValidBeforeModifier:
assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix()) assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix())
case sshCertDefaultsModifier: case sshCertDefaultsModifier:
assert.Equals(t, SSHOptions(v), SSHOptions{CertType: SSHUserCert}) assert.Equals(t, SignSSHOptions(v), SignSSHOptions{CertType: SSHUserCert})
case *sshLimitDuration: case *sshLimitDuration:
assert.Equals(t, v.Claimer, tc.p.claimer) assert.Equals(t, v.Claimer, tc.p.claimer)
assert.Equals(t, v.NotAfter, x5cCerts[0].NotAfter) assert.Equals(t, v.NotAfter, x5cCerts[0].NotAfter)

View file

@ -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. // 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 mods []provisioner.SSHCertModifier
var validators []provisioner.SSHCertValidator var validators []provisioner.SSHCertValidator
@ -453,7 +453,7 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
// Apply validators from provisioner. // Apply validators from provisioner.
for _, v := range validators { 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") return nil, errs.Wrap(http.StatusForbidden, err, "rekeySSH")
} }
} }

View file

@ -62,7 +62,7 @@ func (m sshTestCertModifier) Modify(cert *ssh.Certificate) error {
type sshTestCertValidator string 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 == "" { if v == "" {
return nil return nil
} }
@ -71,7 +71,7 @@ func (v sshTestCertValidator) Valid(crt *ssh.Certificate, opts provisioner.SSHOp
type sshTestOptionsValidator string type sshTestOptionsValidator string
func (v sshTestOptionsValidator) Valid(opts provisioner.SSHOptions) error { func (v sshTestOptionsValidator) Valid(opts provisioner.SignSSHOptions) error {
if v == "" { if v == "" {
return nil return nil
} }
@ -80,7 +80,7 @@ func (v sshTestOptionsValidator) Valid(opts provisioner.SSHOptions) error {
type sshTestOptionsModifier string 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)) return sshTestCertModifier(string(m))
} }
@ -109,7 +109,7 @@ func TestAuthority_SignSSH(t *testing.T) {
} }
type args struct { type args struct {
key ssh.PublicKey key ssh.PublicKey
opts provisioner.SSHOptions opts provisioner.SignSSHOptions
signOpts []provisioner.SignOption signOpts []provisioner.SignOption
} }
type want struct { type want struct {
@ -125,27 +125,27 @@ func TestAuthority_SignSSH(t *testing.T) {
want want want want
wantErr bool wantErr bool
}{ }{
{"ok-user", fields{signer, signer}, args{pub, provisioner.SSHOptions{}, []provisioner.SignOption{userOptions}}, want{CertType: ssh.UserCert}, false}, {"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.SSHOptions{}, []provisioner.SignOption{hostOptions}}, want{CertType: ssh.HostCert}, 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.SSHOptions{CertType: "user"}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert}, 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.SSHOptions{CertType: "host"}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert}, 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.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.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.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-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.SSHOptions{CertType: "user", ValidAfter: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{}}, want{CertType: ssh.UserCert, ValidAfter: uint64(now.Unix())}, 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.SSHOptions{CertType: "host", ValidBefore: provisioner.NewTimeDuration(now)}, []provisioner.SignOption{}}, want{CertType: ssh.HostCert, ValidBefore: 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.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("")}}, want{CertType: ssh.UserCert}, 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.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("")}}, 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.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("")}}, 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.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("")}}, 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.SSHOptions{CertType: "foo"}, []provisioner.SignOption{}}, want{}, true}, {"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.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertValidator("an error")}}, 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.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestCertModifier("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.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsValidator("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.SSHOptions{}, []provisioner.SignOption{userOptions, sshTestOptionsModifier("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.SSHOptions{}, []provisioner.SignOption{userOptions, "wrong type"}}, 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.SSHOptions{CertType: "user"}, []provisioner.SignOption{}}, 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.SSHOptions{CertType: "host"}, []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.SSHOptions{}, []provisioner.SignOption{sshTestModifier{CertType: 0}}}, want{}, true}, {"fail-bad-type", fields{signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{sshTestModifier{CertType: 0}}}, want{}, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -18,8 +18,9 @@ import (
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/tlsutil" "github.com/smallstep/cli/crypto/tlsutil"
"github.com/smallstep/cli/crypto/x509util" x509legacy "github.com/smallstep/cli/crypto/x509util"
"github.com/smallstep/cli/jose" "github.com/smallstep/cli/jose"
"go.step.sm/crypto/x509util"
) )
// GetTLSOptions returns the tls options configured. // 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 oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14} var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14}
func withDefaultASN1DN(def *x509util.ASN1DN) x509util.WithOption { func withDefaultASN1DN(def *x509legacy.ASN1DN) provisioner.CertificateModifierFunc {
return func(p x509util.Profile) error { return func(crt *x509.Certificate, opts provisioner.SignOptions) error {
if def == nil { if def == nil {
return errors.New("default ASN1DN template cannot be nil") return errors.New("default ASN1DN template cannot be nil")
} }
crt := p.Subject()
if len(crt.Subject.Country) == 0 && def.Country != "" { if len(crt.Subject.Country) == 0 && def.Country != "" {
crt.Subject.Country = append(crt.Subject.Country, 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. // 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 ( var (
opts = []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} certOptions []x509util.Option
mods = []x509util.WithOption{withDefaultASN1DN(a.config.AuthorityConfig.Template)} certValidators []provisioner.CertificateValidator
certValidators = []provisioner.CertificateValidator{} certModifiers []provisioner.CertificateModifier
forcedModifiers = []provisioner.CertificateEnforcer{provisioner.ExtraExtsEnforcer{}} 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 // Set backdate with the configured value
signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration
for _, op := range extraOpts { for _, op := range extraOpts {
switch k := op.(type) { switch k := op.(type) {
case provisioner.CertificateValidator: // Adds new options to NewCertificate
certValidators = append(certValidators, k) case provisioner.CertificateOptions:
certOptions = append(certOptions, k.Options(signOpts)...)
// Validate tie given certificate request.
case provisioner.CertificateRequestValidator: case provisioner.CertificateRequestValidator:
if err := k.Valid(csr); err != nil { if err := k.Valid(csr); err != nil {
return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) 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: case provisioner.CertificateEnforcer:
forcedModifiers = append(forcedModifiers, k) certEnforcers = append(certEnforcers, k)
default: default:
return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...) return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...)
} }
} }
if err := csr.CheckSignature(); err != nil { cert, err := x509util.NewCertificate(csr, certOptions...)
return nil, errs.Wrap(http.StatusBadRequest, err, "authority.Sign; invalid certificate request", opts...)
}
leaf, err := x509util.NewLeafProfileWithCSR(csr, a.x509Issuer, a.x509Signer, mods...)
if err != nil { 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...) 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 { 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...) return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...)
} }
} }
// Certificate modifiers after validation // Certificate modifiers after validation
for _, m := range forcedModifiers { for _, m := range certEnforcers {
if err := m.Enforce(leaf.Subject()); err != nil { if err := m.Enforce(leaf); err != nil {
return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) 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 { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Sign; error creating new leaf certificate", opts...) "authority.Sign; error creating 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...)
} }
if err = a.db.StoreCertificate(serverCert); err != nil { 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) 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 { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...) 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. // GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server.
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
profile, err := x509util.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer, profile, err := x509legacy.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer,
x509util.WithHosts(strings.Join(a.config.DNSNames, ","))) x509legacy.WithHosts(strings.Join(a.config.DNSNames, ",")))
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate")
} }

View file

@ -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 { type basicConstraints struct {
IsCA bool `asn1:"optional"` IsCA bool `asn1:"optional"`
MaxPathLen int `asn1:"optional,default:-1"` MaxPathLen int `asn1:"optional,default:-1"`
@ -116,9 +132,10 @@ func TestAuthority_Sign(t *testing.T) {
} }
nb := time.Now() nb := time.Now()
signOpts := provisioner.Options{ signOpts := provisioner.SignOptions{
NotBefore: provisioner.NewTimeDuration(nb), NotBefore: provisioner.NewTimeDuration(nb),
NotAfter: provisioner.NewTimeDuration(nb.Add(time.Minute * 5)), NotAfter: provisioner.NewTimeDuration(nb.Add(time.Minute * 5)),
Backdate: 1 * time.Minute,
} }
// Create a token to get test extra opts. // Create a token to get test extra opts.
@ -134,7 +151,7 @@ func TestAuthority_Sign(t *testing.T) {
type signTest struct { type signTest struct {
auth *Authority auth *Authority
csr *x509.CertificateRequest csr *x509.CertificateRequest
signOpts provisioner.Options signOpts provisioner.SignOptions
extraOpts []provisioner.SignOption extraOpts []provisioner.SignOption
notBefore time.Time notBefore time.Time
notAfter time.Time notAfter time.Time
@ -176,7 +193,7 @@ func TestAuthority_Sign(t *testing.T) {
extraOpts: extraOpts, extraOpts: extraOpts,
signOpts: signOpts, signOpts: signOpts,
err: errors.New("authority.Sign: default ASN1DN template cannot be nil"), 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 { "fail create cert": func(t *testing.T) *signTest {
@ -188,13 +205,13 @@ func TestAuthority_Sign(t *testing.T) {
csr: csr, csr: csr,
extraOpts: extraOpts, extraOpts: extraOpts,
signOpts: signOpts, signOpts: signOpts,
err: errors.New("authority.Sign; error creating new leaf certificate"), err: errors.New("authority.Sign; error creating certificate"),
code: http.StatusInternalServerError, code: http.StatusInternalServerError,
} }
}, },
"fail provisioner duration claim": func(t *testing.T) *signTest { "fail provisioner duration claim": func(t *testing.T) *signTest {
csr := getCSR(t, priv) csr := getCSR(t, priv)
_signOpts := provisioner.Options{ _signOpts := provisioner.SignOptions{
NotBefore: provisioner.NewTimeDuration(nb), NotBefore: provisioner.NewTimeDuration(nb),
NotAfter: provisioner.NewTimeDuration(nb.Add(time.Hour * 25)), NotAfter: provisioner.NewTimeDuration(nb.Add(time.Hour * 25)),
} }
@ -263,6 +280,33 @@ ZYtQ9Ot36qc=
code: http.StatusInternalServerError, 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 { "ok": func(t *testing.T) *signTest {
csr := getCSR(t, priv) csr := getCSR(t, priv)
_a := testAuthority(t) _a := testAuthority(t)
@ -313,6 +357,39 @@ ZYtQ9Ot36qc=
notAfter: now.Add(365 * 24 * time.Hour).Truncate(time.Second), 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 { for name, genTestCase := range tests {
@ -357,9 +434,9 @@ ZYtQ9Ot36qc=
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"}) assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"})
kid, err := generateSubjectKeyID(pub) subjectKeyID, err := generateSubjectKeyID(pub)
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, kid) assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
assert.Equals(t, leaf.AuthorityKeyId, a.x509Issuer.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) { func TestAuthority_Renew(t *testing.T) {
pub, _, err := keys.GenerateDefaultKeyPair() pub, _, err := keys.GenerateDefaultKeyPair()
assert.FatalError(t, err) assert.FatalError(t, err)
@ -435,17 +487,24 @@ func TestAuthority_Renew(t *testing.T) {
CommonName: "renew", 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() now := time.Now().UTC()
nb1 := now.Add(-time.Minute * 7) nb1 := now.Add(-time.Minute * 7)
na1 := now na1 := now
so := &provisioner.Options{ so := &provisioner.SignOptions{
NotBefore: provisioner.NewTimeDuration(nb1), NotBefore: provisioner.NewTimeDuration(nb1),
NotAfter: provisioner.NewTimeDuration(na1), NotAfter: provisioner.NewTimeDuration(na1),
} }
leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer, leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer,
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), 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"), x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID)) withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -456,7 +515,7 @@ func TestAuthority_Renew(t *testing.T) {
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer, leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer,
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), 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"), x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID), 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}) []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"})
// Test Public Key and SubjectKeyId subjectKeyID, err := generateSubjectKeyID(pub)
assert.Equals(t, leaf.PublicKey, cert.PublicKey)
kid, err := generateSubjectKeyID(cert.PublicKey)
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, kid) assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
assert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId)
// We did not change the intermediate before renewing. // We did not change the intermediate before renewing.
if a.x509Issuer.SerialNumber == tc.auth.x509Issuer.SerialNumber { if a.x509Issuer.SerialNumber == tc.auth.x509Issuer.SerialNumber {
@ -658,17 +714,24 @@ func TestAuthority_Rekey(t *testing.T) {
CommonName: "renew", 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() now := time.Now().UTC()
nb1 := now.Add(-time.Minute * 7) nb1 := now.Add(-time.Minute * 7)
na1 := now na1 := now
so := &provisioner.Options{ so := &provisioner.SignOptions{
NotBefore: provisioner.NewTimeDuration(nb1), NotBefore: provisioner.NewTimeDuration(nb1),
NotAfter: provisioner.NewTimeDuration(na1), NotAfter: provisioner.NewTimeDuration(na1),
} }
leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer, leaf, err := x509util.NewLeafProfile("renew", a.x509Issuer, a.x509Signer,
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), 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"), x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID)) withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID))
assert.FatalError(t, err) assert.FatalError(t, err)
@ -679,7 +742,7 @@ func TestAuthority_Rekey(t *testing.T) {
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer, leafNoRenew, err := x509util.NewLeafProfile("norenew", a.x509Issuer, a.x509Signer,
x509util.WithNotBeforeAfterDuration(so.NotBefore.Time(), so.NotAfter.Time(), 0), 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"), x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].(*provisioner.JWK).Key.KeyID), 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) assert.Equals(t, leaf.PublicKey, expectedPK)
kid, err := generateSubjectKeyID(expectedPK) subjectKeyID, err := generateSubjectKeyID(expectedPK)
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, kid) assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
if tc.pk == nil { if tc.pk == nil {
assert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId) assert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId)
} }

View file

@ -34,31 +34,6 @@ import (
"gopkg.in/square/go-jose.v2/jwt" "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 { type ClosingBuffer struct {
*bytes.Buffer *bytes.Buffer
} }
@ -79,6 +54,22 @@ func getCSR(priv interface{}) (*x509.CertificateRequest, error) {
return x509.ParseCertificateRequest(csrBytes) 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) { func TestMain(m *testing.M) {
DisableIdentity = true DisableIdentity = true
os.Exit(m.Run()) os.Exit(m.Run())
@ -326,9 +317,9 @@ ZEp7knvU2psWRw==
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"}) assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"})
kid, err := generateSubjectKeyID(pub) subjectKeyID, err := generateSubjectKeyID(pub)
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, kid) assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
assert.Equals(t, leaf.AuthorityKeyId, intermediateIdentity.Crt.SubjectKeyId) assert.Equals(t, leaf.AuthorityKeyId, intermediateIdentity.Crt.SubjectKeyId)
@ -667,10 +658,9 @@ func TestCARenew(t *testing.T) {
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
assert.Equals(t, leaf.DNSNames, []string{"funk"}) assert.Equals(t, leaf.DNSNames, []string{"funk"})
kid, err := generateSubjectKeyID(pub) subjectKeyID, err := generateSubjectKeyID(pub)
assert.FatalError(t, err) assert.FatalError(t, err)
assert.Equals(t, leaf.SubjectKeyId, kid) assert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
assert.Equals(t, leaf.AuthorityKeyId, intermediateIdentity.Crt.SubjectKeyId) assert.Equals(t, leaf.AuthorityKeyId, intermediateIdentity.Crt.SubjectKeyId)
realIntermediate, err := x509.ParseCertificate(intermediateIdentity.Crt.Raw) realIntermediate, err := x509.ParseCertificate(intermediateIdentity.Crt.Raw)

View file

@ -133,7 +133,7 @@ func (p *Provisioner) SSHToken(certType, keyID string, principals []string) (str
token.WithIssuer(p.name), token.WithIssuer(p.name),
token.WithAudience(p.sshAudience), token.WithAudience(p.sshAudience),
token.WithValidity(notBefore, notAfter), token.WithValidity(notBefore, notAfter),
token.WithSSH(provisioner.SSHOptions{ token.WithSSH(provisioner.SignSSHOptions{
CertType: certType, CertType: certType,
Principals: principals, Principals: principals,
KeyID: keyID, KeyID: keyID,

View file

@ -56,7 +56,7 @@ func (c *Client) getClientTLSConfig(ctx context.Context, sign *api.SignResponse,
return nil, nil, err return nil, nil, err
} }
// Use mutable tls.Config on renew // 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) // tr.DialTLSContext = c.buildDialTLSContext(tlsCtx)
renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk) renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk)
@ -108,7 +108,7 @@ func (c *Client) GetServerTLSConfig(ctx context.Context, sign *api.SignResponse,
return nil, err return nil, err
} }
// Use mutable tls.Config on renew // 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) // tr.DialTLSContext = c.buildDialTLSContext(tlsCtx)
renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk) renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk)

7
go.mod
View file

@ -4,13 +4,11 @@ go 1.13
require ( require (
cloud.google.com/go v0.51.0 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/aws/aws-sdk-go v1.30.29
github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/chi v4.0.2+incompatible
github.com/go-piv/piv-go v1.5.0 github.com/go-piv/piv-go v1.5.0
github.com/googleapis/gax-go/v2 v2.0.5 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/newrelic/go-agent v2.15.0+incompatible
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rs/xid v1.2.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/cli v0.14.7-rc.1.0.20200727165646-eb4e97335f2d
github.com/smallstep/nosql v0.3.0 github.com/smallstep/nosql v0.3.0
github.com/urfave/cli v1.22.2 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 golang.org/x/net v0.0.0-20200202094626-16171245cfb2
google.golang.org/api v0.15.0 google.golang.org/api v0.15.0
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb

62
go.sum
View file

@ -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 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 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk=
github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 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 h1:KSQz7Nb08/3VU9E4ns29dDxcczhOD1q7O1UfM4G3t3g=
github.com/Masterminds/sprig/v3 v3.0.0/go.mod h1:NEUY/Qq8Gdm2xgYA+NwJM6wmfdRV9xkh8h/Rld20R0U= 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/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 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 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/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 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= 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/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/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 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 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 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 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/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/certificates v0.14.5/go.mod h1:zzpB8wMz967gL8FmK6zvCNB4pDVwFDKjPg1diTVc1h8=
github.com/smallstep/certinfo v1.3.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU= 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.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 h1:bDnvzyEXzAMO5in8QHRrQCEUopiXjFVKHiR8c0m7Iww=
github.com/smallstep/cli v0.14.7-rc.1.0.20200727165646-eb4e97335f2d/go.mod h1:7aWHk7WwJMpEP4PYyav86FMpaI9vuA0uJRliUAqCwxg= 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= 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/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/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/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/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/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 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 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 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.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.1.2/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/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= 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.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= 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/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.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/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 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.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/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.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/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/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.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.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/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.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.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/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 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/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/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-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/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/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/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/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 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/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.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/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/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.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 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/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/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= 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/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/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/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/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/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= 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 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 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.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.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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 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 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 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.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.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/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.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 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/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/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.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= 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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-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-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-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-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-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-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-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-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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 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-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-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-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/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-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 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-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-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-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-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-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-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-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 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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-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-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-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/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-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/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-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-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-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-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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/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-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-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-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-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-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/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 h1:LwyF2AFISC9nVbS6MgzsaQNSUsRXI49GS+YQ5KX/QH0=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.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.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.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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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-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-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/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-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-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-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-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/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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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/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.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.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.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.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= 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-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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 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/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.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/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A= 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/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.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.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.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.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/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-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-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.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= 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-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/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/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/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-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-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= 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= 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= 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 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= sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4=