Add period and max_ttl to cert role creation (#3642)

This commit is contained in:
Calvin Leung Huang 2017-12-18 15:29:45 -05:00 committed by Jeff Mitchell
parent 27cdb42258
commit c4e951efb8
4 changed files with 142 additions and 43 deletions

View file

@ -587,7 +587,7 @@ func TestBackend_CRLs(t *testing.T) {
func testFactory(t *testing.T) logical.Backend {
b, err := Factory(&logical.BackendConfig{
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: 300 * time.Second,
DefaultLeaseTTLVal: 1000 * time.Second,
MaxLeaseTTLVal: 1800 * time.Second,
},
StorageView: &logical.InmemStorage{},
@ -647,6 +647,8 @@ func TestBackend_basic_CA(t *testing.T) {
testAccStepCertLease(t, "web", ca, "foo"),
testAccStepCertTTL(t, "web", ca, "foo"),
testAccStepLogin(t, connState),
testAccStepCertMaxTTL(t, "web", ca, "foo"),
testAccStepLogin(t, connState),
testAccStepCertNoLease(t, "web", ca, "foo"),
testAccStepLoginDefaultLease(t, connState),
testAccStepCert(t, "web", ca, "foo", "*.example.com", "", false),
@ -883,7 +885,7 @@ func testAccStepLoginDefaultLease(t *testing.T, connState tls.ConnectionState) l
Unauthenticated: true,
ConnState: &connState,
Check: func(resp *logical.Response) error {
if resp.Auth.TTL != 300*time.Second {
if resp.Auth.TTL != 1000*time.Second {
t.Fatalf("bad lease length: %#v", resp.Auth)
}
@ -1013,6 +1015,21 @@ func testAccStepCertTTL(
}
}
func testAccStepCertMaxTTL(
t *testing.T, name string, cert []byte, policies string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "certs/" + name,
Data: map[string]interface{}{
"certificate": string(cert),
"policies": policies,
"display_name": name,
"ttl": "1000s",
"max_ttl": "1200s",
},
}
}
func testAccStepCertNoLease(
t *testing.T, name string, cert []byte, policies string) logicaltest.TestStep {
return logicaltest.TestStep{

View file

@ -74,6 +74,19 @@ seconds. Defaults to system/backend default TTL.`,
Description: `TTL for tokens issued by this backend.
Defaults to system/backend default TTL time.`,
},
"max_ttl": &framework.FieldSchema{
Type: framework.TypeDurationSecond,
Description: `Duration in either an integer number of seconds (3600) or
an integer time unit (60m) after which the
issued token can no longer be renewed.`,
},
"period": &framework.FieldSchema{
Type: framework.TypeDurationSecond,
Description: `If set, indicates that the token generated using this role
should never expire. The token should be renewed within the
duration specified by this value. At each renewal, the token's
TTL will be set to the value of this parameter.`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
@ -131,18 +144,14 @@ func (b *backend) pathCertRead(
return nil, nil
}
duration := cert.TTL
if duration == 0 {
duration = b.System().DefaultLeaseTTL()
}
return &logical.Response{
Data: map[string]interface{}{
"certificate": cert.Certificate,
"display_name": cert.DisplayName,
"policies": cert.Policies,
"ttl": duration / time.Second,
"allowed_names": cert.AllowedNames,
"certificate": cert.Certificate,
"display_name": cert.DisplayName,
"policies": cert.Policies,
"ttl": cert.TTL / time.Second,
"max_ttl": cert.MaxTTL / time.Second,
"period": cert.Period / time.Second,
},
}, nil
}
@ -156,6 +165,47 @@ func (b *backend) pathCertWrite(
allowedNames := d.Get("allowed_names").([]string)
requiredExtensions := d.Get("required_extensions").([]string)
var resp logical.Response
// Parse the ttl (or lease duration)
systemDefaultTTL := b.System().DefaultLeaseTTL()
ttl := time.Duration(d.Get("ttl").(int)) * time.Second
if ttl == 0 {
ttl = time.Duration(d.Get("lease").(int)) * time.Second
}
if ttl > systemDefaultTTL {
resp.AddWarning(fmt.Sprintf("Given ttl of %d seconds is greater than current mount/system default of %d seconds", ttl/time.Second, systemDefaultTTL/time.Second))
}
if ttl < time.Duration(0) {
return logical.ErrorResponse("ttl cannot be negative"), nil
}
// Parse max_ttl
systemMaxTTL := b.System().MaxLeaseTTL()
maxTTL := time.Duration(d.Get("max_ttl").(int)) * time.Second
if maxTTL > systemMaxTTL {
resp.AddWarning(fmt.Sprintf("Given max_ttl of %d seconds is greater than current mount/system default of %d seconds", maxTTL/time.Second, systemMaxTTL/time.Second))
}
if maxTTL < time.Duration(0) {
return logical.ErrorResponse("max_ttl cannot be negative"), nil
}
if maxTTL != 0 && ttl > maxTTL {
return logical.ErrorResponse("ttl should be shorter than max_ttl"), nil
}
// Parse period
period := time.Duration(d.Get("period").(int)) * time.Second
if period > systemMaxTTL {
resp.AddWarning(fmt.Sprintf("Given period of %d seconds is greater than the backend's maximum TTL of %d seconds", period/time.Second, systemMaxTTL/time.Second))
}
if period < time.Duration(0) {
return logical.ErrorResponse("period cannot be negative"), nil
}
// Default the display name to the certificate name if not given
if displayName == "" {
displayName = name
@ -187,19 +237,9 @@ func (b *backend) pathCertWrite(
Policies: policies,
AllowedNames: allowedNames,
RequiredExtensions: requiredExtensions,
}
// Parse the lease duration or default to backend/system default
maxTTL := b.System().MaxLeaseTTL()
ttl := time.Duration(d.Get("ttl").(int)) * time.Second
if ttl == time.Duration(0) {
ttl = time.Second * time.Duration(d.Get("lease").(int))
}
if ttl > maxTTL {
return logical.ErrorResponse(fmt.Sprintf("Given TTL of %d seconds greater than current mount/system default of %d seconds", ttl/time.Second, maxTTL/time.Second)), nil
}
if ttl > time.Duration(0) {
certEntry.TTL = ttl
TTL: ttl,
MaxTTL: maxTTL,
Period: period,
}
// Store it
@ -210,7 +250,12 @@ func (b *backend) pathCertWrite(
if err := req.Storage.Put(entry); err != nil {
return nil, err
}
return nil, nil
if len(resp.Warnings) == 0 {
return nil, nil
}
return &resp, nil
}
type CertEntry struct {
@ -219,6 +264,8 @@ type CertEntry struct {
DisplayName string
Policies []string
TTL time.Duration
MaxTTL time.Duration
Period time.Duration
AllowedNames []string
RequiredExtensions []string
}

View file

@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"strings"
"time"
"github.com/hashicorp/vault/helper/certutil"
"github.com/hashicorp/vault/helper/policyutil"
@ -85,9 +86,9 @@ func (b *backend) pathLogin(
skid := base64.StdEncoding.EncodeToString(clientCerts[0].SubjectKeyId)
akid := base64.StdEncoding.EncodeToString(clientCerts[0].AuthorityKeyId)
// Generate a response
resp := &logical.Response{
Auth: &logical.Auth{
Period: matched.Entry.Period,
InternalData: map[string]interface{}{
"subject_key_id": skid,
"authority_key_id": akid,
@ -109,6 +110,22 @@ func (b *backend) pathLogin(
},
},
}
if matched.Entry.MaxTTL > time.Duration(0) {
// Cap maxTTL to the sysview's max TTL
maxTTL := matched.Entry.MaxTTL
if maxTTL > b.System().MaxLeaseTTL() {
maxTTL = b.System().MaxLeaseTTL()
}
// Cap TTL to MaxTTL
if resp.Auth.TTL > maxTTL {
resp.AddWarning(fmt.Sprintf("Effective TTL of '%s' exceeded the effective max_ttl of '%s'; TTL value is capped accordingly", (resp.Auth.TTL / time.Second), (maxTTL / time.Second)))
resp.Auth.TTL = maxTTL
}
}
// Generate a response
return resp, nil
}
@ -135,7 +152,7 @@ func (b *backend) pathLoginRenew(
clientCerts := req.Connection.ConnState.PeerCertificates
if len(clientCerts) == 0 {
return nil, fmt.Errorf("no client certificate found")
return logical.ErrorResponse("no client certificate found"), nil
}
skid := base64.StdEncoding.EncodeToString(clientCerts[0].SubjectKeyId)
akid := base64.StdEncoding.EncodeToString(clientCerts[0].AuthorityKeyId)
@ -161,7 +178,12 @@ func (b *backend) pathLoginRenew(
return nil, fmt.Errorf("policies have changed, not renewing")
}
return framework.LeaseExtend(cert.TTL, 0, b.System())(req, d)
resp, err := framework.LeaseExtend(cert.TTL, cert.MaxTTL, b.System())(req, d)
if err != nil {
return nil, err
}
resp.Auth.Period = cert.Period
return resp, nil
}
func (b *backend) verifyCredentials(req *logical.Request, d *framework.FieldData) (*ParsedCert, *logical.Response, error) {

View file

@ -29,21 +29,32 @@ Sets a CA cert and associated parameters in a role name.
- `name` `(string: <required>)` - The name of the certificate role.
- `certificate` `(string: <required>)` - The PEM-format CA certificate.
- `allowed_names` `(string: "")` - Constrain the Common and Alternative Names in
- `allowed_names` `(string: "")` - Constrain the Common and Alternative Names in
the client certificate with a [globbed pattern]
(https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is
a comma-separated list of patterns. Authentication requires at least one Name matching at least one pattern. If not set, defaults to allowing all names.
- `required_extensions` `(string: "" or array:[])` - Require specific Custom Extension OIDs to exist and match the pattern.
Value is a comma separated string or array of `oid:value`. Expects the extension value to be some type of ASN1 encoded string.
All conditions _must_ be met. Supports globbing on `value`.
- `policies` `(string: "")` - A comma-separated list of policies to set on tokens
issued when authenticating against this CA certificate.
- `display_name` `(string: "")` - The `display_name` to set on tokens issued
when authenticating against this CA certificate. If not set, defaults to the
(https://github.com/ryanuber/go-glob/blob/master/README.md#example). Value is
a comma-separated list of patterns. Authentication requires at least one Name
matching at least one pattern. If not set, defaults to allowing all names.
- `required_extensions` `(string: "" or array:[])` - Require specific Custom
Extension OIDs to exist and match the pattern. Value is a comma separated
string or array of `oid:value`. Expects the extension value to be some type
of ASN1 encoded string. All conditions _must_ be met. Supports globbing on
`value`.
- `policies` `(string: "")` - A comma-separated list of policies to set on
tokens issued when authenticating against this CA certificate.
- `display_name` `(string: "")` - The `display_name` to set on tokens issued
when authenticating against this CA certificate. If not set, defaults to the
name of the role.
- `ttl` `(string: "")` - The TTL period of the token, provided as a number of
seconds. If not provided, the token is valid for the the mount or system
default TTL time, in that order.
- `ttl` `(string: "")` - The TTL of the token, provided in either number of
seconds (`3600`) or a time duration (`1h`). If not provided, the token is
valid for the the mount or system default TTL time, in that order.
- `max_ttl` `(string: "")` - Duration in either number of seconds (`3600`) or a
time duration (`1h`) after which the issued token can no longer be renewed.
- `period` `(string: "")` - Duration in either number of seconds (`3600`) or a
time duration (`1h`). If set, the generated token is a periodic token; so long
as it is renewed it never expires unless `max_ttl` is also set, but the TTL
set on the token at each renewal is fixed to the value specified here. If this
value is modified, the token will pick up the new value at its next renewal.
### Sample Payload
@ -97,7 +108,9 @@ $ curl \
"policies": "",
"allowed_names": "",
"required_extensions": "",
"ttl": 2764800
"ttl": 2764800,
"max_ttl": 2764800,
"period": 0
},
"warnings": null,
"auth": null