Add period and max_ttl to cert role creation (#3642)
This commit is contained in:
parent
27cdb42258
commit
c4e951efb8
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue