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