Merge pull request #1 from smallstep/disableRenewal

Add provisioner option to disable renewal
This commit is contained in:
Mariano Cano 2018-11-01 16:26:20 -07:00 committed by GitHub
commit 11dfc41214
6 changed files with 111 additions and 7 deletions

View file

@ -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",

View file

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

View file

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

View file

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

View file

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

View file

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