Merge pull request #1 from smallstep/disableRenewal
Add provisioner option to disable renewal
This commit is contained in:
commit
11dfc41214
6 changed files with 111 additions and 7 deletions
|
@ -15,6 +15,7 @@ func testAuthority(t *testing.T) *Authority {
|
|||
assert.FatalError(t, err)
|
||||
clijwk, err := stepJOSE.ParseKey("testdata/secrets/step_cli_key_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
disableRenewal := true
|
||||
p := []*Provisioner{
|
||||
{
|
||||
Name: "Max",
|
||||
|
@ -26,6 +27,14 @@ func testAuthority(t *testing.T) *Authority {
|
|||
Type: "JWK",
|
||||
Key: clijwk,
|
||||
},
|
||||
{
|
||||
Name: "dev",
|
||||
Type: "JWK",
|
||||
Key: maxjwk,
|
||||
Claims: &ProvisionerClaims{
|
||||
DisableRenewal: &disableRenewal,
|
||||
},
|
||||
},
|
||||
}
|
||||
c := &Config{
|
||||
Address: "127.0.0.1:443",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package authority
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -117,3 +119,54 @@ func (a *Authority) Authorize(ott string) ([]interface{}, error) {
|
|||
|
||||
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,
|
||||
Renegotiation: false,
|
||||
}
|
||||
defaultDisableRenewal = false
|
||||
globalProvisionerClaims = ProvisionerClaims{
|
||||
MinTLSDur: &duration{5 * time.Minute},
|
||||
MaxTLSDur: &duration{24 * time.Hour},
|
||||
DefaultTLSDur: &duration{24 * time.Hour},
|
||||
MinTLSDur: &duration{5 * time.Minute},
|
||||
MaxTLSDur: &duration{24 * time.Hour},
|
||||
DefaultTLSDur: &duration{24 * time.Hour},
|
||||
DisableRenewal: &defaultDisableRenewal,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -11,10 +11,11 @@ import (
|
|||
|
||||
// ProvisionerClaims so that individual provisioners can override global claims.
|
||||
type ProvisionerClaims struct {
|
||||
globalClaims *ProvisionerClaims
|
||||
MinTLSDur *duration `json:"minTLSCertDuration,omitempty"`
|
||||
MaxTLSDur *duration `json:"maxTLSCertDuration,omitempty"`
|
||||
DefaultTLSDur *duration `json:"defaultTLSCertDuration,omitempty"`
|
||||
globalClaims *ProvisionerClaims
|
||||
MinTLSDur *duration `json:"minTLSCertDuration,omitempty"`
|
||||
MaxTLSDur *duration `json:"maxTLSCertDuration,omitempty"`
|
||||
DefaultTLSDur *duration `json:"defaultTLSCertDuration,omitempty"`
|
||||
DisableRenewal *bool `json:"disableRenewal,omitempty"`
|
||||
}
|
||||
|
||||
// Init initializes and validates the individual provisioner claims.
|
||||
|
@ -57,6 +58,16 @@ func (pc *ProvisionerClaims) MaxTLSCertDuration() time.Duration {
|
|||
return pc.MaxTLSDur.Duration
|
||||
}
|
||||
|
||||
// IsDisableRenewal returns if the renewal flow is disabled for the
|
||||
// provisioner. If the property is not set within 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.
|
||||
func (pc *ProvisionerClaims) Validate() error {
|
||||
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
|
||||
// with a validity window that begins 'now'.
|
||||
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
|
||||
|
||||
// Convert a realx509.Certificate to the step x509 Certificate.
|
||||
|
|
|
@ -251,6 +251,19 @@ func TestRenew(t *testing.T) {
|
|||
crt, err := x509.ParseCertificate(crtBytes)
|
||||
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 {
|
||||
auth *Authority
|
||||
crt *x509.Certificate
|
||||
|
@ -274,6 +287,16 @@ func TestRenew(t *testing.T) {
|
|||
http.StatusInternalServerError, context{}},
|
||||
}, 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) {
|
||||
return &renewTest{
|
||||
crt: crt,
|
||||
|
|
Loading…
Reference in a new issue