Merge branch 'master-oss' into cubbyhole-the-world
This commit is contained in:
commit
ce5614bf9b
|
@ -46,6 +46,12 @@ FEATURES:
|
||||||
standby nodes are `standby.vault.service.consul`. Sealed vaults are marked
|
standby nodes are `standby.vault.service.consul`. Sealed vaults are marked
|
||||||
critical and are not listed by default in Consul's service discovery. See
|
critical and are not listed by default in Consul's service discovery. See
|
||||||
the documentation for details. [GH-1349]
|
the documentation for details. [GH-1349]
|
||||||
|
* **Explicit Maximum Token TTLs using Token Roles**: If using token roles, you
|
||||||
|
can now set explicit maximum TTLs on tokens that do not honor changes in the
|
||||||
|
system- or mount-set values. This is useful, for instance, when the max TTL
|
||||||
|
of the system or the `auth/token` mount must be set high to accommodate
|
||||||
|
certain needs but you want more granular restrictions on tokens being issued
|
||||||
|
directly from `auth/token`. [GH-1399]
|
||||||
|
|
||||||
IMPROVEMENTS:
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
|
|
@ -139,6 +139,7 @@ func TestLogical_StandbyRedirect(t *testing.T) {
|
||||||
"ttl": float64(0),
|
"ttl": float64(0),
|
||||||
"creation_ttl": float64(0),
|
"creation_ttl": float64(0),
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"explicit_max_ttl": float64(0),
|
||||||
},
|
},
|
||||||
"warnings": nilWarnings,
|
"warnings": nilWarnings,
|
||||||
"wrap_info": nil,
|
"wrap_info": nil,
|
||||||
|
|
|
@ -303,6 +303,7 @@ func TestSysGenerateRoot_Update_OTP(t *testing.T) {
|
||||||
"ttl": float64(0),
|
"ttl": float64(0),
|
||||||
"path": "auth/token/root",
|
"path": "auth/token/root",
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"explicit_max_ttl": float64(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self")
|
resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self")
|
||||||
|
@ -385,6 +386,7 @@ func TestSysGenerateRoot_Update_PGP(t *testing.T) {
|
||||||
"ttl": float64(0),
|
"ttl": float64(0),
|
||||||
"path": "auth/token/root",
|
"path": "auth/token/root",
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"explicit_max_ttl": float64(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self")
|
resp = testHttpGet(t, newRootToken, addr+"/v1/auth/token/lookup-self")
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/armon/go-metrics"
|
"github.com/armon/go-metrics"
|
||||||
"github.com/fatih/structs"
|
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
|
"github.com/hashicorp/vault/helper/policyutil"
|
||||||
"github.com/hashicorp/vault/helper/salt"
|
"github.com/hashicorp/vault/helper/salt"
|
||||||
"github.com/hashicorp/vault/helper/strutil"
|
"github.com/hashicorp/vault/helper/strutil"
|
||||||
"github.com/hashicorp/vault/logical"
|
"github.com/hashicorp/vault/logical"
|
||||||
|
@ -154,6 +154,12 @@ func NewTokenStore(c *Core, config *logical.BackendConfig) (*TokenStore, error)
|
||||||
Default: "",
|
Default: "",
|
||||||
Description: tokenPathSuffixHelp + pathSuffixSanitize.String(),
|
Description: tokenPathSuffixHelp + pathSuffixSanitize.String(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"explicit_max_ttl": &framework.FieldSchema{
|
||||||
|
Type: framework.TypeDurationSecond,
|
||||||
|
Default: 0,
|
||||||
|
Description: tokenExplicitMaxTTLHelp,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
|
@ -418,6 +424,7 @@ type TokenEntry struct {
|
||||||
NumUses int // Used to restrict the number of uses (zero is unlimited). This is to support one-time-tokens (generalized).
|
NumUses int // Used to restrict the number of uses (zero is unlimited). This is to support one-time-tokens (generalized).
|
||||||
CreationTime int64 // Time of token creation
|
CreationTime int64 // Time of token creation
|
||||||
TTL time.Duration // Duration set when token was created
|
TTL time.Duration // Duration set when token was created
|
||||||
|
ExplicitMaxTTL time.Duration // Explicit maximum TTL on the token
|
||||||
Role string // If set, the role that was used for parameters at creation time
|
Role string // If set, the role that was used for parameters at creation time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,8 +445,12 @@ type tsRoleEntry struct {
|
||||||
Period time.Duration `json:"period" mapstructure:"period" structs:"period"`
|
Period time.Duration `json:"period" mapstructure:"period" structs:"period"`
|
||||||
|
|
||||||
// If set, a suffix will be set on the token path, making it easier to
|
// If set, a suffix will be set on the token path, making it easier to
|
||||||
// revoke using 'revoke-prefix'.
|
// revoke using 'revoke-prefix'
|
||||||
PathSuffix string `json:"path_suffix" mapstructure:"path_suffix" structs:"path_suffix"`
|
PathSuffix string `json:"path_suffix" mapstructure:"path_suffix" structs:"path_suffix"`
|
||||||
|
|
||||||
|
// If set, the token entry will have an explicit maximum TTL set, rather
|
||||||
|
// than deferring to role/mount values
|
||||||
|
ExplicitMaxTTL time.Duration `json:"explicit_max_ttl" mapstructure:"explicit_max_ttl" structs:"explicit_max_ttl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetExpirationManager is used to provide the token store with
|
// SetExpirationManager is used to provide the token store with
|
||||||
|
@ -991,8 +1002,12 @@ func (ts *TokenStore) handleCreateCommon(
|
||||||
if len(data.Policies) == 0 {
|
if len(data.Policies) == 0 {
|
||||||
data.Policies = role.AllowedPolicies
|
data.Policies = role.AllowedPolicies
|
||||||
} else {
|
} else {
|
||||||
if !strutil.StrListSubset(role.AllowedPolicies, data.Policies) {
|
// Sanitize passed-in and role policies before comparison
|
||||||
return logical.ErrorResponse("token policies must be subset of the role's allowed policies"), logical.ErrInvalidRequest
|
sanitizedInputPolicies := policyutil.SanitizePolicies(data.Policies)
|
||||||
|
sanitizedRolePolicies := policyutil.SanitizePolicies(role.AllowedPolicies)
|
||||||
|
|
||||||
|
if !strutil.StrListSubset(sanitizedRolePolicies, sanitizedInputPolicies) {
|
||||||
|
return logical.ErrorResponse(fmt.Sprintf("token policies (%v) must be subset of the role's allowed policies (%v)", sanitizedInputPolicies, sanitizedRolePolicies)), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1001,9 +1016,15 @@ func (ts *TokenStore) handleCreateCommon(
|
||||||
|
|
||||||
// When a role is not in use, only permit policies to be a subset unless
|
// When a role is not in use, only permit policies to be a subset unless
|
||||||
// the client has root or sudo privileges
|
// the client has root or sudo privileges
|
||||||
case !isSudo && !strutil.StrListSubset(parent.Policies, data.Policies):
|
case !isSudo:
|
||||||
|
// Sanitize passed-in and parent policies before comparison
|
||||||
|
sanitizedInputPolicies := policyutil.SanitizePolicies(data.Policies)
|
||||||
|
sanitizedParentPolicies := policyutil.SanitizePolicies(parent.Policies)
|
||||||
|
|
||||||
|
if !strutil.StrListSubset(sanitizedParentPolicies, sanitizedInputPolicies) {
|
||||||
return logical.ErrorResponse("child policies must be subset of parent"), logical.ErrInvalidRequest
|
return logical.ErrorResponse("child policies must be subset of parent"), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use a map to filter out/prevent duplicates
|
// Use a map to filter out/prevent duplicates
|
||||||
policyMap := map[string]bool{}
|
policyMap := map[string]bool{}
|
||||||
|
@ -1061,6 +1082,7 @@ func (ts *TokenStore) handleCreateCommon(
|
||||||
}
|
}
|
||||||
te.TTL = dur
|
te.TTL = dur
|
||||||
} else if data.Lease != "" {
|
} else if data.Lease != "" {
|
||||||
|
// This block is compatibility
|
||||||
dur, err := time.ParseDuration(data.Lease)
|
dur, err := time.ParseDuration(data.Lease)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
||||||
|
@ -1084,14 +1106,35 @@ func (ts *TokenStore) handleCreateCommon(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resp := &logical.Response{}
|
||||||
|
|
||||||
|
if role != nil && role.ExplicitMaxTTL != 0 {
|
||||||
|
sysView := ts.System()
|
||||||
|
|
||||||
|
// Limit the lease duration
|
||||||
|
if sysView.MaxLeaseTTL() != time.Duration(0) && role.ExplicitMaxTTL > sysView.MaxLeaseTTL() {
|
||||||
|
return logical.ErrorResponse(fmt.Sprintf(
|
||||||
|
"role explicit max TTL of %d is greater than system/mount allowed value of %d seconds",
|
||||||
|
role.ExplicitMaxTTL.Seconds(), sysView.MaxLeaseTTL().Seconds())), logical.ErrInvalidRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
if te.TTL > role.ExplicitMaxTTL {
|
||||||
|
resp.AddWarning(fmt.Sprintf(
|
||||||
|
"Requested TTL higher than role explicit max TTL; value being capped to %d seconds",
|
||||||
|
role.ExplicitMaxTTL.Seconds()))
|
||||||
|
te.TTL = role.ExplicitMaxTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
te.ExplicitMaxTTL = role.ExplicitMaxTTL
|
||||||
|
}
|
||||||
|
|
||||||
// Create the token
|
// Create the token
|
||||||
if err := ts.create(&te); err != nil {
|
if err := ts.create(&te); err != nil {
|
||||||
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the response
|
// Generate the response
|
||||||
resp := &logical.Response{
|
resp.Auth = &logical.Auth{
|
||||||
Auth: &logical.Auth{
|
|
||||||
DisplayName: te.DisplayName,
|
DisplayName: te.DisplayName,
|
||||||
Policies: te.Policies,
|
Policies: te.Policies,
|
||||||
Metadata: te.Meta,
|
Metadata: te.Meta,
|
||||||
|
@ -1101,7 +1144,6 @@ func (ts *TokenStore) handleCreateCommon(
|
||||||
},
|
},
|
||||||
ClientToken: te.ID,
|
ClientToken: te.ID,
|
||||||
Accessor: te.Accessor,
|
Accessor: te.Accessor,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ts.policyLookupFunc != nil {
|
if ts.policyLookupFunc != nil {
|
||||||
|
@ -1236,6 +1278,7 @@ func (ts *TokenStore) handleLookup(
|
||||||
"creation_ttl": int64(out.TTL.Seconds()),
|
"creation_ttl": int64(out.TTL.Seconds()),
|
||||||
"ttl": int64(0),
|
"ttl": int64(0),
|
||||||
"role": out.Role,
|
"role": out.Role,
|
||||||
|
"explicit_max_ttl": int64(out.ExplicitMaxTTL.Seconds()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1311,8 +1354,6 @@ func (ts *TokenStore) authRenew(
|
||||||
return nil, fmt.Errorf("request auth is nil")
|
return nil, fmt.Errorf("request auth is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
f := framework.LeaseExtend(req.Auth.Increment, 0, ts.System())
|
|
||||||
|
|
||||||
te, err := ts.Lookup(req.Auth.ClientToken)
|
te, err := ts.Lookup(req.Auth.ClientToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error looking up token: %s", err)
|
return nil, fmt.Errorf("error looking up token: %s", err)
|
||||||
|
@ -1321,6 +1362,8 @@ func (ts *TokenStore) authRenew(
|
||||||
return nil, fmt.Errorf("no token entry found during lookup")
|
return nil, fmt.Errorf("no token entry found during lookup")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f := framework.LeaseExtend(req.Auth.Increment, te.ExplicitMaxTTL, ts.System())
|
||||||
|
|
||||||
// No role? Use normal LeaseExtend semantics
|
// No role? Use normal LeaseExtend semantics
|
||||||
if te.Role == "" {
|
if te.Role == "" {
|
||||||
return f(req, d)
|
return f(req, d)
|
||||||
|
@ -1339,7 +1382,13 @@ func (ts *TokenStore) authRenew(
|
||||||
// periodic token is always the same (the role's period value). It is not
|
// periodic token is always the same (the role's period value). It is not
|
||||||
// subject to normal maximum TTL checks that would come from calling
|
// subject to normal maximum TTL checks that would come from calling
|
||||||
// LeaseExtend, so we fast path it.
|
// LeaseExtend, so we fast path it.
|
||||||
if role.Period != 0 {
|
//
|
||||||
|
// The one wrinkle here is if the token has an explicit max TTL. Roles
|
||||||
|
// don't support having both configured, but they could be changed. We
|
||||||
|
// don't support tokens that are both periodic and have an explicit max
|
||||||
|
// TTL, so if the token has one, we treat it as a regular token even if the
|
||||||
|
// role is periodic.
|
||||||
|
if role.Period != 0 && te.ExplicitMaxTTL == 0 {
|
||||||
req.Auth.TTL = role.Period
|
req.Auth.TTL = role.Period
|
||||||
return &logical.Response{Auth: req.Auth}, nil
|
return &logical.Response{Auth: req.Auth}, nil
|
||||||
}
|
}
|
||||||
|
@ -1400,12 +1449,14 @@ func (ts *TokenStore) tokenStoreRoleRead(
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &logical.Response{
|
resp := &logical.Response{
|
||||||
Data: structs.New(role).Map(),
|
Data: map[string]interface{}{
|
||||||
}
|
"period": int64(role.Period.Seconds()),
|
||||||
|
"explicit_max_ttl": int64(role.ExplicitMaxTTL.Seconds()),
|
||||||
// Make the period nicer
|
"allowed_policies": role.AllowedPolicies,
|
||||||
if role.Period != 0 {
|
"name": role.Name,
|
||||||
resp.Data["period"] = role.Period.Seconds()
|
"orphan": role.Orphan,
|
||||||
|
"path_suffix": role.PathSuffix,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
|
@ -1461,13 +1512,36 @@ func (ts *TokenStore) tokenStoreRoleCreateUpdate(
|
||||||
entry.Period = time.Second * time.Duration(data.Get("period").(int))
|
entry.Period = time.Second * time.Duration(data.Get("period").(int))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resp *logical.Response
|
||||||
|
|
||||||
|
explicitMaxTTLInt, ok := data.GetOk("explicit_max_ttl")
|
||||||
|
if ok {
|
||||||
|
entry.ExplicitMaxTTL = time.Second * time.Duration(explicitMaxTTLInt.(int))
|
||||||
|
} else if req.Operation == logical.CreateOperation {
|
||||||
|
entry.ExplicitMaxTTL = time.Second * time.Duration(data.Get("explicit_max_ttl").(int))
|
||||||
|
}
|
||||||
|
if entry.ExplicitMaxTTL != 0 {
|
||||||
|
sysView := ts.System()
|
||||||
|
|
||||||
|
if sysView.MaxLeaseTTL() != time.Duration(0) && entry.ExplicitMaxTTL > sysView.MaxLeaseTTL() {
|
||||||
|
if resp == nil {
|
||||||
|
resp = &logical.Response{}
|
||||||
|
}
|
||||||
|
resp.AddWarning(fmt.Sprintf(
|
||||||
|
"Given explicit max TTL of %d is greater than system/mount allowed value of %d seconds; until this is fixed attempting to create tokens against this role will result in an error",
|
||||||
|
entry.ExplicitMaxTTL.Seconds(), sysView.MaxLeaseTTL().Seconds()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pathSuffixInt, ok := data.GetOk("path_suffix")
|
pathSuffixInt, ok := data.GetOk("path_suffix")
|
||||||
if ok {
|
if ok {
|
||||||
pathSuffix := pathSuffixInt.(string)
|
pathSuffix := pathSuffixInt.(string)
|
||||||
if pathSuffix != "" {
|
if pathSuffix != "" {
|
||||||
matched := pathSuffixSanitize.MatchString(pathSuffix)
|
matched := pathSuffixSanitize.MatchString(pathSuffix)
|
||||||
if !matched {
|
if !matched {
|
||||||
return logical.ErrorResponse(fmt.Sprintf("given role path suffix contains invalid characters; must match %s", pathSuffixSanitize.String())), nil
|
return logical.ErrorResponse(fmt.Sprintf(
|
||||||
|
"given role path suffix contains invalid characters; must match %s",
|
||||||
|
pathSuffixSanitize.String())), nil
|
||||||
}
|
}
|
||||||
entry.PathSuffix = pathSuffix
|
entry.PathSuffix = pathSuffix
|
||||||
}
|
}
|
||||||
|
@ -1485,6 +1559,12 @@ func (ts *TokenStore) tokenStoreRoleCreateUpdate(
|
||||||
entry.AllowedPolicies = strings.Split(data.Get("allowed_policies").(string), ",")
|
entry.AllowedPolicies = strings.Split(data.Get("allowed_policies").(string), ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explicit max TTLs and periods cannot be used at the same time since the
|
||||||
|
// purpose of a periodic token is to escape max TTL semantics
|
||||||
|
if entry.Period > 0 && entry.ExplicitMaxTTL > 0 {
|
||||||
|
return logical.ErrorResponse("a role cannot be used to issue both periodic tokens and tokens with explicit max TTLs"), logical.ErrInvalidRequest
|
||||||
|
}
|
||||||
|
|
||||||
// Store it
|
// Store it
|
||||||
jsonEntry, err := logical.StorageEntryJSON(fmt.Sprintf("%s%s", rolesPrefix, name), entry)
|
jsonEntry, err := logical.StorageEntryJSON(fmt.Sprintf("%s%s", rolesPrefix, name), entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1494,7 +1574,7 @@ func (ts *TokenStore) tokenStoreRoleCreateUpdate(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1533,5 +1613,11 @@ will contain the given suffix as a part of
|
||||||
their path. This can be used to assist use
|
their path. This can be used to assist use
|
||||||
of the 'revoke-prefix' endpoint later on.
|
of the 'revoke-prefix' endpoint later on.
|
||||||
The given suffix must match the regular
|
The given suffix must match the regular
|
||||||
expression `
|
expression.`
|
||||||
|
tokenExplicitMaxTTLHelp = `If set, tokens created via this role
|
||||||
|
carry an explicit maximum TTL. During renewal,
|
||||||
|
the current maximum TTL values of the role
|
||||||
|
and the mount are not checked for changes,
|
||||||
|
and any updates to these values will have
|
||||||
|
no effect on the token being renewed.`
|
||||||
)
|
)
|
||||||
|
|
|
@ -979,6 +979,7 @@ func TestTokenStore_HandleRequest_Lookup(t *testing.T) {
|
||||||
"creation_ttl": int64(0),
|
"creation_ttl": int64(0),
|
||||||
"ttl": int64(0),
|
"ttl": int64(0),
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"explicit_max_ttl": int64(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Data["creation_time"].(int64) == 0 {
|
if resp.Data["creation_time"].(int64) == 0 {
|
||||||
|
@ -1014,6 +1015,7 @@ func TestTokenStore_HandleRequest_Lookup(t *testing.T) {
|
||||||
"creation_ttl": int64(3600),
|
"creation_ttl": int64(3600),
|
||||||
"ttl": int64(3600),
|
"ttl": int64(3600),
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"explicit_max_ttl": int64(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Data["creation_time"].(int64) == 0 {
|
if resp.Data["creation_time"].(int64) == 0 {
|
||||||
|
@ -1055,6 +1057,7 @@ func TestTokenStore_HandleRequest_Lookup(t *testing.T) {
|
||||||
"creation_ttl": int64(3600),
|
"creation_ttl": int64(3600),
|
||||||
"ttl": int64(3600),
|
"ttl": int64(3600),
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"explicit_max_ttl": int64(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Data["creation_time"].(int64) == 0 {
|
if resp.Data["creation_time"].(int64) == 0 {
|
||||||
|
@ -1119,6 +1122,7 @@ func TestTokenStore_HandleRequest_LookupSelf(t *testing.T) {
|
||||||
"creation_ttl": int64(0),
|
"creation_ttl": int64(0),
|
||||||
"ttl": int64(0),
|
"ttl": int64(0),
|
||||||
"role": "",
|
"role": "",
|
||||||
|
"explicit_max_ttl": int64(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.Data["creation_time"].(int64) == 0 {
|
if resp.Data["creation_time"].(int64) == 0 {
|
||||||
|
@ -1265,9 +1269,10 @@ func TestTokenStore_RoleCRUD(t *testing.T) {
|
||||||
expected := map[string]interface{}{
|
expected := map[string]interface{}{
|
||||||
"name": "test",
|
"name": "test",
|
||||||
"orphan": true,
|
"orphan": true,
|
||||||
"period": float64(259200),
|
"period": int64(259200),
|
||||||
"allowed_policies": []string{"test1", "test2"},
|
"allowed_policies": []string{"test1", "test2"},
|
||||||
"path_suffix": "happenin",
|
"path_suffix": "happenin",
|
||||||
|
"explicit_max_ttl": int64(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, resp.Data) {
|
if !reflect.DeepEqual(expected, resp.Data) {
|
||||||
|
@ -1305,9 +1310,57 @@ func TestTokenStore_RoleCRUD(t *testing.T) {
|
||||||
expected = map[string]interface{}{
|
expected = map[string]interface{}{
|
||||||
"name": "test",
|
"name": "test",
|
||||||
"orphan": true,
|
"orphan": true,
|
||||||
"period": float64(284400),
|
"period": int64(284400),
|
||||||
"allowed_policies": []string{"test3"},
|
"allowed_policies": []string{"test3"},
|
||||||
"path_suffix": "happenin",
|
"path_suffix": "happenin",
|
||||||
|
"explicit_max_ttl": int64(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected, resp.Data) {
|
||||||
|
t.Fatalf("expected:\n%v\nactual:\n%v\n", expected, resp.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now test setting explicit max ttl at the same time as period, which
|
||||||
|
// should be an error
|
||||||
|
req.Operation = logical.CreateOperation
|
||||||
|
req.Data = map[string]interface{}{
|
||||||
|
"explicit_max_ttl": "5",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now set explicit max ttl and clear the period
|
||||||
|
req.Operation = logical.CreateOperation
|
||||||
|
req.Data = map[string]interface{}{
|
||||||
|
"explicit_max_ttl": "5",
|
||||||
|
"period": "0s",
|
||||||
|
}
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v %v", err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Operation = logical.ReadOperation
|
||||||
|
req.Data = map[string]interface{}{}
|
||||||
|
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v %v", err, resp)
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatalf("got a nil response")
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = map[string]interface{}{
|
||||||
|
"name": "test",
|
||||||
|
"orphan": true,
|
||||||
|
"explicit_max_ttl": int64(5),
|
||||||
|
"allowed_policies": []string{"test3"},
|
||||||
|
"path_suffix": "happenin",
|
||||||
|
"period": int64(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, resp.Data) {
|
if !reflect.DeepEqual(expected, resp.Data) {
|
||||||
|
@ -1623,3 +1676,205 @@ func TestTokenStore_RolePeriod(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTokenStore_RoleExplicitMaxTTL(t *testing.T) {
|
||||||
|
core, _, _, root := TestCoreWithTokenStore(t)
|
||||||
|
|
||||||
|
core.defaultLeaseTTL = 5 * time.Second
|
||||||
|
core.maxLeaseTTL = 5 * time.Hour
|
||||||
|
|
||||||
|
// Note: these requests are sent to Core since Core handles registration
|
||||||
|
// with the expiration manager and we need the storage to be consistent
|
||||||
|
|
||||||
|
// Make sure we can't make it larger than the system/mount max; we should get a warning on role write and an error on token creation
|
||||||
|
req := logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
|
||||||
|
req.ClientToken = root
|
||||||
|
req.Data = map[string]interface{}{
|
||||||
|
"explicit_max_ttl": "100h",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v %v", err, resp)
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatalf("expected a warning")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Operation = logical.UpdateOperation
|
||||||
|
req.Path = "auth/token/create/test"
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to a good explicit max
|
||||||
|
req = logical.TestRequest(t, logical.UpdateOperation, "auth/token/roles/test")
|
||||||
|
req.ClientToken = root
|
||||||
|
req.Data = map[string]interface{}{
|
||||||
|
"explicit_max_ttl": "6s",
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v %v", err, resp)
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
t.Fatalf("expected a nil response")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This first set of logic is to verify that a normal non-root token will
|
||||||
|
// be given a TTL of 5 seconds, and that renewing will cause the TTL to
|
||||||
|
// increase
|
||||||
|
{
|
||||||
|
req.Path = "auth/token/create"
|
||||||
|
req.Data = map[string]interface{}{
|
||||||
|
"policies": []string{"default"},
|
||||||
|
}
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v %v", err, resp)
|
||||||
|
}
|
||||||
|
if resp.Auth.ClientToken == "" {
|
||||||
|
t.Fatalf("bad: %#v", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.ClientToken = resp.Auth.ClientToken
|
||||||
|
req.Operation = logical.ReadOperation
|
||||||
|
req.Path = "auth/token/lookup-self"
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
ttl := resp.Data["ttl"].(int64)
|
||||||
|
if ttl > 5 {
|
||||||
|
t.Fatalf("TTL too large")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the TTL go down a bit to 3 seconds
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
req.Operation = logical.UpdateOperation
|
||||||
|
req.Path = "auth/token/renew-self"
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v %v", err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Operation = logical.ReadOperation
|
||||||
|
req.Path = "auth/token/lookup-self"
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
ttl = resp.Data["ttl"].(int64)
|
||||||
|
if ttl < 4 {
|
||||||
|
t.Fatalf("TTL too small after renewal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we create a token against the role. After renew our max should still
|
||||||
|
// be the same.
|
||||||
|
{
|
||||||
|
req.ClientToken = root
|
||||||
|
req.Operation = logical.UpdateOperation
|
||||||
|
req.Path = "auth/token/create/test"
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v %v", err, resp)
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("response was nil")
|
||||||
|
}
|
||||||
|
if resp.Auth == nil {
|
||||||
|
t.Fatal(fmt.Sprintf("response auth was nil, resp is %#v", *resp))
|
||||||
|
}
|
||||||
|
if resp.Auth.ClientToken == "" {
|
||||||
|
t.Fatalf("bad: %#v", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.ClientToken = resp.Auth.ClientToken
|
||||||
|
req.Operation = logical.ReadOperation
|
||||||
|
req.Path = "auth/token/lookup-self"
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
ttl := resp.Data["ttl"].(int64)
|
||||||
|
if ttl > 6 {
|
||||||
|
t.Fatalf("TTL too big")
|
||||||
|
}
|
||||||
|
maxTTL := resp.Data["explicit_max_ttl"].(int64)
|
||||||
|
if maxTTL != 6 {
|
||||||
|
t.Fatalf("expected 6 for explicit max TTL, got %d", maxTTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the TTL go down a bit to 3 seconds (4 against explicit max)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
req.Operation = logical.UpdateOperation
|
||||||
|
req.Path = "auth/token/renew-self"
|
||||||
|
req.Data = map[string]interface{}{
|
||||||
|
"increment": 300,
|
||||||
|
}
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v %v", err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Operation = logical.ReadOperation
|
||||||
|
req.Path = "auth/token/lookup-self"
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
ttl = resp.Data["ttl"].(int64)
|
||||||
|
if ttl > 4 {
|
||||||
|
t.Fatalf("TTL too big")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the TTL go down a bit more to 2 seconds (2 against explicit max)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
req.Operation = logical.UpdateOperation
|
||||||
|
req.Path = "auth/token/renew-self"
|
||||||
|
req.Data = map[string]interface{}{
|
||||||
|
"increment": 300,
|
||||||
|
}
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v %v", err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Operation = logical.ReadOperation
|
||||||
|
req.Path = "auth/token/lookup-self"
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
ttl = resp.Data["ttl"].(int64)
|
||||||
|
if ttl > 2 {
|
||||||
|
t.Fatalf("TTL too big")
|
||||||
|
}
|
||||||
|
|
||||||
|
// It should expire
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
req.Operation = logical.UpdateOperation
|
||||||
|
req.Path = "auth/token/renew-self"
|
||||||
|
req.Data = map[string]interface{}{
|
||||||
|
"increment": 300,
|
||||||
|
}
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Operation = logical.ReadOperation
|
||||||
|
req.Path = "auth/token/lookup-self"
|
||||||
|
resp, err = core.HandleRequest(req)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -601,7 +601,7 @@ of the header should be "X-Vault-Token" and the value should be the token.
|
||||||
each renewal. So long as they continue to be renewed, they will never
|
each renewal. So long as they continue to be renewed, they will never
|
||||||
expire. The parameter is an integer duration of seconds. Tokens issued
|
expire. The parameter is an integer duration of seconds. Tokens issued
|
||||||
track updates to the role value; the new period takes effect upon next
|
track updates to the role value; the new period takes effect upon next
|
||||||
renew.
|
renew. This cannot be used in conjunction with `explicit_max_ttl`.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="param">path_suffix</span>
|
<span class="param">path_suffix</span>
|
||||||
|
@ -614,6 +614,16 @@ of the header should be "X-Vault-Token" and the value should be the token.
|
||||||
part of their path, and then tokens with the old suffix can be revoked
|
part of their path, and then tokens with the old suffix can be revoked
|
||||||
via `sys/revoke-prefix`.
|
via `sys/revoke-prefix`.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="param">explicit_max_ttl</span>
|
||||||
|
<span class="param-flags">optional</span>
|
||||||
|
If set, tokens created with this role have an explicit max TTL set upon
|
||||||
|
them. This maximum token TTL *cannot* be changed later, and unlike with
|
||||||
|
normal tokens, updates to the role or the system/mount max TTL value
|
||||||
|
will have no effect at renewal time -- the token will never be able to
|
||||||
|
be renewed or used past the value set at issue time. This cannot be
|
||||||
|
used in conjunction with `period`.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue