diff --git a/vault/token_store.go b/vault/token_store.go index db1dbbf60b23..4760cf9c714b 100644 --- a/vault/token_store.go +++ b/vault/token_store.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-uuid" "github.com/hashicorp/vault/helper/consts" + "github.com/hashicorp/vault/helper/identity" "github.com/hashicorp/vault/helper/jsonutil" "github.com/hashicorp/vault/helper/locksutil" "github.com/hashicorp/vault/helper/parseutil" @@ -104,6 +105,8 @@ type TokenStore struct { salt *salt.Salt tidyLock int64 + + identityPoliciesDeriverFunc func(string) (*identity.Entity, []string, error) } // NewTokenStore is used to construct a token store that is @@ -114,11 +117,12 @@ func NewTokenStore(ctx context.Context, logger log.Logger, c *Core, config *logi // Initialize the store t := &TokenStore{ - view: view, - cubbyholeDestroyer: destroyCubbyhole, - logger: logger, - tokenLocks: locksutil.CreateLocks(), - saltLock: sync.RWMutex{}, + view: view, + cubbyholeDestroyer: destroyCubbyhole, + logger: logger, + tokenLocks: locksutil.CreateLocks(), + saltLock: sync.RWMutex{}, + identityPoliciesDeriverFunc: c.fetchEntityAndDerivedPolicies, } if c.policyStore != nil { @@ -2204,6 +2208,16 @@ func (ts *TokenStore) handleLookup(ctx context.Context, req *logical.Request, da resp.Data["issue_time"] = leaseTimes.IssueTime } + if out.EntityID != "" { + _, identityPolicies, err := ts.identityPoliciesDeriverFunc(out.EntityID) + if err != nil { + return nil, err + } + if len(identityPolicies) != 0 { + resp.Data["identity_policies"] = identityPolicies + } + } + if urltoken { resp.AddWarning(`Using a token in the path is unsafe as the token can be logged in many places. Please use POST or PUT with the token passed in via the "token" parameter.`) } diff --git a/vault/token_store_ext_test.go b/vault/token_store_ext_test.go new file mode 100644 index 000000000000..3ebc1928e2eb --- /dev/null +++ b/vault/token_store_ext_test.go @@ -0,0 +1,219 @@ +package vault_test + +import ( + "reflect" + "sort" + "testing" + + "github.com/hashicorp/vault/api" + credLdap "github.com/hashicorp/vault/builtin/credential/ldap" + vaulthttp "github.com/hashicorp/vault/http" + "github.com/hashicorp/vault/logical" + "github.com/hashicorp/vault/vault" +) + +func TestTokenStore_IdentityPolicies(t *testing.T) { + coreConfig := &vault.CoreConfig{ + CredentialBackends: map[string]logical.Factory{ + "ldap": credLdap.Factory, + }, + } + cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ + HandlerFunc: vaulthttp.Handler, + }) + cluster.Start() + defer cluster.Cleanup() + + core := cluster.Cores[0].Core + vault.TestWaitActive(t, core) + client := cluster.Cores[0].Client + + // Enable LDAP auth + err := client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{ + Type: "ldap", + }) + if err != nil { + t.Fatal(err) + } + + // Configure LDAP auth + _, err = client.Logical().Write("auth/ldap/config", map[string]interface{}{ + "url": "ldap://ldap.forumsys.com", + "userattr": "uid", + "userdn": "dc=example,dc=com", + "groupdn": "dc=example,dc=com", + "binddn": "cn=read-only-admin,dc=example,dc=com", + }) + if err != nil { + t.Fatal(err) + } + + // Create group in LDAP auth + _, err = client.Logical().Write("auth/ldap/groups/testgroup1", map[string]interface{}{ + "policies": "testgroup1-policy", + }) + if err != nil { + t.Fatal(err) + } + + // Create user in LDAP auth + _, err = client.Logical().Write("auth/ldap/users/tesla", map[string]interface{}{ + "policies": "default", + "groups": "testgroup1", + }) + if err != nil { + t.Fatal(err) + } + + // Login using LDAP + secret, err := client.Logical().Write("auth/ldap/login/tesla", map[string]interface{}{ + "password": "password", + }) + if err != nil { + t.Fatal(err) + } + ldapClientToken := secret.Auth.ClientToken + + // At this point there shouldn't be any identity policy on the token + secret, err = client.Logical().Read("auth/token/lookup/" + ldapClientToken) + if err != nil { + t.Fatal(err) + } + _, ok := secret.Data["identity_policies"] + if ok { + t.Fatalf("identity_policies should not have been set") + } + + // Extract the entity ID of the token and set some policies on the entity + entityID := secret.Data["entity_id"].(string) + _, err = client.Logical().Write("identity/entity/id/"+entityID, map[string]interface{}{ + "policies": []string{ + "entity_policy_1", + "entity_policy_2", + }, + }) + if err != nil { + t.Fatal(err) + } + + // Lookup the token and expect entity policies on the token + secret, err = client.Logical().Read("auth/token/lookup/" + ldapClientToken) + if err != nil { + t.Fatal(err) + } + identityPolicies := secret.Data["identity_policies"].([]interface{}) + var actualPolicies []string + for _, item := range identityPolicies { + actualPolicies = append(actualPolicies, item.(string)) + } + sort.Strings(actualPolicies) + + expectedPolicies := []string{ + "entity_policy_1", + "entity_policy_2", + } + sort.Strings(expectedPolicies) + if !reflect.DeepEqual(expectedPolicies, actualPolicies) { + t.Fatalf("bad: identity policies; expected: %#v\nactual: %#v", expectedPolicies, actualPolicies) + } + + // Create identity group and add entity as its member + secret, err = client.Logical().Write("identity/group", map[string]interface{}{ + "policies": []string{ + "group_policy_1", + "group_policy_2", + }, + "member_entity_ids": []string{ + entityID, + }, + }) + if err != nil { + t.Fatal(err) + } + + // Lookup token and expect both entity and group policies on the token + secret, err = client.Logical().Read("auth/token/lookup/" + ldapClientToken) + if err != nil { + t.Fatal(err) + } + identityPolicies = secret.Data["identity_policies"].([]interface{}) + actualPolicies = nil + for _, item := range identityPolicies { + actualPolicies = append(actualPolicies, item.(string)) + } + sort.Strings(actualPolicies) + + expectedPolicies = []string{ + "entity_policy_1", + "entity_policy_2", + "group_policy_1", + "group_policy_2", + } + sort.Strings(expectedPolicies) + if !reflect.DeepEqual(expectedPolicies, actualPolicies) { + t.Fatalf("bad: identity policies; expected: %#v\nactual: %#v", expectedPolicies, actualPolicies) + } + + // Create an external group and renew the token. This should add external + // group policies to the token. + auths, err := client.Sys().ListAuth() + if err != nil { + t.Fatal(err) + } + ldapMountAccessor1 := auths["ldap/"].Accessor + + // Create an external group + secret, err = client.Logical().Write("identity/group", map[string]interface{}{ + "type": "external", + "policies": []string{ + "external_group_policy_1", + "external_group_policy_2", + }, + }) + if err != nil { + t.Fatal(err) + } + ldapExtGroupID1 := secret.Data["id"].(string) + + // Associate a group from LDAP auth as a group-alias in the external group + _, err = client.Logical().Write("identity/group-alias", map[string]interface{}{ + "name": "testgroup1", + "mount_accessor": ldapMountAccessor1, + "canonical_id": ldapExtGroupID1, + }) + if err != nil { + t.Fatal(err) + } + + // Renew token to refresh external group memberships + secret, err = client.Auth().Token().Renew(ldapClientToken, 10) + if err != nil { + t.Fatal(err) + } + + // Lookup token and expect entity, group and external group policies on the + // token + secret, err = client.Logical().Read("auth/token/lookup/" + ldapClientToken) + if err != nil { + t.Fatal(err) + } + identityPolicies = secret.Data["identity_policies"].([]interface{}) + actualPolicies = nil + for _, item := range identityPolicies { + actualPolicies = append(actualPolicies, item.(string)) + } + sort.Strings(actualPolicies) + + expectedPolicies = []string{ + "entity_policy_1", + "entity_policy_2", + "group_policy_1", + "group_policy_2", + "external_group_policy_1", + "external_group_policy_2", + } + sort.Strings(expectedPolicies) + if !reflect.DeepEqual(expectedPolicies, actualPolicies) { + t.Fatalf("bad: identity policies; expected: %#v\nactual: %#v", expectedPolicies, actualPolicies) + } +}