Allow globbing dis/allowed_policies_glob in token roles (#7277)

* Add allowed_policies_glob and disallowed_policies_glob that are the same as allowed_policies and disallowed_policies but allow glob matching.

* Update changelog, docs, tests, and comments for (dis)allowed_token_glob token role feature.

* Improve docs and unit tests for auth/token role policy globbing.
This commit is contained in:
Tiernan 2021-09-22 01:25:06 +10:00 committed by GitHub
parent 8a0250d277
commit a538936367
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 468 additions and 124 deletions

3
changelog/7277.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
auth/token: Add `allowed_policies_glob` and `disallowed_policies_glob` fields to token roles to allow glob matching of policies
```

View File

@ -392,6 +392,16 @@ func (ts *TokenStore) paths() []*framework.Path {
Description: tokenDisallowedPoliciesHelp, Description: tokenDisallowedPoliciesHelp,
}, },
"allowed_policies_glob": {
Type: framework.TypeCommaStringSlice,
Description: tokenAllowedPoliciesGlobHelp,
},
"disallowed_policies_glob": {
Type: framework.TypeCommaStringSlice,
Description: tokenDisallowedPoliciesGlobHelp,
},
"orphan": { "orphan": {
Type: framework.TypeBool, Type: framework.TypeBool,
Description: tokenOrphanHelp, Description: tokenOrphanHelp,
@ -623,6 +633,12 @@ type tsRoleEntry struct {
// List of policies to be not allowed during token creation using this role // List of policies to be not allowed during token creation using this role
DisallowedPolicies []string `json:"disallowed_policies" mapstructure:"disallowed_policies" structs:"disallowed_policies"` DisallowedPolicies []string `json:"disallowed_policies" mapstructure:"disallowed_policies" structs:"disallowed_policies"`
// An extension to AllowedPolicies that instead uses glob matching on policy names
AllowedPoliciesGlob []string `json:"allowed_policies_glob" mapstructure:"allowed_policies_glob" structs:"allowed_policies_glob"`
// An extension to DisallowedPolicies that instead uses glob matching on policy names
DisallowedPoliciesGlob []string `json:"disallowed_policies_glob" mapstructure:"disallowed_policies_glob" structs:"disallowed_policies_glob"`
// If true, tokens created using this role will be orphans // If true, tokens created using this role will be orphans
Orphan bool `json:"orphan" mapstructure:"orphan" structs:"orphan"` Orphan bool `json:"orphan" mapstructure:"orphan" structs:"orphan"`
@ -2475,7 +2491,8 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque
// and shouldn't be added is kept because we want to do subset comparisons // and shouldn't be added is kept because we want to do subset comparisons
// based on adding default when it's correct to do so. // based on adding default when it's correct to do so.
switch { switch {
case role != nil && (len(role.AllowedPolicies) > 0 || len(role.DisallowedPolicies) > 0): case role != nil && (len(role.AllowedPolicies) > 0 || len(role.DisallowedPolicies) > 0 ||
len(role.AllowedPoliciesGlob) > 0 || len(role.DisallowedPoliciesGlob) > 0):
// Holds the final set of policies as they get munged // Holds the final set of policies as they get munged
var finalPolicies []string var finalPolicies []string
@ -2487,7 +2504,9 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque
// isn't in the disallowed list, add it. This is in line with the idea // isn't in the disallowed list, add it. This is in line with the idea
// that roles, when allowed/disallowed ar set, allow a subset of // that roles, when allowed/disallowed ar set, allow a subset of
// policies to be set disjoint from the parent token's policies. // policies to be set disjoint from the parent token's policies.
if !data.NoDefaultPolicy && !role.TokenNoDefaultPolicy && !strutil.StrListContains(role.DisallowedPolicies, "default") { if !data.NoDefaultPolicy && !role.TokenNoDefaultPolicy &&
!strutil.StrListContains(role.DisallowedPolicies, "default") &&
!strutil.StrListContainsGlob(role.DisallowedPoliciesGlob, "default") {
localAddDefault = true localAddDefault = true
} }
@ -2496,12 +2515,12 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque
finalPolicies = policyutil.SanitizePolicies(data.Policies, localAddDefault) finalPolicies = policyutil.SanitizePolicies(data.Policies, localAddDefault)
} }
var sanitizedRolePolicies []string var sanitizedRolePolicies, sanitizedRolePoliciesGlob []string
// First check allowed policies; if policies are specified they will be // First check allowed policies; if policies are specified they will be
// checked, otherwise if an allowed set exists that will be the set // checked, otherwise if an allowed set exists that will be the set
// that is used // that is used
if len(role.AllowedPolicies) > 0 { if len(role.AllowedPolicies) > 0 || len(role.AllowedPoliciesGlob) > 0 {
// Note that if "default" is already in allowed, and also in // Note that if "default" is already in allowed, and also in
// disallowed, this will still result in an error later since this // disallowed, this will still result in an error later since this
// doesn't strip out default // doesn't strip out default
@ -2510,8 +2529,13 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque
if len(finalPolicies) == 0 { if len(finalPolicies) == 0 {
finalPolicies = sanitizedRolePolicies finalPolicies = sanitizedRolePolicies
} else { } else {
if !strutil.StrListSubset(sanitizedRolePolicies, finalPolicies) { sanitizedRolePoliciesGlob = policyutil.SanitizePolicies(role.AllowedPoliciesGlob, false)
return logical.ErrorResponse(fmt.Sprintf("token policies (%q) must be subset of the role's allowed policies (%q)", finalPolicies, sanitizedRolePolicies)), logical.ErrInvalidRequest
for _, finalPolicy := range finalPolicies {
if !strutil.StrListContains(sanitizedRolePolicies, finalPolicy) &&
!strutil.StrListContainsGlob(sanitizedRolePoliciesGlob, finalPolicy) {
return logical.ErrorResponse(fmt.Sprintf("token policies (%q) must be subset of the role's allowed policies (%q) or glob policies (%q)", finalPolicies, sanitizedRolePolicies, sanitizedRolePoliciesGlob)), logical.ErrInvalidRequest
}
} }
} }
} else { } else {
@ -2522,12 +2546,14 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque
} }
} }
if len(role.DisallowedPolicies) > 0 { if len(role.DisallowedPolicies) > 0 || len(role.DisallowedPoliciesGlob) > 0 {
// We don't add the default here because we only want to disallow it if it's explicitly set // We don't add the default here because we only want to disallow it if it's explicitly set
sanitizedRolePolicies = strutil.RemoveDuplicates(role.DisallowedPolicies, true) sanitizedRolePolicies = strutil.RemoveDuplicates(role.DisallowedPolicies, true)
sanitizedRolePoliciesGlob = strutil.RemoveDuplicates(role.DisallowedPoliciesGlob, true)
for _, finalPolicy := range finalPolicies { for _, finalPolicy := range finalPolicies {
if strutil.StrListContains(sanitizedRolePolicies, finalPolicy) { if strutil.StrListContains(sanitizedRolePolicies, finalPolicy) ||
strutil.StrListContainsGlob(sanitizedRolePoliciesGlob, finalPolicy) {
return logical.ErrorResponse(fmt.Sprintf("token policy %q is disallowed by this role", finalPolicy)), logical.ErrInvalidRequest return logical.ErrorResponse(fmt.Sprintf("token policy %q is disallowed by this role", finalPolicy)), logical.ErrInvalidRequest
} }
} }
@ -3183,18 +3209,20 @@ func (ts *TokenStore) tokenStoreRoleRead(ctx context.Context, req *logical.Reque
// TODO (1.4): Remove "period" and "explicit_max_ttl" if they're zero // TODO (1.4): Remove "period" and "explicit_max_ttl" if they're zero
resp := &logical.Response{ resp := &logical.Response{
Data: map[string]interface{}{ Data: map[string]interface{}{
"period": int64(role.Period.Seconds()), "period": int64(role.Period.Seconds()),
"token_period": int64(role.TokenPeriod.Seconds()), "token_period": int64(role.TokenPeriod.Seconds()),
"explicit_max_ttl": int64(role.ExplicitMaxTTL.Seconds()), "explicit_max_ttl": int64(role.ExplicitMaxTTL.Seconds()),
"token_explicit_max_ttl": int64(role.TokenExplicitMaxTTL.Seconds()), "token_explicit_max_ttl": int64(role.TokenExplicitMaxTTL.Seconds()),
"disallowed_policies": role.DisallowedPolicies, "disallowed_policies": role.DisallowedPolicies,
"allowed_policies": role.AllowedPolicies, "allowed_policies": role.AllowedPolicies,
"name": role.Name, "disallowed_policies_glob": role.DisallowedPoliciesGlob,
"orphan": role.Orphan, "allowed_policies_glob": role.AllowedPoliciesGlob,
"path_suffix": role.PathSuffix, "name": role.Name,
"renewable": role.Renewable, "orphan": role.Orphan,
"token_type": role.TokenType.String(), "path_suffix": role.PathSuffix,
"allowed_entity_aliases": role.AllowedEntityAliases, "renewable": role.Renewable,
"token_type": role.TokenType.String(),
"allowed_entity_aliases": role.AllowedEntityAliases,
}, },
} }
@ -3292,6 +3320,20 @@ func (ts *TokenStore) tokenStoreRoleCreateUpdate(ctx context.Context, req *logic
} else if req.Operation == logical.CreateOperation { } else if req.Operation == logical.CreateOperation {
entry.DisallowedPolicies = strutil.RemoveDuplicates(data.Get("disallowed_policies").([]string), true) entry.DisallowedPolicies = strutil.RemoveDuplicates(data.Get("disallowed_policies").([]string), true)
} }
allowedPoliciesGlobRaw, ok := data.GetOk("allowed_policies_glob")
if ok {
entry.AllowedPoliciesGlob = policyutil.SanitizePolicies(allowedPoliciesGlobRaw.([]string), policyutil.DoNotAddDefaultPolicy)
} else if req.Operation == logical.CreateOperation {
entry.AllowedPoliciesGlob = policyutil.SanitizePolicies(data.Get("allowed_policies_glob").([]string), policyutil.DoNotAddDefaultPolicy)
}
disallowedPoliciesGlobRaw, ok := data.GetOk("disallowed_policies_glob")
if ok {
entry.DisallowedPoliciesGlob = strutil.RemoveDuplicates(disallowedPoliciesGlobRaw.([]string), true)
} else if req.Operation == logical.CreateOperation {
entry.DisallowedPoliciesGlob = strutil.RemoveDuplicates(data.Get("disallowed_policies_glob").([]string), true)
}
} }
// We handle token type a bit differently than tokenutil does so we need to // We handle token type a bit differently than tokenutil does so we need to
@ -3779,6 +3821,13 @@ calling token's policies. The parameter is a comma-delimited string of
policy names.` policy names.`
tokenDisallowedPoliciesHelp = `If set, successful token creation via this role will require that tokenDisallowedPoliciesHelp = `If set, successful token creation via this role will require that
no policies in the given list are requested. The parameter is a comma-delimited string of policy names.` no policies in the given list are requested. The parameter is a comma-delimited string of policy names.`
tokenAllowedPoliciesGlobHelp = `If set, tokens can be created with any subset of glob matched policies in this
list, rather than the normal semantics of tokens being a subset of the
calling token's policies. The parameter is a comma-delimited string of
policy name globs.`
tokenDisallowedPoliciesGlobHelp = `If set, successful token creation via this role will require that
no requested policies glob match any of policies in this list.
The parameter is a comma-delimited string of policy name globs.`
tokenOrphanHelp = `If true, tokens created via this role tokenOrphanHelp = `If true, tokens created via this role
will be orphan tokens (have no parent)` will be orphan tokens (have no parent)`
tokenPeriodHelp = `If set, tokens created via this role tokenPeriodHelp = `If set, tokens created via this role

View File

@ -3179,19 +3179,21 @@ func TestTokenStore_RoleCRUD(t *testing.T) {
} }
expected := map[string]interface{}{ expected := map[string]interface{}{
"name": "test", "name": "test",
"orphan": true, "orphan": true,
"token_period": int64(259200), "token_period": int64(259200),
"period": int64(259200), "period": int64(259200),
"allowed_policies": []string{"test1", "test2"}, "allowed_policies": []string{"test1", "test2"},
"disallowed_policies": []string{}, "disallowed_policies": []string{},
"path_suffix": "happenin", "allowed_policies_glob": []string{},
"explicit_max_ttl": int64(7200), "disallowed_policies_glob": []string{},
"token_explicit_max_ttl": int64(7200), "path_suffix": "happenin",
"renewable": true, "explicit_max_ttl": int64(7200),
"token_type": "default-service", "token_explicit_max_ttl": int64(7200),
"token_num_uses": 123, "renewable": true,
"allowed_entity_aliases": []string(nil), "token_type": "default-service",
"token_num_uses": 123,
"allowed_entity_aliases": []string(nil),
} }
if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" { if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
@ -3240,18 +3242,20 @@ func TestTokenStore_RoleCRUD(t *testing.T) {
} }
expected = map[string]interface{}{ expected = map[string]interface{}{
"name": "test", "name": "test",
"orphan": true, "orphan": true,
"period": int64(284400), "period": int64(284400),
"token_period": int64(284400), "token_period": int64(284400),
"allowed_policies": []string{"test3"}, "allowed_policies": []string{"test3"},
"disallowed_policies": []string{}, "disallowed_policies": []string{},
"path_suffix": "happenin", "allowed_policies_glob": []string{},
"token_explicit_max_ttl": int64(288000), "disallowed_policies_glob": []string{},
"explicit_max_ttl": int64(288000), "path_suffix": "happenin",
"renewable": false, "token_explicit_max_ttl": int64(288000),
"token_type": "default-service", "explicit_max_ttl": int64(288000),
"allowed_entity_aliases": []string(nil), "renewable": false,
"token_type": "default-service",
"allowed_entity_aliases": []string(nil),
} }
if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" { if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
@ -3290,18 +3294,20 @@ func TestTokenStore_RoleCRUD(t *testing.T) {
} }
expected = map[string]interface{}{ expected = map[string]interface{}{
"name": "test", "name": "test",
"orphan": true, "orphan": true,
"explicit_max_ttl": int64(5), "explicit_max_ttl": int64(5),
"token_explicit_max_ttl": int64(5), "token_explicit_max_ttl": int64(5),
"allowed_policies": []string{"test3"}, "allowed_policies": []string{"test3"},
"disallowed_policies": []string{}, "disallowed_policies": []string{},
"path_suffix": "happenin", "allowed_policies_glob": []string{},
"period": int64(0), "disallowed_policies_glob": []string{},
"token_period": int64(0), "path_suffix": "happenin",
"renewable": false, "period": int64(0),
"token_type": "default-service", "token_period": int64(0),
"allowed_entity_aliases": []string(nil), "renewable": false,
"token_type": "default-service",
"allowed_entity_aliases": []string(nil),
} }
if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" { if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "0.0.0.0/0" {
@ -3340,18 +3346,20 @@ func TestTokenStore_RoleCRUD(t *testing.T) {
} }
expected = map[string]interface{}{ expected = map[string]interface{}{
"name": "test", "name": "test",
"orphan": true, "orphan": true,
"token_explicit_max_ttl": int64(5), "token_explicit_max_ttl": int64(5),
"explicit_max_ttl": int64(5), "explicit_max_ttl": int64(5),
"allowed_policies": []string{"test3"}, "allowed_policies": []string{"test3"},
"disallowed_policies": []string{}, "disallowed_policies": []string{},
"path_suffix": "", "allowed_policies_glob": []string{},
"period": int64(0), "disallowed_policies_glob": []string{},
"token_period": int64(0), "path_suffix": "",
"renewable": false, "period": int64(0),
"token_type": "default-service", "token_period": int64(0),
"allowed_entity_aliases": []string(nil), "renewable": false,
"token_type": "default-service",
"allowed_entity_aliases": []string(nil),
} }
if diff := deep.Equal(expected, resp.Data); diff != nil { if diff := deep.Equal(expected, resp.Data); diff != nil {
@ -3495,6 +3503,17 @@ func TestTokenStore_RoleDisallowedPolicies(t *testing.T) {
t.Fatalf("err:%v resp:%v", err, resp) t.Fatalf("err:%v resp:%v", err, resp)
} }
// policy containing a glob character in the non-glob disallowed_policies field
req = logical.TestRequest(t, logical.UpdateOperation, "roles/testglobdisabled")
req.ClientToken = root
req.Data = map[string]interface{}{
"disallowed_policies": "test*",
}
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
// Create a token that has all the policies defined above // Create a token that has all the policies defined above
req = logical.TestRequest(t, logical.UpdateOperation, "create") req = logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root req.ClientToken = root
@ -3508,6 +3527,7 @@ func TestTokenStore_RoleDisallowedPolicies(t *testing.T) {
} }
parentToken := resp.Auth.ClientToken parentToken := resp.Auth.ClientToken
// Test that the parent token's policies are rejected by disallowed_policies
req = logical.TestRequest(t, logical.UpdateOperation, "create/test1") req = logical.TestRequest(t, logical.UpdateOperation, "create/test1")
req.ClientToken = parentToken req.ClientToken = parentToken
resp, err = ts.HandleRequest(namespace.RootContext(nil), req) resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
@ -3536,6 +3556,21 @@ func TestTokenStore_RoleDisallowedPolicies(t *testing.T) {
req.ClientToken = parentToken req.ClientToken = parentToken
testMakeTokenViaRequest(t, ts, req) testMakeTokenViaRequest(t, ts, req)
// Check to be sure 'test*' without globbing matches 'test*'
req = logical.TestRequest(t, logical.UpdateOperation, "create/testglobdisabled")
req.Data["policies"] = []string{"test*"}
req.ClientToken = parentToken
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err == nil || resp != nil && !resp.IsError() {
t.Fatalf("expected an error response, got %#v", resp)
}
// Check to be sure 'test*' without globbing doesn't match 'test1' or 'test'
req = logical.TestRequest(t, logical.UpdateOperation, "create/testglobdisabled")
req.Data["policies"] = []string{"test1", "test"}
req.ClientToken = parentToken
testMakeTokenViaRequest(t, ts, req)
// Create a role to have 'default' policy disallowed // Create a role to have 'default' policy disallowed
req = logical.TestRequest(t, logical.UpdateOperation, "roles/default") req = logical.TestRequest(t, logical.UpdateOperation, "roles/default")
req.ClientToken = root req.ClientToken = root
@ -3588,6 +3623,40 @@ func TestTokenStore_RoleAllowedPolicies(t *testing.T) {
t.Fatalf("bad: %#v", resp) t.Fatalf("bad: %#v", resp)
} }
// test not glob matching when using allowed_policies instead of allowed_policies_glob
req = logical.TestRequest(t, logical.UpdateOperation, "roles/testnoglob")
req.ClientToken = root
req.Data = map[string]interface{}{
"allowed_policies": "test*",
}
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req.Path = "create/testnoglob"
req.Data["policies"] = []string{"test"}
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err == nil {
t.Fatalf("expected error")
}
req.Data["policies"] = []string{"testfoo"}
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err == nil {
t.Fatalf("expected error")
}
req.Data["policies"] = []string{"test*"}
resp = testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
// When allowed_policies is blank, should fall back to a subset of the parent policies // When allowed_policies is blank, should fall back to a subset of the parent policies
req = logical.TestRequest(t, logical.UpdateOperation, "roles/test") req = logical.TestRequest(t, logical.UpdateOperation, "roles/test")
req.ClientToken = root req.ClientToken = root
@ -3643,6 +3712,201 @@ func TestTokenStore_RoleAllowedPolicies(t *testing.T) {
} }
} }
func TestTokenStore_RoleDisallowedPoliciesGlob(t *testing.T) {
var req *logical.Request
var resp *logical.Response
var err error
core, _, root := TestCoreUnsealed(t)
ts := core.tokenStore
ps := core.policyStore
// Create 4 different policies
policy, _ := ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
policy.Name = "test1"
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
t.Fatal(err)
}
policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
policy.Name = "test2"
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
t.Fatal(err)
}
policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
policy.Name = "test3"
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
t.Fatal(err)
}
policy, _ = ParseACLPolicy(namespace.RootNamespace, tokenCreationPolicy)
policy.Name = "test3b"
if err := ps.SetPolicy(namespace.RootContext(nil), policy); err != nil {
t.Fatal(err)
}
// Create roles with different disallowed_policies configuration
req = logical.TestRequest(t, logical.UpdateOperation, "roles/test1")
req.ClientToken = root
req.Data = map[string]interface{}{
"disallowed_policies_glob": "test1",
}
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "roles/testnot23")
req.ClientToken = root
req.Data = map[string]interface{}{
"disallowed_policies_glob": "test2,test3*",
}
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
// Create a token that has all the policies defined above
req = logical.TestRequest(t, logical.UpdateOperation, "create")
req.ClientToken = root
req.Data["policies"] = []string{"test1", "test2", "test3", "test3b"}
resp = testMakeTokenViaRequest(t, ts, req)
if resp == nil || resp.Auth == nil {
t.Fatal("got nil response")
}
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: ClientToken; resp:%#v", resp)
}
parentToken := resp.Auth.ClientToken
// Test that the parent token's policies are rejected by disallowed_policies
req = logical.TestRequest(t, logical.UpdateOperation, "create/test1")
req.ClientToken = parentToken
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err == nil || resp != nil && !resp.IsError() {
t.Fatalf("expected an error response, got %#v", resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
req.ClientToken = parentToken
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err == nil || resp != nil && !resp.IsError() {
t.Fatalf("expected an error response, got %#v", resp)
}
// Disallowed should act as a blacklist so make sure we can still make
// something with other policies in the request
req = logical.TestRequest(t, logical.UpdateOperation, "create/test1")
req.Data["policies"] = []string{"foo", "bar"}
req.ClientToken = parentToken
testMakeTokenViaRequest(t, ts, req)
// Check to be sure 'test3*' matches 'test3'
req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
req.Data["policies"] = []string{"test3"}
req.ClientToken = parentToken
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err == nil || resp != nil && !resp.IsError() {
t.Fatalf("expected an error response, got %#v", resp)
}
// Check to be sure 'test3*' matches 'test3b'
req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
req.Data["policies"] = []string{"test3b"}
req.ClientToken = parentToken
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err == nil || resp != nil && !resp.IsError() {
t.Fatalf("expected an error response, got %#v", resp)
}
// Check that non-blacklisted policies still work
req = logical.TestRequest(t, logical.UpdateOperation, "create/testnot23")
req.Data["policies"] = []string{"test1"}
req.ClientToken = parentToken
testMakeTokenViaRequest(t, ts, req)
// Create a role to have 'default' policy disallowed
req = logical.TestRequest(t, logical.UpdateOperation, "roles/default")
req.ClientToken = root
req.Data = map[string]interface{}{
"disallowed_policies_glob": "default",
}
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%v", err, resp)
}
req = logical.TestRequest(t, logical.UpdateOperation, "create/default")
req.ClientToken = parentToken
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err == nil || resp != nil && !resp.IsError() {
t.Fatal("expected an error response")
}
}
func TestTokenStore_RoleAllowedPoliciesGlob(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore
// test literal matching works in allowed_policies_glob
req := logical.TestRequest(t, logical.UpdateOperation, "roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"allowed_policies_glob": "test1,test2",
}
resp, err := ts.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req.Data = map[string]interface{}{}
req.Path = "create/test"
req.Data["policies"] = []string{"foo"}
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err == nil {
t.Fatalf("expected error")
}
req.Data["policies"] = []string{"test2"}
resp = testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
// test glob matching in allowed_policies_glob
req = logical.TestRequest(t, logical.UpdateOperation, "roles/test")
req.ClientToken = root
req.Data = map[string]interface{}{
"allowed_policies_glob": "test*",
}
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err: %v\nresp: %#v", err, resp)
}
if resp != nil {
t.Fatalf("expected a nil response")
}
req.Path = "create/test"
req.Data["policies"] = []string{"footest"}
resp, err = ts.HandleRequest(namespace.RootContext(nil), req)
if err == nil {
t.Fatalf("expected error")
}
req.Data["policies"] = []string{"testfoo", "test2", "test"}
resp = testMakeTokenViaRequest(t, ts, req)
if resp.Auth.ClientToken == "" {
t.Fatalf("bad: %#v", resp)
}
}
func TestTokenStore_RoleOrphan(t *testing.T) { func TestTokenStore_RoleOrphan(t *testing.T) {
c, _, root := TestCoreUnsealed(t) c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore ts := c.tokenStore
@ -4150,18 +4414,20 @@ func TestTokenStore_RoleTokenFields(t *testing.T) {
} }
expected := map[string]interface{}{ expected := map[string]interface{}{
"name": "test", "name": "test",
"orphan": false, "orphan": false,
"period": int64(1), "period": int64(1),
"token_period": int64(1), "token_period": int64(1),
"allowed_policies": []string(nil), "allowed_policies": []string(nil),
"disallowed_policies": []string(nil), "disallowed_policies": []string(nil),
"path_suffix": "", "allowed_policies_glob": []string(nil),
"token_explicit_max_ttl": int64(3600), "disallowed_policies_glob": []string(nil),
"explicit_max_ttl": int64(3600), "path_suffix": "",
"renewable": false, "token_explicit_max_ttl": int64(3600),
"token_type": "batch", "explicit_max_ttl": int64(3600),
"allowed_entity_aliases": []string(nil), "renewable": false,
"token_type": "batch",
"allowed_entity_aliases": []string(nil),
} }
if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" { if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
@ -4203,18 +4469,20 @@ func TestTokenStore_RoleTokenFields(t *testing.T) {
} }
expected := map[string]interface{}{ expected := map[string]interface{}{
"name": "test", "name": "test",
"orphan": false, "orphan": false,
"period": int64(5), "period": int64(5),
"token_period": int64(5), "token_period": int64(5),
"allowed_policies": []string(nil), "allowed_policies": []string(nil),
"disallowed_policies": []string(nil), "disallowed_policies": []string(nil),
"path_suffix": "", "allowed_policies_glob": []string(nil),
"token_explicit_max_ttl": int64(7200), "disallowed_policies_glob": []string(nil),
"explicit_max_ttl": int64(7200), "path_suffix": "",
"renewable": false, "token_explicit_max_ttl": int64(7200),
"token_type": "default-service", "explicit_max_ttl": int64(7200),
"allowed_entity_aliases": []string(nil), "renewable": false,
"token_type": "default-service",
"allowed_entity_aliases": []string(nil),
} }
if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" { if resp.Data["bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
@ -4255,18 +4523,20 @@ func TestTokenStore_RoleTokenFields(t *testing.T) {
} }
expected := map[string]interface{}{ expected := map[string]interface{}{
"name": "test", "name": "test",
"orphan": false, "orphan": false,
"period": int64(0), "period": int64(0),
"token_period": int64(7), "token_period": int64(7),
"allowed_policies": []string(nil), "allowed_policies": []string(nil),
"disallowed_policies": []string(nil), "disallowed_policies": []string(nil),
"path_suffix": "", "allowed_policies_glob": []string(nil),
"token_explicit_max_ttl": int64(5200), "disallowed_policies_glob": []string(nil),
"explicit_max_ttl": int64(0), "path_suffix": "",
"renewable": false, "token_explicit_max_ttl": int64(5200),
"token_type": "default-service", "explicit_max_ttl": int64(0),
"allowed_entity_aliases": []string(nil), "renewable": false,
"token_type": "default-service",
"allowed_entity_aliases": []string(nil),
} }
if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" { if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {
@ -4309,18 +4579,20 @@ func TestTokenStore_RoleTokenFields(t *testing.T) {
} }
expected := map[string]interface{}{ expected := map[string]interface{}{
"name": "test", "name": "test",
"orphan": false, "orphan": false,
"period": int64(0), "period": int64(0),
"token_period": int64(5), "token_period": int64(5),
"allowed_policies": []string(nil), "allowed_policies": []string(nil),
"disallowed_policies": []string(nil), "disallowed_policies": []string(nil),
"path_suffix": "", "allowed_policies_glob": []string(nil),
"token_explicit_max_ttl": int64(7200), "disallowed_policies_glob": []string(nil),
"explicit_max_ttl": int64(0), "path_suffix": "",
"renewable": false, "token_explicit_max_ttl": int64(7200),
"token_type": "service", "explicit_max_ttl": int64(0),
"allowed_entity_aliases": []string(nil), "renewable": false,
"token_type": "service",
"allowed_entity_aliases": []string(nil),
} }
if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" { if resp.Data["token_bound_cidrs"].([]*sockaddr.SockAddrMarshaler)[0].String() != "127.0.0.1" {

View File

@ -627,6 +627,8 @@ $ curl \
], ],
"allowed_policies": [], "allowed_policies": [],
"disallowed_policies": [], "disallowed_policies": [],
"allowed_policies_glob": [],
"disallowed_policies_glob": [],
"explicit_max_ttl": 0, "explicit_max_ttl": 0,
"name": "nomad", "name": "nomad",
"orphan": false, "orphan": false,
@ -690,13 +692,31 @@ tokens created against a role to be revoked using the
tokens being a subset of the calling token's policies. The parameter is a tokens being a subset of the calling token's policies. The parameter is a
comma-delimited string of policy names. If at creation time comma-delimited string of policy names. If at creation time
`no_default_policy` is not set and `"default"` is not contained in `no_default_policy` is not set and `"default"` is not contained in
`disallowed_policies`, the `"default"` policy will be added to the created `disallowed_policies` or glob matched in `disallowed_policies_glob`,
token automatically. the `"default"` policy will be added to the created token automatically.
- `disallowed_policies` `(list: [])` If set, successful token creation via - `disallowed_policies` `(list: [])` If set, successful token creation via
this role will require that no policies in the given list are requested. The this role will require that no policies in the given list are requested. The
parameter is a comma-delimited string of policy names. Adding `"default"` to parameter is a comma-delimited string of policy names. Adding `"default"` to
this list will prevent `"default"` from being added automatically to created this list will prevent `"default"` from being added automatically to created
tokens. tokens.
- `allowed_policies_glob` `(list: [])` If set, tokens can be created with any
subset of glob matched policies in this list, rather than the normal semantics
of tokens being a subset of the calling token's policies. The parameter is a
comma-delimited string of policy name globs. If at creation time
`no_default_policy` is not set and `"default"` is not contained in
`disallowed_policies` or glob matched in `disallowed_policies_glob`,
the `"default"` policy will be added to the created token automatically.
If combined with `allowed_policies` policies need to only match one of the two
lists to be permitted. Note that unlike `allowed_policies` the policies listed
in `allowed_policies_glob` will not be added to the token when no policies are
specified in the call to `/auth/token/create/:role_name`.
- `disallowed_policies_glob` `(list: [])` If set, successful token creation via
this role will require that no requested policies glob match any of policies in
this list. The parameter is a comma-delimited string of policy name globs.
Adding any glob that matches `"default"` to this list will prevent `"default"`
from being added automatically to created tokens.
If combined with `disallowed_policies` policies need to only match one of the
two lists to be blocked.
- `orphan` `(bool: false)` - If `true`, tokens created against this policy will - `orphan` `(bool: false)` - If `true`, tokens created against this policy will
be orphan tokens (they will have no parent). As such, they will not be be orphan tokens (they will have no parent). As such, they will not be
automatically revoked by the revocation of any other token. automatically revoked by the revocation of any other token.