Deprecate lease -> ttl in PKI backend, and default to system TTL values if not given. This prevents issuing certificates with a longer duration than the maximum lease TTL configured in Vault. Fixes #470.
This commit is contained in:
parent
eed9b6da7f
commit
a4fc4a8e90
|
@ -23,7 +23,16 @@ var (
|
|||
|
||||
// Performs basic tests on CA functionality
|
||||
func TestBackend_basic(t *testing.T) {
|
||||
b := Backend()
|
||||
b, err := Factory(&logical.BackendConfig{
|
||||
Logger: nil,
|
||||
System: &logical.StaticSystemView{
|
||||
DefaultLeaseTTLVal: time.Hour * 24,
|
||||
MaxLeaseTTLVal: time.Hour * 24 * 30,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create backend: %s", err)
|
||||
}
|
||||
|
||||
testCase := logicaltest.TestCase{
|
||||
Backend: b,
|
||||
|
@ -40,7 +49,16 @@ func TestBackend_basic(t *testing.T) {
|
|||
// Generates and tests steps that walk through the various possibilities
|
||||
// of role flags to ensure that they are properly restricted
|
||||
func TestBackend_roles(t *testing.T) {
|
||||
b := Backend()
|
||||
b, err := Factory(&logical.BackendConfig{
|
||||
Logger: nil,
|
||||
System: &logical.StaticSystemView{
|
||||
DefaultLeaseTTLVal: time.Hour * 24,
|
||||
MaxLeaseTTLVal: time.Hour * 24 * 30,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create backend: %s", err)
|
||||
}
|
||||
|
||||
testCase := logicaltest.TestCase{
|
||||
Backend: b,
|
||||
|
@ -111,7 +129,7 @@ func checkCertsAndPrivateKey(keyType string, usage certUsage, validity time.Dura
|
|||
}
|
||||
|
||||
if math.Abs(float64(time.Now().Add(validity).Unix()-cert.NotAfter.Unix())) > 10 {
|
||||
return nil, fmt.Errorf("Validity period too large")
|
||||
return nil, fmt.Errorf("Validity period of %d too large vs max of 10", cert.NotAfter.Unix())
|
||||
}
|
||||
|
||||
return parsedCertBundle, nil
|
||||
|
@ -204,7 +222,7 @@ func generateCASteps(t *testing.T) []logicaltest.TestStep {
|
|||
// Generates steps to test out various role permutations
|
||||
func generateRoleSteps(t *testing.T) []logicaltest.TestStep {
|
||||
roleVals := roleEntry{
|
||||
LeaseMax: "12h",
|
||||
MaxTTL: "12h",
|
||||
}
|
||||
issueVals := certutil.IssueData{}
|
||||
ret := []logicaltest.TestStep{}
|
||||
|
@ -324,7 +342,7 @@ func generateRoleSteps(t *testing.T) []logicaltest.TestStep {
|
|||
issueTestStep.ErrorOk = true
|
||||
}
|
||||
|
||||
validity, _ := time.ParseDuration(roleVals.LeaseMax)
|
||||
validity, _ := time.ParseDuration(roleVals.MaxTTL)
|
||||
addTests(getCnCheck(name, roleVals.KeyType, usage, validity))
|
||||
}
|
||||
}
|
||||
|
@ -388,11 +406,11 @@ func generateRoleSteps(t *testing.T) []logicaltest.TestStep {
|
|||
{
|
||||
roleTestStep.ErrorOk = true
|
||||
roleVals.Lease = ""
|
||||
roleVals.LeaseMax = ""
|
||||
roleVals.MaxTTL = ""
|
||||
addTests(nil)
|
||||
|
||||
roleVals.Lease = "12h"
|
||||
roleVals.LeaseMax = "6h"
|
||||
roleVals.MaxTTL = "6h"
|
||||
addTests(nil)
|
||||
|
||||
roleTestStep.ErrorOk = false
|
||||
|
|
|
@ -34,7 +34,7 @@ type certCreationBundle struct {
|
|||
IPSANs []net.IP
|
||||
KeyType string
|
||||
KeyBits int
|
||||
Lease time.Duration
|
||||
TTL time.Duration
|
||||
Usage certUsage
|
||||
}
|
||||
|
||||
|
@ -234,7 +234,7 @@ func createCertificate(creationInfo *certCreationBundle) (*certutil.ParsedCertBu
|
|||
SerialNumber: serialNumber,
|
||||
Subject: subject,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(creationInfo.Lease),
|
||||
NotAfter: time.Now().Add(creationInfo.TTL),
|
||||
KeyUsage: x509.KeyUsage(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
|
|
|
@ -39,7 +39,14 @@ common-delimited list`,
|
|||
},
|
||||
"lease": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "The requested lease",
|
||||
Description: `The requested lease. DEPRECATED: use "ttl" instead.`,
|
||||
},
|
||||
"ttl": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: `The requested Time To Live for the certificate;
|
||||
sets the expiration date. If not specified
|
||||
the role default TTL it used. Cannot be larer
|
||||
than the role max TTL.`,
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -97,24 +104,44 @@ func (b *backend) pathIssueCert(
|
|||
}
|
||||
}
|
||||
|
||||
leaseField := data.Get("lease").(string)
|
||||
if len(leaseField) == 0 {
|
||||
leaseField = role.Lease
|
||||
ttlField := data.Get("ttl").(string)
|
||||
if len(ttlField) == 0 {
|
||||
ttlField = data.Get("lease").(string)
|
||||
if len(ttlField) == 0 {
|
||||
ttlField = role.TTL
|
||||
}
|
||||
}
|
||||
|
||||
lease, err := time.ParseDuration(leaseField)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"Invalid requested lease: %s", err)), nil
|
||||
}
|
||||
leaseMax, err := time.ParseDuration(role.LeaseMax)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"Invalid lease: %s", err)), nil
|
||||
var ttl time.Duration
|
||||
if len(ttlField) == 0 {
|
||||
ttl = b.System.DefaultLeaseTTL()
|
||||
} else {
|
||||
ttl, err = time.ParseDuration(ttlField)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"Invalid requested ttl: %s", err)), nil
|
||||
}
|
||||
}
|
||||
|
||||
if lease > leaseMax {
|
||||
return logical.ErrorResponse("Lease expires after maximum allowed by this role"), nil
|
||||
var maxTTL time.Duration
|
||||
if len(role.MaxTTL) == 0 {
|
||||
maxTTL = b.System.MaxLeaseTTL()
|
||||
} else {
|
||||
maxTTL, err = time.ParseDuration(role.MaxTTL)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"Invalid ttl: %s", err)), nil
|
||||
}
|
||||
}
|
||||
|
||||
if ttl > maxTTL {
|
||||
// Don't error if they were using system defaults, only error if
|
||||
// they specifically chose a bad TTL
|
||||
if len(ttlField) == 0 {
|
||||
ttl = maxTTL
|
||||
} else {
|
||||
return logical.ErrorResponse("TTL is larger than maximum allowed by this role"), nil
|
||||
}
|
||||
}
|
||||
|
||||
badName, err := validateCommonNames(req, commonNames, role)
|
||||
|
@ -132,8 +159,8 @@ func (b *backend) pathIssueCert(
|
|||
return nil, fmt.Errorf("Error fetching CA certificate: %s", caErr)
|
||||
}
|
||||
|
||||
if time.Now().Add(lease).After(signingBundle.Certificate.NotAfter) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("Cannot satisfy request, as maximum lease is beyond the expiration of the CA certificate")), nil
|
||||
if time.Now().Add(ttl).After(signingBundle.Certificate.NotAfter) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("Cannot satisfy request, as TTL is beyond the expiration of the CA certificate")), nil
|
||||
}
|
||||
|
||||
var usage certUsage
|
||||
|
@ -154,7 +181,7 @@ func (b *backend) pathIssueCert(
|
|||
IPSANs: ipSANs,
|
||||
KeyType: role.KeyType,
|
||||
KeyBits: role.KeyBits,
|
||||
Lease: lease,
|
||||
TTL: ttl,
|
||||
Usage: usage,
|
||||
}
|
||||
|
||||
|
@ -177,7 +204,7 @@ func (b *backend) pathIssueCert(
|
|||
"serial_number": cb.SerialNumber,
|
||||
})
|
||||
|
||||
resp.Secret.TTL = lease
|
||||
resp.Secret.TTL = ttl
|
||||
|
||||
err = req.Storage.Put(&logical.StorageEntry{
|
||||
Key: "certs/" + cb.SerialNumber,
|
||||
|
|
|
@ -24,13 +24,29 @@ func pathRoles(b *backend) *framework.Path {
|
|||
Description: `The lease length if no specific lease length is
|
||||
requested. The lease length controls the expiration
|
||||
of certificates issued by this backend. Defaults to
|
||||
the value of lease_max.`,
|
||||
the value of lease_max. DEPRECATED: use "ttl" instead.`,
|
||||
},
|
||||
|
||||
"lease_max": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "",
|
||||
Description: `The maximum allowed lease length.
|
||||
DEPRECATED: use "ttl" instead.`,
|
||||
},
|
||||
|
||||
"ttl": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "",
|
||||
Description: `The lease duration if no specific lease duration is
|
||||
requested. The lease duration controls the expiration
|
||||
of certificates issued by this backend. Defaults to
|
||||
the value of max_ttl.`,
|
||||
},
|
||||
|
||||
"max_ttl": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "",
|
||||
Description: "The maximum allowed lease length",
|
||||
Description: "The maximum allowed lease duration",
|
||||
},
|
||||
|
||||
"allow_localhost": &framework.FieldSchema{
|
||||
|
@ -150,6 +166,28 @@ func (b *backend) getRole(s logical.Storage, n string) (*roleEntry, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Migrate existing saved entries and save back if changed
|
||||
modified := false
|
||||
if len(result.TTL) == 0 && len(result.Lease) != 0 {
|
||||
result.TTL = result.Lease
|
||||
result.Lease = ""
|
||||
modified = true
|
||||
}
|
||||
if len(result.MaxTTL) == 0 && len(result.LeaseMax) != 0 {
|
||||
result.MaxTTL = result.LeaseMax
|
||||
result.LeaseMax = ""
|
||||
modified = true
|
||||
}
|
||||
if modified {
|
||||
jsonEntry, err := logical.StorageEntryJSON("role/"+n, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.Put(jsonEntry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
|
@ -173,6 +211,19 @@ func (b *backend) pathRoleRead(
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
hasMax := true
|
||||
if len(role.MaxTTL) == 0 {
|
||||
role.MaxTTL = "(system default)"
|
||||
hasMax = false
|
||||
}
|
||||
if len(role.TTL) == 0 {
|
||||
if hasMax {
|
||||
role.TTL = "(system default, capped to role max)"
|
||||
} else {
|
||||
role.TTL = "(system default)"
|
||||
}
|
||||
}
|
||||
|
||||
resp := &logical.Response{
|
||||
Data: structs.New(role).Map(),
|
||||
}
|
||||
|
@ -182,11 +233,12 @@ func (b *backend) pathRoleRead(
|
|||
|
||||
func (b *backend) pathRoleCreate(
|
||||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
var err error
|
||||
name := data.Get("name").(string)
|
||||
|
||||
entry := &roleEntry{
|
||||
LeaseMax: data.Get("lease_max").(string),
|
||||
Lease: data.Get("lease").(string),
|
||||
MaxTTL: data.Get("max_ttl").(string),
|
||||
TTL: data.Get("ttl").(string),
|
||||
AllowLocalhost: data.Get("allow_localhost").(bool),
|
||||
AllowedBaseDomain: data.Get("allowed_base_domain").(string),
|
||||
AllowTokenDisplayName: data.Get("allow_token_displayname").(bool),
|
||||
|
@ -201,27 +253,45 @@ func (b *backend) pathRoleCreate(
|
|||
KeyBits: data.Get("key_bits").(int),
|
||||
}
|
||||
|
||||
if len(entry.LeaseMax) == 0 {
|
||||
return logical.ErrorResponse("\"lease_max\" value must be supplied"), nil
|
||||
if len(entry.MaxTTL) == 0 {
|
||||
entry.MaxTTL = data.Get("lease_max").(string)
|
||||
}
|
||||
|
||||
leaseMax, err := time.ParseDuration(entry.LeaseMax)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"Invalid lease: %s", err)), nil
|
||||
}
|
||||
|
||||
switch len(entry.Lease) {
|
||||
case 0:
|
||||
entry.Lease = entry.LeaseMax
|
||||
default:
|
||||
lease, err := time.ParseDuration(entry.Lease)
|
||||
var maxTTL time.Duration
|
||||
if len(entry.MaxTTL) == 0 {
|
||||
maxTTL = b.System.MaxLeaseTTL()
|
||||
} else {
|
||||
maxTTL, err = time.ParseDuration(entry.MaxTTL)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"Invalid lease: %s", err)), nil
|
||||
"Invalid ttl: %s", err)), nil
|
||||
}
|
||||
if lease > leaseMax {
|
||||
return logical.ErrorResponse("\"lease\" value must be less than \"lease_max\" value"), nil
|
||||
}
|
||||
if maxTTL > b.System.MaxLeaseTTL() {
|
||||
return logical.ErrorResponse("Requested max TTL is higher than system maximum"), nil
|
||||
}
|
||||
|
||||
var ttl time.Duration
|
||||
if len(entry.TTL) == 0 {
|
||||
entry.TTL = data.Get("lease").(string)
|
||||
}
|
||||
switch len(entry.TTL) {
|
||||
case 0:
|
||||
ttl = b.System.DefaultLeaseTTL()
|
||||
default:
|
||||
ttl, err = time.ParseDuration(entry.TTL)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf(
|
||||
"Invalid ttl: %s", err)), nil
|
||||
}
|
||||
}
|
||||
if ttl > maxTTL {
|
||||
// If they are using the system default, cap it to the role max;
|
||||
// if it was specified on the command line, make it an error
|
||||
if len(entry.TTL) == 0 {
|
||||
ttl = maxTTL
|
||||
} else {
|
||||
return logical.ErrorResponse("\"ttl\" value must be less than \"max_ttl\" and/or system default max lease TTL value"), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,6 +332,8 @@ func (b *backend) pathRoleCreate(
|
|||
type roleEntry struct {
|
||||
LeaseMax string `json:"lease_max" structs:"lease_max" mapstructure:"lease_max"`
|
||||
Lease string `json:"lease" structs:"lease" mapstructure:"lease"`
|
||||
MaxTTL string `json:"max_ttl" structs:"max_ttl" mapstructure:"max_ttl"`
|
||||
TTL string `json:"ttl" structs:"ttl" mapstructure:"ttl"`
|
||||
AllowLocalhost bool `json:"allow_localhost" structs:"allow_localhost" mapstructure:"allow_localhost"`
|
||||
AllowedBaseDomain string `json:"allowed_base_domain" structs:"allowed_base_domain" mapstructure:"allowed_base_domain"`
|
||||
AllowTokenDisplayName bool `json:"allow_token_displayname" structs:"allow_token_displayname" mapstructure:"allow_token_displayname"`
|
||||
|
|
|
@ -12,7 +12,7 @@ Name: `pki`
|
|||
|
||||
The PKI secret backend for Vault generates X.509 certificates dynamically based on configured roles. This means services can get certificates needed for both client and server authentication without going through the usual manual process of generating a private key and CSR, submitting to a CA, and waiting for a verification and signing process to complete. Vault's built-in authentication and authorization mechanisms provide the verification functionality.
|
||||
|
||||
By keeping leases relatively short, revocations are less likely to be needed, keeping CRLs short and helping the backend scale to large workloads. This in turn allows each instance of a running application to have a unique certificate, eliminating sharing and the accompanying pain of revocation and rollover.
|
||||
By keeping TTLs relatively short, revocations are less likely to be needed, keeping CRLs short and helping the backend scale to large workloads. This in turn allows each instance of a running application to have a unique certificate, eliminating sharing and the accompanying pain of revocation and rollover.
|
||||
|
||||
In addition, by allowing revocation to mostly be forgone, this backend allows for ephemeral certificates; certificates can be fetched and stored in memory upon application startup and discarded upon shutdown, without ever being written to disk.
|
||||
|
||||
|
@ -92,7 +92,7 @@ The next step is to configure a role. A role is a logical name that maps to a po
|
|||
```text
|
||||
$ vault write pki/roles/example-dot-com \
|
||||
allowed_base_domain="example.com" \
|
||||
allow_subdomains="true" lease_max="72h"
|
||||
allow_subdomains="true" max_ttl="72h"
|
||||
Success! Data written to: pki/roles/example-dot-com
|
||||
```
|
||||
|
||||
|
@ -370,11 +370,12 @@ If you get stuck at any time, simply run `vault path-help pki` or with a subpath
|
|||
default).
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">lease</span>
|
||||
<span class="param">ttl</span>
|
||||
<span class="param-flags">optional</span>
|
||||
Requested lease time. Cannot be greater than the role's
|
||||
`lease_max` parameter. If not provided, the role's `lease`
|
||||
value will be used.
|
||||
Requested Time To Live. Cannot be greater than the role's
|
||||
`max_ttl` value. If not provided, the role's `ttl`
|
||||
value will be used. Note that the role values default
|
||||
to system values if not explicitly set.
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
|
@ -470,17 +471,19 @@ If you get stuck at any time, simply run `vault path-help pki` or with a subpath
|
|||
<dd>
|
||||
<ul>
|
||||
<li>
|
||||
<span class="param">lease</span>
|
||||
<span class="param">ttl</span>
|
||||
<span class="param-flags">optional</span>
|
||||
The lease value provided as a string duration
|
||||
The Time To Live value provided as a string duration
|
||||
with time suffix. Hour is the largest suffix.
|
||||
If not set, uses the value of `lease_max`.
|
||||
If not set, uses the system default value or the
|
||||
value of `max_ttl`, whichever is shorter.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">lease_max</span>
|
||||
<span class="param-flags">required</span>
|
||||
The maximum lease value provided as a string duration
|
||||
with time suffix. Hour is the largest suffix.
|
||||
<span class="param">max_ttl</span>
|
||||
<span class="param-flags">optional</span>
|
||||
The maximum Time To Live provided as a string duration
|
||||
with time suffix. Hour is the largest suffix. If not set,
|
||||
defaults to the system maximum lease TTL.
|
||||
</li>
|
||||
<li>
|
||||
<span class="param">allow_localhost</span>
|
||||
|
@ -611,8 +614,8 @@ If you get stuck at any time, simply run `vault path-help pki` or with a subpath
|
|||
"code_signing_flag": false,
|
||||
"key_bits": 2048,
|
||||
"key_type": "rsa",
|
||||
"lease": "6h",
|
||||
"lease_max": "12h",
|
||||
"ttl": "6h",
|
||||
"max_ttl": "12h",
|
||||
"server_flag": true
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue