forked from TrueCloudLab/certificates
Add options to use custom renewal methods.
This commit is contained in:
parent
389815642d
commit
79349b4d7c
5 changed files with 93 additions and 25 deletions
|
@ -70,10 +70,12 @@ type Authority struct {
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
|
|
||||||
// Custom functions
|
// Custom functions
|
||||||
sshBastionFunc func(ctx context.Context, user, hostname string) (*config.Bastion, error)
|
sshBastionFunc func(ctx context.Context, user, hostname string) (*config.Bastion, error)
|
||||||
sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)
|
sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)
|
||||||
sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]config.Host, error)
|
sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]config.Host, error)
|
||||||
getIdentityFunc provisioner.GetIdentityFunc
|
getIdentityFunc provisioner.GetIdentityFunc
|
||||||
|
authorizeRenewFunc provisioner.AuthorizeRenewFunc
|
||||||
|
authorizeSSHRenewFunc provisioner.AuthorizeSSHRenewFunc
|
||||||
|
|
||||||
adminMutex sync.RWMutex
|
adminMutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
|
@ -1011,6 +1011,23 @@ func TestAuthority_authorizeSSHSign(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthority_authorizeSSHRenew(t *testing.T) {
|
func TestAuthority_authorizeSSHRenew(t *testing.T) {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
sshpop := func(a *Authority) (*ssh.Certificate, string) {
|
||||||
|
p, ok := a.provisioners.Load("sshpop/sshpop")
|
||||||
|
assert.Fatal(t, ok, "sshpop provisioner not found in test authority")
|
||||||
|
key, err := pemutil.Read("./testdata/secrets/ssh_host_ca_key")
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
signer, ok := key.(crypto.Signer)
|
||||||
|
assert.Fatal(t, ok, "could not cast ssh signing key to crypto signer")
|
||||||
|
sshSigner, err := ssh.NewSignerFromSigner(signer)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.HostCert}, sshSigner)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
token, err := generateToken("foo", p.GetName(), testAudiences.SSHRenew[0]+"#sshpop/sshpop", []string{"foo.smallstep.com"}, now, jwk, withSSHPOPFile(cert))
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
return cert, token
|
||||||
|
}
|
||||||
|
|
||||||
a := testAuthority(t)
|
a := testAuthority(t)
|
||||||
|
|
||||||
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
|
||||||
|
@ -1020,8 +1037,6 @@ func TestAuthority_authorizeSSHRenew(t *testing.T) {
|
||||||
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID))
|
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID))
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
now := time.Now().UTC()
|
|
||||||
|
|
||||||
validIssuer := "step-cli"
|
validIssuer := "step-cli"
|
||||||
|
|
||||||
type authorizeTest struct {
|
type authorizeTest struct {
|
||||||
|
@ -1058,27 +1073,34 @@ func TestAuthority_authorizeSSHRenew(t *testing.T) {
|
||||||
code: http.StatusUnauthorized,
|
code: http.StatusUnauthorized,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fail/WithAuthorizeSSHRenewFunc": func(t *testing.T) *authorizeTest {
|
||||||
|
aa := testAuthority(t, WithAuthorizeSSHRenewFunc(func(ctx context.Context, p *provisioner.Controller, cert *ssh.Certificate) error {
|
||||||
|
return errs.Forbidden("forbidden")
|
||||||
|
}))
|
||||||
|
_, token := sshpop(aa)
|
||||||
|
return &authorizeTest{
|
||||||
|
auth: aa,
|
||||||
|
token: token,
|
||||||
|
err: errors.New("authority.authorizeSSHRenew: forbidden"),
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
}
|
||||||
|
},
|
||||||
"ok": func(t *testing.T) *authorizeTest {
|
"ok": func(t *testing.T) *authorizeTest {
|
||||||
key, err := pemutil.Read("./testdata/secrets/ssh_host_ca_key")
|
cert, token := sshpop(a)
|
||||||
assert.FatalError(t, err)
|
|
||||||
signer, ok := key.(crypto.Signer)
|
|
||||||
assert.Fatal(t, ok, "could not cast ssh signing key to crypto signer")
|
|
||||||
sshSigner, err := ssh.NewSignerFromSigner(signer)
|
|
||||||
assert.FatalError(t, err)
|
|
||||||
|
|
||||||
cert, _jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.HostCert}, sshSigner)
|
|
||||||
assert.FatalError(t, err)
|
|
||||||
|
|
||||||
p, ok := a.provisioners.Load("sshpop/sshpop")
|
|
||||||
assert.Fatal(t, ok, "sshpop provisioner not found in test authority")
|
|
||||||
|
|
||||||
tok, err := generateToken("foo", p.GetName(), testAudiences.SSHRenew[0]+"#sshpop/sshpop",
|
|
||||||
[]string{"foo.smallstep.com"}, now, _jwk, withSSHPOPFile(cert))
|
|
||||||
assert.FatalError(t, err)
|
|
||||||
|
|
||||||
return &authorizeTest{
|
return &authorizeTest{
|
||||||
auth: a,
|
auth: a,
|
||||||
token: tok,
|
token: token,
|
||||||
|
cert: cert,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ok/WithAuthorizeSSHRenewFunc": func(t *testing.T) *authorizeTest {
|
||||||
|
aa := testAuthority(t, WithAuthorizeSSHRenewFunc(func(ctx context.Context, p *provisioner.Controller, cert *ssh.Certificate) error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
cert, token := sshpop(aa)
|
||||||
|
return &authorizeTest{
|
||||||
|
auth: aa,
|
||||||
|
token: token,
|
||||||
cert: cert,
|
cert: cert,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -92,6 +92,24 @@ func WithGetIdentityFunc(fn func(ctx context.Context, p provisioner.Interface, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAuthorizeRenewFunc sets a custom function that authorizes the renewal of
|
||||||
|
// an X.509 certificate.
|
||||||
|
func WithAuthorizeRenewFunc(fn func(ctx context.Context, p *provisioner.Controller, cert *x509.Certificate) error) Option {
|
||||||
|
return func(a *Authority) error {
|
||||||
|
a.authorizeRenewFunc = fn
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAuthorizeSSHRenewFunc sets a custom function that authorizes the renewal
|
||||||
|
// of a SSH certificate.
|
||||||
|
func WithAuthorizeSSHRenewFunc(fn func(ctx context.Context, p *provisioner.Controller, cert *ssh.Certificate) error) Option {
|
||||||
|
return func(a *Authority) error {
|
||||||
|
a.authorizeSSHRenewFunc = fn
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithSSHBastionFunc sets a custom function to get the bastion for a
|
// WithSSHBastionFunc sets a custom function to get the bastion for a
|
||||||
// given user-host pair.
|
// given user-host pair.
|
||||||
func WithSSHBastionFunc(fn func(ctx context.Context, user, host string) (*config.Bastion, error)) Option {
|
func WithSSHBastionFunc(fn func(ctx context.Context, user, host string) (*config.Bastion, error)) Option {
|
||||||
|
|
|
@ -108,7 +108,9 @@ func (a *Authority) generateProvisionerConfig(ctx context.Context) (provisioner.
|
||||||
UserKeys: sshKeys.UserKeys,
|
UserKeys: sshKeys.UserKeys,
|
||||||
HostKeys: sshKeys.HostKeys,
|
HostKeys: sshKeys.HostKeys,
|
||||||
},
|
},
|
||||||
GetIdentityFunc: a.getIdentityFunc,
|
GetIdentityFunc: a.getIdentityFunc,
|
||||||
|
AuthorizeRenewFunc: a.authorizeRenewFunc,
|
||||||
|
AuthorizeSSHRenewFunc: a.authorizeSSHRenewFunc,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -802,6 +802,19 @@ func TestAuthority_Renew(t *testing.T) {
|
||||||
code: http.StatusUnauthorized,
|
code: http.StatusUnauthorized,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
"fail/WithAuthorizeRenewFunc": func() (*renewTest, error) {
|
||||||
|
aa := testAuthority(t, WithAuthorizeRenewFunc(func(ctx context.Context, p *provisioner.Controller, cert *x509.Certificate) error {
|
||||||
|
return errs.Unauthorized("not authorized")
|
||||||
|
}))
|
||||||
|
aa.x509CAService = a.x509CAService
|
||||||
|
aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template
|
||||||
|
return &renewTest{
|
||||||
|
auth: aa,
|
||||||
|
cert: cert,
|
||||||
|
err: errors.New("authority.Rekey: authority.authorizeRenew: not authorized"),
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
"ok": func() (*renewTest, error) {
|
"ok": func() (*renewTest, error) {
|
||||||
return &renewTest{
|
return &renewTest{
|
||||||
auth: a,
|
auth: a,
|
||||||
|
@ -820,6 +833,17 @@ func TestAuthority_Renew(t *testing.T) {
|
||||||
cert: cert,
|
cert: cert,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
"ok/WithAuthorizeRenewFunc": func() (*renewTest, error) {
|
||||||
|
aa := testAuthority(t, WithAuthorizeRenewFunc(func(ctx context.Context, p *provisioner.Controller, cert *x509.Certificate) error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
aa.x509CAService = a.x509CAService
|
||||||
|
aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template
|
||||||
|
return &renewTest{
|
||||||
|
auth: aa,
|
||||||
|
cert: cert,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, genTestCase := range tests {
|
for name, genTestCase := range tests {
|
||||||
|
|
Loading…
Reference in a new issue