Add provisioner option to disable renewal.
Fixes smallstep/ca-component#108
This commit is contained in:
parent
c74fcd57a7
commit
d6cad2a7f3
6 changed files with 111 additions and 7 deletions
|
@ -15,6 +15,7 @@ func testAuthority(t *testing.T) *Authority {
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk")
|
clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk")
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
disableRenewal := true
|
||||||
p := []*Provisioner{
|
p := []*Provisioner{
|
||||||
{
|
{
|
||||||
Name: "Max",
|
Name: "Max",
|
||||||
|
@ -26,6 +27,14 @@ func testAuthority(t *testing.T) *Authority {
|
||||||
Type: "JWK",
|
Type: "JWK",
|
||||||
Key: clijwk,
|
Key: clijwk,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "dev",
|
||||||
|
Type: "JWK",
|
||||||
|
Key: maxjwk,
|
||||||
|
Claims: &ProvisionerClaims{
|
||||||
|
DisableRenewal: &disableRenewal,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
c := &Config{
|
c := &Config{
|
||||||
Address: "127.0.0.1:443",
|
Address: "127.0.0.1:443",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package authority
|
package authority
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/asn1"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -117,3 +119,54 @@ func (a *Authority) Authorize(ott string) ([]interface{}, error) {
|
||||||
|
|
||||||
return signOps, nil
|
return signOps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// authorizeRenewal tries to locate the step provisioner extension, and checks
|
||||||
|
// if for the configured provisioner, the renewal is enabled or not. If the
|
||||||
|
// extra extension cannot be found, authorize the renewal by default.
|
||||||
|
//
|
||||||
|
// TODO(mariano): should we authorize by default?
|
||||||
|
func (a *Authority) authorizeRenewal(crt *x509.Certificate) error {
|
||||||
|
errContext := map[string]interface{}{"serialNumber": crt.SerialNumber.String()}
|
||||||
|
for _, e := range crt.Extensions {
|
||||||
|
if e.Id.Equal(stepOIDProvisioner) {
|
||||||
|
var provisioner stepProvisionerASN1
|
||||||
|
if _, err := asn1.Unmarshal(e.Value, &provisioner); err != nil {
|
||||||
|
return &apiError{
|
||||||
|
err: errors.Wrap(err, "error decoding step provisioner extension"),
|
||||||
|
code: http.StatusInternalServerError,
|
||||||
|
context: errContext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for the provisioner, if it cannot be found, renewal will not
|
||||||
|
// be authorized.
|
||||||
|
pid := string(provisioner.Name) + ":" + string(provisioner.CredentialID)
|
||||||
|
val, ok := a.provisionerIDIndex.Load(pid)
|
||||||
|
if !ok {
|
||||||
|
return &apiError{
|
||||||
|
err: errors.Errorf("not found: provisioner %s", pid),
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
context: errContext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p, ok := val.(*Provisioner)
|
||||||
|
if !ok {
|
||||||
|
return &apiError{
|
||||||
|
err: errors.Errorf("invalid type: provisioner %s, type %T", pid, val),
|
||||||
|
code: http.StatusInternalServerError,
|
||||||
|
context: errContext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Claims.IsDisableRenewal() {
|
||||||
|
return &apiError{
|
||||||
|
err: errors.Errorf("renew disabled: provisioner %s", pid),
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
context: errContext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -23,10 +23,12 @@ var (
|
||||||
MaxVersion: 1.2,
|
MaxVersion: 1.2,
|
||||||
Renegotiation: false,
|
Renegotiation: false,
|
||||||
}
|
}
|
||||||
|
defaultDisableRenewal = false
|
||||||
globalProvisionerClaims = ProvisionerClaims{
|
globalProvisionerClaims = ProvisionerClaims{
|
||||||
MinTLSDur: &duration{5 * time.Minute},
|
MinTLSDur: &duration{5 * time.Minute},
|
||||||
MaxTLSDur: &duration{24 * time.Hour},
|
MaxTLSDur: &duration{24 * time.Hour},
|
||||||
DefaultTLSDur: &duration{24 * time.Hour},
|
DefaultTLSDur: &duration{24 * time.Hour},
|
||||||
|
DisableRenewal: &defaultDisableRenewal,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ type ProvisionerClaims struct {
|
||||||
MinTLSDur *duration `json:"minTLSCertDuration,omitempty"`
|
MinTLSDur *duration `json:"minTLSCertDuration,omitempty"`
|
||||||
MaxTLSDur *duration `json:"maxTLSCertDuration,omitempty"`
|
MaxTLSDur *duration `json:"maxTLSCertDuration,omitempty"`
|
||||||
DefaultTLSDur *duration `json:"defaultTLSCertDuration,omitempty"`
|
DefaultTLSDur *duration `json:"defaultTLSCertDuration,omitempty"`
|
||||||
|
DisableRenewal *bool `json:"disableRenewal,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes and validates the individual provisioner claims.
|
// Init initializes and validates the individual provisioner claims.
|
||||||
|
@ -57,6 +58,16 @@ func (pc *ProvisionerClaims) MaxTLSCertDuration() time.Duration {
|
||||||
return pc.MaxTLSDur.Duration
|
return pc.MaxTLSDur.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsDisableRenewal returns if the renewal flow is disabled for the
|
||||||
|
// provisioner. If the property is not set withing the provisioner, then the
|
||||||
|
// global value from the authority configuration will be used.
|
||||||
|
func (pc *ProvisionerClaims) IsDisableRenewal() bool {
|
||||||
|
if pc.DisableRenewal == nil {
|
||||||
|
return pc.globalClaims.IsDisableRenewal()
|
||||||
|
}
|
||||||
|
return *pc.DisableRenewal
|
||||||
|
}
|
||||||
|
|
||||||
// Validate validates and modifies the Claims with default values.
|
// Validate validates and modifies the Claims with default values.
|
||||||
func (pc *ProvisionerClaims) Validate() error {
|
func (pc *ProvisionerClaims) Validate() error {
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -169,6 +169,12 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts SignOptions, ext
|
||||||
// Renew creates a new Certificate identical to the old certificate, except
|
// Renew creates a new Certificate identical to the old certificate, except
|
||||||
// with a validity window that begins 'now'.
|
// with a validity window that begins 'now'.
|
||||||
func (a *Authority) Renew(ocx *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) {
|
func (a *Authority) Renew(ocx *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) {
|
||||||
|
// Check step provisioner extensions
|
||||||
|
if err := a.authorizeRenewal(ocx); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issuer
|
||||||
issIdentity := a.intermediateIdentity
|
issIdentity := a.intermediateIdentity
|
||||||
|
|
||||||
// Convert a realx509.Certificate to the step x509 Certificate.
|
// Convert a realx509.Certificate to the step x509 Certificate.
|
||||||
|
|
|
@ -251,6 +251,19 @@ func TestRenew(t *testing.T) {
|
||||||
crt, err := x509.ParseCertificate(crtBytes)
|
crt, err := x509.ParseCertificate(crtBytes)
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
|
leafNoRenew, err := x509util.NewLeafProfile("norenew", a.intermediateIdentity.Crt,
|
||||||
|
a.intermediateIdentity.Key,
|
||||||
|
x509util.WithNotBeforeAfterDuration(so.NotBefore, so.NotAfter, 0),
|
||||||
|
withDefaultASN1DN(a.config.AuthorityConfig.Template),
|
||||||
|
x509util.WithPublicKey(pub), x509util.WithHosts("test.smallstep.com,test"),
|
||||||
|
withProvisionerOID("dev", a.config.AuthorityConfig.Provisioners[2].Key.KeyID),
|
||||||
|
)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
crtBytesNoRenew, err := leafNoRenew.CreateCertificate()
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
crtNoRenew, err := x509.ParseCertificate(crtBytesNoRenew)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
type renewTest struct {
|
type renewTest struct {
|
||||||
auth *Authority
|
auth *Authority
|
||||||
crt *x509.Certificate
|
crt *x509.Certificate
|
||||||
|
@ -274,6 +287,16 @@ func TestRenew(t *testing.T) {
|
||||||
http.StatusInternalServerError, context{}},
|
http.StatusInternalServerError, context{}},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
"fail-unauthorized": func() (*renewTest, error) {
|
||||||
|
ctx := map[string]interface{}{
|
||||||
|
"serialNumber": crtNoRenew.SerialNumber.String(),
|
||||||
|
}
|
||||||
|
return &renewTest{
|
||||||
|
crt: crtNoRenew,
|
||||||
|
err: &apiError{errors.New("renew disabled"),
|
||||||
|
http.StatusUnauthorized, ctx},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
"success": func() (*renewTest, error) {
|
"success": func() (*renewTest, error) {
|
||||||
return &renewTest{
|
return &renewTest{
|
||||||
crt: crt,
|
crt: crt,
|
||||||
|
|
Loading…
Reference in a new issue