External identity groups (#3447)

* external identity groups

* add local LDAP groups as well to group aliases

* add group aliases for okta credential backend

* Fix panic in tests

* fix build failure

* remove duplicated struct tag

* add test steps to test out removal of group member during renewals

* Add comment for having a prefix check in router

* fix tests

* s/parent_id/canonical_id

* s/parent/canonical in comments and errors
This commit is contained in:
Vishal Nayak 2017-11-02 16:05:48 -04:00 committed by GitHub
parent 710243ab26
commit 7bae606662
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 2287 additions and 388 deletions

View File

@ -74,7 +74,7 @@ func (b *backend) pathLogin(
return logical.ErrorResponse(fmt.Sprintf("error sanitizing TTLs: %s", err)), nil
}
return &logical.Response{
resp := &logical.Response{
Auth: &logical.Auth{
InternalData: map[string]interface{}{
"token": token,
@ -93,7 +93,15 @@ func (b *backend) pathLogin(
Name: *verifyResp.User.Login,
},
},
}, nil
}
for _, teamName := range verifyResp.TeamNames {
resp.Auth.GroupAliases = append(resp.Auth.GroupAliases, &logical.Alias{
Name: teamName,
})
}
return resp, nil
}
func (b *backend) pathLoginRenew(
@ -125,7 +133,22 @@ func (b *backend) pathLoginRenew(
if err != nil {
return nil, err
}
return framework.LeaseExtend(config.TTL, config.MaxTTL, b.System())(req, d)
resp, err := framework.LeaseExtend(config.TTL, config.MaxTTL, b.System())(req, d)
if err != nil {
return nil, err
}
// Remove old aliases
resp.Auth.GroupAliases = nil
for _, teamName := range verifyResp.TeamNames {
resp.Auth.GroupAliases = append(resp.Auth.GroupAliases, &logical.Alias{
Name: teamName,
})
}
return resp, nil
}
func (b *backend) verifyCredentials(req *logical.Request, token string) (*verifyCredentialsResp, *logical.Response, error) {
@ -233,14 +256,16 @@ func (b *backend) verifyCredentials(req *logical.Request, token string) (*verify
}
return &verifyCredentialsResp{
User: user,
Org: org,
Policies: append(groupPoliciesList, userPoliciesList...),
User: user,
Org: org,
Policies: append(groupPoliciesList, userPoliciesList...),
TeamNames: teamNames,
}, nil, nil
}
type verifyCredentialsResp struct {
User *github.User
Org *github.Organization
Policies []string
User *github.User
Org *github.Organization
Policies []string
TeamNames []string
}

View File

@ -88,22 +88,22 @@ func EscapeLDAPValue(input string) string {
return input
}
func (b *backend) Login(req *logical.Request, username string, password string) ([]string, *logical.Response, error) {
func (b *backend) Login(req *logical.Request, username string, password string) ([]string, *logical.Response, []string, error) {
cfg, err := b.Config(req)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
if cfg == nil {
return nil, logical.ErrorResponse("ldap backend not configured"), nil
return nil, logical.ErrorResponse("ldap backend not configured"), nil, nil
}
c, err := cfg.DialLDAP()
if err != nil {
return nil, logical.ErrorResponse(err.Error()), nil
return nil, logical.ErrorResponse(err.Error()), nil, nil
}
if c == nil {
return nil, logical.ErrorResponse("invalid connection returned from LDAP dial"), nil
return nil, logical.ErrorResponse("invalid connection returned from LDAP dial"), nil, nil
}
// Clean connection
@ -111,7 +111,7 @@ func (b *backend) Login(req *logical.Request, username string, password string)
userBindDN, err := b.getUserBindDN(cfg, c, username)
if err != nil {
return nil, logical.ErrorResponse(err.Error()), nil
return nil, logical.ErrorResponse(err.Error()), nil, nil
}
if b.Logger().IsDebug() {
@ -119,7 +119,7 @@ func (b *backend) Login(req *logical.Request, username string, password string)
}
if cfg.DenyNullBind && len(password) == 0 {
return nil, logical.ErrorResponse("password cannot be of zero length when passwordless binds are being denied"), nil
return nil, logical.ErrorResponse("password cannot be of zero length when passwordless binds are being denied"), nil, nil
}
// Try to bind as the login user. This is where the actual authentication takes place.
@ -129,14 +129,14 @@ func (b *backend) Login(req *logical.Request, username string, password string)
err = c.UnauthenticatedBind(userBindDN)
}
if err != nil {
return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind failed: %v", err)), nil
return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind failed: %v", err)), nil, nil
}
// We re-bind to the BindDN if it's defined because we assume
// the BindDN should be the one to search, not the user logging in.
if cfg.BindDN != "" && cfg.BindPassword != "" {
if err := c.Bind(cfg.BindDN, cfg.BindPassword); err != nil {
return nil, logical.ErrorResponse(fmt.Sprintf("Encountered an error while attempting to re-bind with the BindDN User: %s", err.Error())), nil
return nil, logical.ErrorResponse(fmt.Sprintf("Encountered an error while attempting to re-bind with the BindDN User: %s", err.Error())), nil, nil
}
if b.Logger().IsDebug() {
b.Logger().Debug("auth/ldap: Re-Bound to original BindDN")
@ -145,12 +145,12 @@ func (b *backend) Login(req *logical.Request, username string, password string)
userDN, err := b.getUserDN(cfg, c, userBindDN)
if err != nil {
return nil, logical.ErrorResponse(err.Error()), nil
return nil, logical.ErrorResponse(err.Error()), nil, nil
}
ldapGroups, err := b.getLdapGroups(cfg, c, userDN, username)
if err != nil {
return nil, logical.ErrorResponse(err.Error()), nil
return nil, logical.ErrorResponse(err.Error()), nil, nil
}
if b.Logger().IsDebug() {
b.Logger().Debug("auth/ldap: Groups fetched from server", "num_server_groups", len(ldapGroups), "server_groups", ldapGroups)
@ -199,10 +199,10 @@ func (b *backend) Login(req *logical.Request, username string, password string)
}
ldapResponse.Data["error"] = errStr
return nil, ldapResponse, nil
return nil, ldapResponse, nil, nil
}
return policies, ldapResponse, nil
return policies, ldapResponse, allGroups, nil
}
/*

View File

@ -55,7 +55,7 @@ func (b *backend) pathLogin(
username := d.Get("username").(string)
password := d.Get("password").(string)
policies, resp, err := b.Login(req, username, password)
policies, resp, groupNames, err := b.Login(req, username, password)
// Handle an internal error
if err != nil {
return nil, err
@ -87,6 +87,12 @@ func (b *backend) pathLogin(
Name: username,
},
}
for _, groupName := range groupNames {
resp.Auth.GroupAliases = append(resp.Auth.GroupAliases, &logical.Alias{
Name: groupName,
})
}
return resp, nil
}
@ -96,7 +102,7 @@ func (b *backend) pathLoginRenew(
username := req.Auth.Metadata["username"]
password := req.Auth.InternalData["password"].(string)
loginPolicies, resp, err := b.Login(req, username, password)
loginPolicies, resp, groupNames, err := b.Login(req, username, password)
if len(loginPolicies) == 0 {
return resp, err
}
@ -105,7 +111,21 @@ func (b *backend) pathLoginRenew(
return nil, fmt.Errorf("policies have changed, not renewing")
}
return framework.LeaseExtend(0, 0, b.System())(req, d)
resp, err = framework.LeaseExtend(0, 0, b.System())(req, d)
if err != nil {
return nil, err
}
// Remove old aliases
resp.Auth.GroupAliases = nil
for _, groupName := range groupNames {
resp.Auth.GroupAliases = append(resp.Auth.GroupAliases, &logical.Alias{
Name: groupName,
})
}
return resp, nil
}
const pathLoginSyn = `

View File

@ -47,13 +47,13 @@ type backend struct {
*framework.Backend
}
func (b *backend) Login(req *logical.Request, username string, password string) ([]string, *logical.Response, error) {
func (b *backend) Login(req *logical.Request, username string, password string) ([]string, *logical.Response, []string, error) {
cfg, err := b.Config(req.Storage)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
if cfg == nil {
return nil, logical.ErrorResponse("Okta backend not configured"), nil
return nil, logical.ErrorResponse("Okta backend not configured"), nil, nil
}
client := cfg.OktaClient()
@ -71,16 +71,16 @@ func (b *backend) Login(req *logical.Request, username string, password string)
"password": password,
})
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
var result authResult
rsp, err := client.Do(authReq, &result)
if err != nil {
return nil, logical.ErrorResponse(fmt.Sprintf("Okta auth failed: %v", err)), nil
return nil, logical.ErrorResponse(fmt.Sprintf("Okta auth failed: %v", err)), nil, nil
}
if rsp == nil {
return nil, logical.ErrorResponse("okta auth backend unexpected failure"), nil
return nil, logical.ErrorResponse("okta auth backend unexpected failure"), nil, nil
}
oktaResponse := &logical.Response{
@ -92,7 +92,7 @@ func (b *backend) Login(req *logical.Request, username string, password string)
if cfg.Token != "" {
oktaGroups, err := b.getOktaGroups(client, &result.Embedded.User)
if err != nil {
return nil, logical.ErrorResponse(fmt.Sprintf("okta failure retrieving groups: %v", err)), nil
return nil, logical.ErrorResponse(fmt.Sprintf("okta failure retrieving groups: %v", err)), nil, nil
}
if len(oktaGroups) == 0 {
errString := fmt.Sprintf(
@ -142,10 +142,10 @@ func (b *backend) Login(req *logical.Request, username string, password string)
}
oktaResponse.Data["error"] = errStr
return nil, oktaResponse, nil
return nil, oktaResponse, nil, nil
}
return policies, oktaResponse, nil
return policies, oktaResponse, allGroups, nil
}
func (b *backend) getOktaGroups(client *okta.Client, user *okta.User) ([]string, error) {

View File

@ -57,7 +57,7 @@ func (b *backend) pathLogin(
username := d.Get("username").(string)
password := d.Get("password").(string)
policies, resp, err := b.Login(req, username, password)
policies, resp, groupNames, err := b.Login(req, username, password)
// Handle an internal error
if err != nil {
return nil, err
@ -96,6 +96,13 @@ func (b *backend) pathLogin(
Name: username,
},
}
for _, groupName := range groupNames {
resp.Auth.GroupAliases = append(resp.Auth.GroupAliases, &logical.Alias{
Name: groupName,
})
}
return resp, nil
}
@ -105,7 +112,7 @@ func (b *backend) pathLoginRenew(
username := req.Auth.Metadata["username"]
password := req.Auth.InternalData["password"].(string)
loginPolicies, resp, err := b.Login(req, username, password)
loginPolicies, resp, groupNames, err := b.Login(req, username, password)
if len(loginPolicies) == 0 {
return resp, err
}
@ -119,7 +126,22 @@ func (b *backend) pathLoginRenew(
return nil, err
}
return framework.LeaseExtend(cfg.TTL, cfg.MaxTTL, b.System())(req, d)
resp, err = framework.LeaseExtend(cfg.TTL, cfg.MaxTTL, b.System())(req, d)
if err != nil {
return nil, err
}
// Remove old aliases
resp.Auth.GroupAliases = nil
for _, groupName := range groupNames {
resp.Auth.GroupAliases = append(resp.Auth.GroupAliases, &logical.Alias{
Name: groupName,
})
}
return resp, nil
}
func (b *backend) getConfig(req *logical.Request) (*ConfigEntry, error) {

View File

@ -0,0 +1,368 @@
package command
import (
"testing"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/credential/ldap"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"
logxi "github.com/mgutz/logxi/v1"
)
func TestIdentityStore_Integ_GroupAliases(t *testing.T) {
var err error
coreConfig := &vault.CoreConfig{
DisableMlock: true,
DisableCache: true,
Logger: logxi.NullLog,
CredentialBackends: map[string]logical.Factory{
"ldap": ldap.Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
cores := cluster.Cores
vault.TestWaitActive(t, cores[0].Core)
client := cores[0].Client
err = client.Sys().EnableAuthWithOptions("ldap", &api.EnableAuthOptions{
Type: "ldap",
})
if err != nil {
t.Fatal(err)
}
auth, err := client.Sys().ListAuth()
if err != nil {
t.Fatal(err)
}
accessor := auth["ldap/"].Accessor
secret, err := client.Logical().Write("identity/group", map[string]interface{}{
"type": "external",
"name": "ldap_Italians",
})
if err != nil {
t.Fatal(err)
}
italiansGroupID := secret.Data["id"].(string)
secret, err = client.Logical().Write("identity/group", map[string]interface{}{
"type": "external",
"name": "ldap_Scientists",
})
if err != nil {
t.Fatal(err)
}
scientistsGroupID := secret.Data["id"].(string)
secret, err = client.Logical().Write("identity/group", map[string]interface{}{
"type": "external",
"name": "ldap_devops",
})
if err != nil {
t.Fatal(err)
}
devopsGroupID := secret.Data["id"].(string)
secret, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
"name": "Italians",
"canonical_id": italiansGroupID,
"mount_accessor": accessor,
})
if err != nil {
t.Fatal(err)
}
secret, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
"name": "Scientists",
"canonical_id": scientistsGroupID,
"mount_accessor": accessor,
})
if err != nil {
t.Fatal(err)
}
secret, err = client.Logical().Write("identity/group-alias", map[string]interface{}{
"name": "devops",
"canonical_id": devopsGroupID,
"mount_accessor": accessor,
})
if err != nil {
t.Fatal(err)
}
secret, err = client.Logical().Read("identity/group/id/" + italiansGroupID)
if err != nil {
t.Fatal(err)
}
aliasMap := secret.Data["alias"].(map[string]interface{})
if aliasMap["canonical_id"] != italiansGroupID ||
aliasMap["name"] != "Italians" ||
aliasMap["mount_accessor"] != accessor {
t.Fatalf("bad: group alias: %#v\n", aliasMap)
}
secret, err = client.Logical().Read("identity/group/id/" + scientistsGroupID)
if err != nil {
t.Fatal(err)
}
aliasMap = secret.Data["alias"].(map[string]interface{})
if aliasMap["canonical_id"] != scientistsGroupID ||
aliasMap["name"] != "Scientists" ||
aliasMap["mount_accessor"] != accessor {
t.Fatalf("bad: group alias: %#v\n", aliasMap)
}
// Configure LDAP auth backend
secret, 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 a local group in LDAP backend
secret, err = client.Logical().Write("auth/ldap/groups/devops", map[string]interface{}{
"policies": "default",
})
if err != nil {
t.Fatal(err)
}
// Create a local group in LDAP backend
secret, err = client.Logical().Write("auth/ldap/groups/engineers", map[string]interface{}{
"policies": "default",
})
if err != nil {
t.Fatal(err)
}
// Create a local user in LDAP
secret, err = client.Logical().Write("auth/ldap/users/tesla", map[string]interface{}{
"policies": "default",
"groups": "engineers,devops",
})
if err != nil {
t.Fatal(err)
}
// Login with LDAP and create a token
secret, err = client.Logical().Write("auth/ldap/login/tesla", map[string]interface{}{
"password": "password",
})
if err != nil {
t.Fatal(err)
}
token := secret.Auth.ClientToken
// Lookup the token to get the entity ID
secret, err = client.Auth().Token().Lookup(token)
if err != nil {
t.Fatal(err)
}
entityID := secret.Data["entity_id"].(string)
// Re-read the Scientists, Italians and devops group. This entity ID should have
// been added to both of these groups by now.
secret, err = client.Logical().Read("identity/group/id/" + italiansGroupID)
if err != nil {
t.Fatal(err)
}
groupMap := secret.Data
found := false
for _, entityIDRaw := range groupMap["member_entity_ids"].([]interface{}) {
if entityIDRaw.(string) == entityID {
found = true
}
}
if !found {
t.Fatalf("expected entity ID %q to be part of Italians group")
}
secret, err = client.Logical().Read("identity/group/id/" + scientistsGroupID)
if err != nil {
t.Fatal(err)
}
groupMap = secret.Data
found = false
for _, entityIDRaw := range groupMap["member_entity_ids"].([]interface{}) {
if entityIDRaw.(string) == entityID {
found = true
}
}
if !found {
t.Fatalf("expected entity ID %q to be part of Scientists group")
}
secret, err = client.Logical().Read("identity/group/id/" + devopsGroupID)
if err != nil {
t.Fatal(err)
}
groupMap = secret.Data
found = false
for _, entityIDRaw := range groupMap["member_entity_ids"].([]interface{}) {
if entityIDRaw.(string) == entityID {
found = true
}
}
if !found {
t.Fatalf("expected entity ID %q to be part of devops group")
}
identityStore := cores[0].IdentityStore()
group, err := identityStore.MemDBGroupByID(italiansGroupID, true)
if err != nil {
t.Fatal(err)
}
// Remove its member entities
group.MemberEntityIDs = nil
err = identityStore.UpsertGroup(group, true)
if err != nil {
t.Fatal(err)
}
group, err = identityStore.MemDBGroupByID(italiansGroupID, true)
if err != nil {
t.Fatal(err)
}
if group.MemberEntityIDs != nil {
t.Fatalf("failed to remove entity ID from the group")
}
group, err = identityStore.MemDBGroupByID(scientistsGroupID, true)
if err != nil {
t.Fatal(err)
}
// Remove its member entities
group.MemberEntityIDs = nil
err = identityStore.UpsertGroup(group, true)
if err != nil {
t.Fatal(err)
}
group, err = identityStore.MemDBGroupByID(scientistsGroupID, true)
if err != nil {
t.Fatal(err)
}
if group.MemberEntityIDs != nil {
t.Fatalf("failed to remove entity ID from the group")
}
group, err = identityStore.MemDBGroupByID(devopsGroupID, true)
if err != nil {
t.Fatal(err)
}
// Remove its member entities
group.MemberEntityIDs = nil
err = identityStore.UpsertGroup(group, true)
if err != nil {
t.Fatal(err)
}
group, err = identityStore.MemDBGroupByID(devopsGroupID, true)
if err != nil {
t.Fatal(err)
}
if group.MemberEntityIDs != nil {
t.Fatalf("failed to remove entity ID from the group")
}
_, err = client.Auth().Token().Renew(token, 0)
if err != nil {
t.Fatal(err)
}
// EntityIDs should have been added to the groups again during renewal
secret, err = client.Logical().Read("identity/group/id/" + italiansGroupID)
if err != nil {
t.Fatal(err)
}
groupMap = secret.Data
found = false
for _, entityIDRaw := range groupMap["member_entity_ids"].([]interface{}) {
if entityIDRaw.(string) == entityID {
found = true
}
}
if !found {
t.Fatalf("expected entity ID %q to be part of Italians group")
}
secret, err = client.Logical().Read("identity/group/id/" + scientistsGroupID)
if err != nil {
t.Fatal(err)
}
groupMap = secret.Data
found = false
for _, entityIDRaw := range groupMap["member_entity_ids"].([]interface{}) {
if entityIDRaw.(string) == entityID {
found = true
}
}
if !found {
t.Fatalf("expected entity ID %q to be part of Italians group")
}
secret, err = client.Logical().Read("identity/group/id/" + devopsGroupID)
if err != nil {
t.Fatal(err)
}
groupMap = secret.Data
found = false
for _, entityIDRaw := range groupMap["member_entity_ids"].([]interface{}) {
if entityIDRaw.(string) == entityID {
found = true
}
}
if !found {
t.Fatalf("expected entity ID %q to be part of devops group")
}
// Remove user tesla from the devops group in LDAP backend
secret, err = client.Logical().Write("auth/ldap/users/tesla", map[string]interface{}{
"policies": "default",
"groups": "engineers",
})
if err != nil {
t.Fatal(err)
}
// Renewing the token now should remove its entity ID from the devops
// group
_, err = client.Auth().Token().Renew(token, 0)
if err != nil {
t.Fatal(err)
}
group, err = identityStore.MemDBGroupByID(devopsGroupID, true)
if err != nil {
t.Fatal(err)
}
if group.MemberEntityIDs != nil {
t.Fatalf("failed to remove entity ID from the group")
}
}

View File

@ -50,7 +50,7 @@ func (p *Alias) SentinelGet(key string) (interface{}, error) {
case "last_update_time":
return ptypes.TimestampString(p.LastUpdateTime), nil
case "merged_from_entity_ids":
return p.MergedFromEntityIDs, nil
return p.MergedFromCanonicalIDs, nil
}
return nil, nil

View File

@ -59,6 +59,15 @@ type Group struct {
// the groups belonging to a particular bucket during invalidation of the
// storage key.
BucketKeyHash string `sentinel:"" protobuf:"bytes,10,opt,name=bucket_key_hash,json=bucketKeyHash" json:"bucket_key_hash,omitempty"`
// Alias is used to mark this group as an internal mapping of a group that
// is external to the identity store. Alias can only be set if the 'type'
// is set to 'external'.
Alias *Alias `sentinel:"" protobuf:"bytes,11,opt,name=alias" json:"alias,omitempty"`
// Type indicates if this group is an internal group or an external group.
// Memberships of the internal groups can be managed over the API whereas
// the memberships on the external group --for which a corresponding alias
// will be set-- will be managed automatically.
Type string `sentinel:"" protobuf:"bytes,12,opt,name=type" json:"type,omitempty"`
}
func (m *Group) Reset() { *m = Group{} }
@ -136,6 +145,20 @@ func (m *Group) GetBucketKeyHash() string {
return ""
}
func (m *Group) GetAlias() *Alias {
if m != nil {
return m.Alias
}
return nil
}
func (m *Group) GetType() string {
if m != nil {
return m.Type
}
return ""
}
// Entity represents an entity that gets persisted and indexed.
// Entity is fundamentally composed of zero or many aliases.
type Entity struct {
@ -253,8 +276,8 @@ func (m *Entity) GetBucketKeyHash() string {
type Alias struct {
// ID is the unique identifier that represents this alias
ID string `sentinel:"" protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
// EntityID is the entity identifier to which this alias belongs to
EntityID string `sentinel:"" protobuf:"bytes,2,opt,name=entity_id,json=entityId" json:"entity_id,omitempty"`
// CanonicalID is the entity identifier to which this alias belongs to
CanonicalID string `sentinel:"" protobuf:"bytes,2,opt,name=canonical_id,json=canonicalId" json:"canonical_id,omitempty"`
// MountType is the backend mount's type to which this alias belongs to.
// This enables categorically querying aliases of specific backend types.
MountType string `sentinel:"" protobuf:"bytes,3,opt,name=mount_type,json=mountType" json:"mount_type,omitempty"`
@ -270,8 +293,8 @@ type Alias struct {
// against their metadata.
Metadata map[string]string `sentinel:"" protobuf:"bytes,6,rep,name=metadata" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
// Name is the identifier of this alias in its authentication source.
// This does not uniquely identify a alias in Vault. This in conjunction
// with MountAccessor form to be the factors that represent a alias in a
// This does not uniquely identify an alias in Vault. This in conjunction
// with MountAccessor form to be the factors that represent an alias in a
// unique way. Aliases will be indexed based on this combined uniqueness
// factor.
Name string `sentinel:"" protobuf:"bytes,7,opt,name=name" json:"name,omitempty"`
@ -281,10 +304,8 @@ type Alias struct {
// alias got modified. This is helpful in filtering out aliases based
// on its age and to take action on them, if desired.
LastUpdateTime *google_protobuf.Timestamp `sentinel:"" protobuf:"bytes,9,opt,name=last_update_time,json=lastUpdateTime" json:"last_update_time,omitempty"`
// MergedFromEntityIDs is the FIFO history of merging activity by entity IDs from
// which this alias is transfered over to the entity to which it
// currently belongs to.
MergedFromEntityIDs []string `sentinel:"" protobuf:"bytes,10,rep,name=merged_from_entity_ids,json=mergedFromEntityIDs" json:"merged_from_entity_ids,omitempty"`
// MergedFromCanonicalIDs is the FIFO history of merging activity
MergedFromCanonicalIDs []string `sentinel:"" protobuf:"bytes,10,rep,name=merged_from_canonical_ids,json=mergedFromCanonicalIds" json:"merged_from_canonical_ids,omitempty"`
}
func (m *Alias) Reset() { *m = Alias{} }
@ -299,9 +320,9 @@ func (m *Alias) GetID() string {
return ""
}
func (m *Alias) GetEntityID() string {
func (m *Alias) GetCanonicalID() string {
if m != nil {
return m.EntityID
return m.CanonicalID
}
return ""
}
@ -355,9 +376,9 @@ func (m *Alias) GetLastUpdateTime() *google_protobuf.Timestamp {
return nil
}
func (m *Alias) GetMergedFromEntityIDs() []string {
func (m *Alias) GetMergedFromCanonicalIDs() []string {
if m != nil {
return m.MergedFromEntityIDs
return m.MergedFromCanonicalIDs
}
return nil
}
@ -371,41 +392,43 @@ func init() {
func init() { proto.RegisterFile("types.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 570 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x94, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0xc7, 0xe5, 0x38, 0x1f, 0xf6, 0xa4, 0x4d, 0xcb, 0x82, 0x90, 0x15, 0x54, 0x08, 0x95, 0x40,
0x86, 0x83, 0x2b, 0xb5, 0x17, 0x28, 0x07, 0x54, 0x89, 0x02, 0x15, 0x42, 0x42, 0x56, 0x39, 0x5b,
0x9b, 0x78, 0x9a, 0xac, 0x1a, 0x7b, 0x2d, 0xef, 0x1a, 0xe1, 0x27, 0xe4, 0x39, 0x38, 0xf1, 0x1a,
0xc8, 0xb3, 0x76, 0x62, 0x08, 0x5f, 0x15, 0xb9, 0xd9, 0xff, 0x99, 0x1d, 0xcf, 0xce, 0xff, 0x37,
0x86, 0xa1, 0x2e, 0x33, 0x54, 0x41, 0x96, 0x4b, 0x2d, 0x99, 0x23, 0x62, 0x4c, 0xb5, 0xd0, 0xe5,
0xf8, 0xc1, 0x5c, 0xca, 0xf9, 0x12, 0x8f, 0x48, 0x9f, 0x16, 0x57, 0x47, 0x5a, 0x24, 0xa8, 0x34,
0x4f, 0x32, 0x93, 0x7a, 0xf8, 0xcd, 0x86, 0xde, 0x9b, 0x5c, 0x16, 0x19, 0x1b, 0x41, 0x47, 0xc4,
0x9e, 0x35, 0xb1, 0x7c, 0x37, 0xec, 0x88, 0x98, 0x31, 0xe8, 0xa6, 0x3c, 0x41, 0xaf, 0x43, 0x0a,
0x3d, 0xb3, 0x31, 0x38, 0x99, 0x5c, 0x8a, 0x99, 0x40, 0xe5, 0xd9, 0x13, 0xdb, 0x77, 0xc3, 0xd5,
0x3b, 0xf3, 0x61, 0x3f, 0xe3, 0x39, 0xa6, 0x3a, 0x9a, 0x57, 0xf5, 0x22, 0x11, 0x2b, 0xaf, 0x4b,
0x39, 0x23, 0xa3, 0xd3, 0x67, 0x2e, 0x62, 0xc5, 0x9e, 0xc2, 0xad, 0x04, 0x93, 0x29, 0xe6, 0x91,
0xe9, 0x92, 0x52, 0x7b, 0x94, 0xba, 0x67, 0x02, 0xe7, 0xa4, 0x57, 0xb9, 0xcf, 0xc1, 0x49, 0x50,
0xf3, 0x98, 0x6b, 0xee, 0xf5, 0x27, 0xb6, 0x3f, 0x3c, 0x3e, 0x08, 0x9a, 0xdb, 0x05, 0x54, 0x31,
0x78, 0x5f, 0xc7, 0xcf, 0x53, 0x9d, 0x97, 0xe1, 0x2a, 0x9d, 0xbd, 0x84, 0xdd, 0x59, 0x8e, 0x5c,
0x0b, 0x99, 0x46, 0xd5, 0xb5, 0xbd, 0xc1, 0xc4, 0xf2, 0x87, 0xc7, 0xe3, 0xc0, 0xcc, 0x24, 0x68,
0x66, 0x12, 0x5c, 0x36, 0x33, 0x09, 0x77, 0x9a, 0x03, 0x95, 0xc4, 0x5e, 0xc1, 0xfe, 0x92, 0x2b,
0x1d, 0x15, 0x59, 0xcc, 0x35, 0x9a, 0x1a, 0xce, 0x5f, 0x6b, 0x8c, 0xaa, 0x33, 0x1f, 0xe9, 0x08,
0x55, 0x79, 0x08, 0x3b, 0x89, 0x8c, 0xc5, 0x55, 0x19, 0x89, 0x34, 0xc6, 0xcf, 0x9e, 0x3b, 0xb1,
0xfc, 0x6e, 0x38, 0x34, 0xda, 0x45, 0x25, 0xb1, 0xc7, 0xb0, 0x37, 0x2d, 0x66, 0xd7, 0xa8, 0xa3,
0x6b, 0x2c, 0xa3, 0x05, 0x57, 0x0b, 0x0f, 0x68, 0xea, 0xbb, 0x46, 0x7e, 0x87, 0xe5, 0x5b, 0xae,
0x16, 0xe3, 0x17, 0xb0, 0xfb, 0xc3, 0x65, 0xd9, 0x3e, 0xd8, 0xd7, 0x58, 0xd6, 0xa6, 0x55, 0x8f,
0xec, 0x0e, 0xf4, 0x3e, 0xf1, 0x65, 0xd1, 0xd8, 0x66, 0x5e, 0x4e, 0x3b, 0xcf, 0xac, 0xc3, 0x2f,
0x36, 0xf4, 0xcd, 0x5c, 0xd9, 0x13, 0x18, 0xf0, 0xa5, 0xe0, 0x0a, 0x95, 0x67, 0xd1, 0x4c, 0xf7,
0xd6, 0x33, 0x3d, 0xab, 0x02, 0x61, 0x13, 0xaf, 0xa9, 0xe8, 0x6c, 0x50, 0x61, 0xb7, 0xa8, 0x38,
0x6d, 0x79, 0xd4, 0xa5, 0x7a, 0xf7, 0xd7, 0xf5, 0xcc, 0x27, 0xff, 0xdd, 0xa4, 0xde, 0x16, 0x4c,
0xea, 0xdf, 0xd8, 0x24, 0x42, 0x32, 0x9f, 0x63, 0xdc, 0x46, 0x72, 0xd0, 0x20, 0x59, 0x05, 0xd6,
0x48, 0xb6, 0x97, 0xc0, 0xf9, 0x69, 0x09, 0x7e, 0xe1, 0xa4, 0xbb, 0x75, 0x27, 0xbf, 0xda, 0xd0,
0x23, 0x9b, 0x36, 0x76, 0xf6, 0x1e, 0xb8, 0xab, 0xfe, 0xeb, 0x73, 0x0e, 0xd6, 0x8d, 0xb3, 0x03,
0x80, 0x44, 0x16, 0xa9, 0x8e, 0xaa, 0x5f, 0x45, 0x6d, 0xa0, 0x4b, 0xca, 0x65, 0x99, 0x21, 0x7b,
0x04, 0x23, 0x13, 0xe6, 0xb3, 0x19, 0x2a, 0x25, 0x73, 0xaf, 0x6b, 0x3a, 0x27, 0xf5, 0xac, 0x16,
0xd7, 0x55, 0x32, 0xae, 0x17, 0xe4, 0x56, 0x53, 0xe5, 0x03, 0xd7, 0x8b, 0x3f, 0xef, 0x2b, 0x35,
0xfd, 0x5b, 0x14, 0x1a, 0xb4, 0x06, 0x2d, 0xb4, 0x36, 0xf0, 0x70, 0xb6, 0x80, 0x87, 0x7b, 0x63,
0x3c, 0x4e, 0xe0, 0x6e, 0x8d, 0xc7, 0x55, 0x2e, 0x93, 0x36, 0x23, 0x40, 0x00, 0xdc, 0x36, 0xd1,
0xd7, 0xb9, 0x4c, 0x56, 0x9c, 0xfc, 0x97, 0xc7, 0xd3, 0x3e, 0x75, 0x75, 0xf2, 0x3d, 0x00, 0x00,
0xff, 0xff, 0x17, 0x1c, 0xfc, 0x89, 0xd8, 0x05, 0x00, 0x00,
// 603 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0xdd, 0x6e, 0xd3, 0x30,
0x14, 0xc7, 0xd5, 0xa6, 0x9f, 0x27, 0x5d, 0x37, 0x2c, 0x84, 0x4c, 0xa5, 0x41, 0x37, 0x69, 0x28,
0x70, 0x91, 0x49, 0xe3, 0x86, 0x8d, 0x0b, 0x34, 0xc1, 0x80, 0x09, 0x21, 0xa1, 0x68, 0x5c, 0x47,
0x6e, 0xe2, 0xb5, 0xd6, 0x92, 0x38, 0x8a, 0x1d, 0x44, 0x5e, 0x87, 0x97, 0xe1, 0x69, 0x78, 0x07,
0xe4, 0xe3, 0xa6, 0x0d, 0x74, 0x7c, 0x4c, 0xdb, 0x9d, 0xf3, 0x3f, 0xc7, 0xc7, 0x27, 0xe7, 0xff,
0x3b, 0xe0, 0xea, 0x2a, 0xe7, 0xca, 0xcf, 0x0b, 0xa9, 0x25, 0x19, 0x88, 0x98, 0x67, 0x5a, 0xe8,
0x6a, 0xf2, 0x78, 0x2e, 0xe5, 0x3c, 0xe1, 0x87, 0xa8, 0xcf, 0xca, 0xcb, 0x43, 0x2d, 0x52, 0xae,
0x34, 0x4b, 0x73, 0x9b, 0xba, 0xff, 0xad, 0x03, 0xdd, 0x77, 0x85, 0x2c, 0x73, 0x32, 0x86, 0xb6,
0x88, 0x69, 0x6b, 0xda, 0xf2, 0x86, 0x41, 0x5b, 0xc4, 0x84, 0x40, 0x27, 0x63, 0x29, 0xa7, 0x6d,
0x54, 0xf0, 0x4c, 0x26, 0x30, 0xc8, 0x65, 0x22, 0x22, 0xc1, 0x15, 0x75, 0xa6, 0x8e, 0x37, 0x0c,
0x56, 0xdf, 0xc4, 0x83, 0x9d, 0x9c, 0x15, 0x3c, 0xd3, 0xe1, 0xdc, 0xd4, 0x0b, 0x45, 0xac, 0x68,
0x07, 0x73, 0xc6, 0x56, 0xc7, 0x67, 0xce, 0x63, 0x45, 0x9e, 0xc1, 0xbd, 0x94, 0xa7, 0x33, 0x5e,
0x84, 0xb6, 0x4b, 0x4c, 0xed, 0x62, 0xea, 0xb6, 0x0d, 0x9c, 0xa1, 0x6e, 0x72, 0x8f, 0x61, 0x90,
0x72, 0xcd, 0x62, 0xa6, 0x19, 0xed, 0x4d, 0x1d, 0xcf, 0x3d, 0xda, 0xf5, 0xeb, 0xbf, 0xf3, 0xb1,
0xa2, 0xff, 0x71, 0x19, 0x3f, 0xcb, 0x74, 0x51, 0x05, 0xab, 0x74, 0xf2, 0x0a, 0xb6, 0xa2, 0x82,
0x33, 0x2d, 0x64, 0x16, 0x9a, 0xdf, 0xa6, 0xfd, 0x69, 0xcb, 0x73, 0x8f, 0x26, 0xbe, 0x9d, 0x89,
0x5f, 0xcf, 0xc4, 0xbf, 0xa8, 0x67, 0x12, 0x8c, 0xea, 0x0b, 0x46, 0x22, 0x6f, 0x60, 0x27, 0x61,
0x4a, 0x87, 0x65, 0x1e, 0x33, 0xcd, 0x6d, 0x8d, 0xc1, 0x3f, 0x6b, 0x8c, 0xcd, 0x9d, 0xcf, 0x78,
0x05, 0xab, 0xec, 0xc1, 0x28, 0x95, 0xb1, 0xb8, 0xac, 0x42, 0x91, 0xc5, 0xfc, 0x2b, 0x1d, 0x4e,
0x5b, 0x5e, 0x27, 0x70, 0xad, 0x76, 0x6e, 0x24, 0xf2, 0x04, 0xb6, 0x67, 0x65, 0x74, 0xc5, 0x75,
0x78, 0xc5, 0xab, 0x70, 0xc1, 0xd4, 0x82, 0x02, 0x4e, 0x7d, 0xcb, 0xca, 0x1f, 0x78, 0xf5, 0x9e,
0xa9, 0x05, 0x39, 0x80, 0x2e, 0x4b, 0x04, 0x53, 0xd4, 0xc5, 0x2e, 0xb6, 0xd7, 0x93, 0x38, 0x35,
0x72, 0x60, 0xa3, 0xc6, 0x39, 0x43, 0x03, 0x1d, 0x59, 0xe7, 0xcc, 0x79, 0xf2, 0x12, 0xb6, 0x7e,
0x99, 0x13, 0xd9, 0x01, 0xe7, 0x8a, 0x57, 0x4b, 0xbf, 0xcd, 0x91, 0xdc, 0x87, 0xee, 0x17, 0x96,
0x94, 0xb5, 0xe3, 0xf6, 0xe3, 0xa4, 0xfd, 0xa2, 0xb5, 0xff, 0xdd, 0x81, 0x9e, 0xb5, 0x84, 0x3c,
0x85, 0x3e, 0x3e, 0xc2, 0x15, 0x6d, 0xa1, 0x1d, 0x1b, 0x4d, 0xd4, 0xf1, 0x25, 0x50, 0xed, 0x0d,
0xa0, 0x9c, 0x06, 0x50, 0x27, 0x0d, 0x7b, 0x3b, 0x58, 0xef, 0xd1, 0xba, 0x9e, 0x7d, 0xf2, 0xff,
0xfd, 0xed, 0xde, 0x81, 0xbf, 0xbd, 0x1b, 0xfb, 0x8b, 0x34, 0x17, 0x73, 0x1e, 0x37, 0x69, 0xee,
0xd7, 0x34, 0x9b, 0xc0, 0x9a, 0xe6, 0xe6, 0xfe, 0x0c, 0x7e, 0xdb, 0x9f, 0x6b, 0x20, 0x18, 0x5e,
0x03, 0xc1, 0xed, 0x9c, 0xfc, 0xe1, 0x40, 0x17, 0x6d, 0xda, 0x58, 0xf7, 0x3d, 0x18, 0x45, 0x2c,
0x93, 0x99, 0x88, 0x58, 0x12, 0xae, 0x7c, 0x73, 0x57, 0xda, 0x79, 0x4c, 0x76, 0x01, 0x52, 0x59,
0x66, 0x3a, 0x44, 0xba, 0xac, 0x8d, 0x43, 0x54, 0x2e, 0xaa, 0x9c, 0x93, 0x03, 0x18, 0xdb, 0x30,
0x8b, 0x22, 0xae, 0x94, 0x2c, 0x68, 0xc7, 0xf6, 0x8f, 0xea, 0xe9, 0x52, 0x5c, 0x57, 0xc9, 0x99,
0x5e, 0xa0, 0x67, 0x75, 0x95, 0x4f, 0x4c, 0x2f, 0xfe, 0xbe, 0xf0, 0xd8, 0xfa, 0x1f, 0x81, 0xa8,
0x01, 0xeb, 0x37, 0x00, 0xdb, 0x80, 0x64, 0x70, 0x07, 0x90, 0x0c, 0x6f, 0x0c, 0xc9, 0x31, 0x3c,
0x5c, 0x42, 0x72, 0x59, 0xc8, 0x34, 0x6c, 0x4e, 0x5a, 0x51, 0x40, 0x12, 0x1e, 0xd8, 0x84, 0xb7,
0x85, 0x4c, 0x5f, 0xaf, 0x87, 0xae, 0x6e, 0xe5, 0xf7, 0xac, 0x87, 0xbd, 0x3d, 0xff, 0x19, 0x00,
0x00, 0xff, 0xff, 0x8e, 0x4a, 0xc5, 0xdb, 0x1f, 0x06, 0x00, 0x00,
}

View File

@ -42,6 +42,17 @@ message Group {
// the groups belonging to a particular bucket during invalidation of the
// storage key.
string bucket_key_hash = 10;
// Alias is used to mark this group as an internal mapping of a group that
// is external to the identity store. Alias can only be set if the 'type'
// is set to 'external'.
Alias alias = 11;
// Type indicates if this group is an internal group or an external group.
// Memberships of the internal groups can be managed over the API whereas
// the memberships on the external group --for which a corresponding alias
// will be set-- will be managed automatically.
string type = 12;
}
@ -108,8 +119,8 @@ message Alias {
// ID is the unique identifier that represents this alias
string id = 1;
// EntityID is the entity identifier to which this alias belongs to
string entity_id = 2;
// CanonicalID is the entity identifier to which this alias belongs to
string canonical_id = 2;
// MountType is the backend mount's type to which this alias belongs to.
// This enables categorically querying aliases of specific backend types.
@ -130,8 +141,8 @@ message Alias {
map<string, string> metadata = 6;
// Name is the identifier of this alias in its authentication source.
// This does not uniquely identify a alias in Vault. This in conjunction
// with MountAccessor form to be the factors that represent a alias in a
// This does not uniquely identify an alias in Vault. This in conjunction
// with MountAccessor form to be the factors that represent an alias in a
// unique way. Aliases will be indexed based on this combined uniqueness
// factor.
string name = 7;
@ -144,8 +155,6 @@ message Alias {
// on its age and to take action on them, if desired.
google.protobuf.Timestamp last_update_time = 9;
// MergedFromEntityIDs is the FIFO history of merging activity by entity IDs from
// which this alias is transfered over to the entity to which it
// currently belongs to.
repeated string merged_from_entity_ids = 10;
// MergedFromCanonicalIDs is the FIFO history of merging activity
repeated string merged_from_canonical_ids = 10;
}

View File

@ -58,7 +58,13 @@ type Auth struct {
// Alias is the information about the authenticated client returned by
// the auth backend
Alias *Alias `json:"alias" structs:"alias" mapstructure:"alias"`
Alias *Alias `json:"alias" mapstructure:"alias" structs:"alias"`
// GroupAliases are the informational mappings of external groups which an
// authenticated user belongs to. This is used to check if there are
// mappings groups for the group aliases in identity store. For all the
// matching groups, the entity ID of the user will be added.
GroupAliases []*Alias `json:"group_aliases" mapstructure:"group_aliases" structs:"group_aliases"`
}
func (a *Auth) GoString() string {

View File

@ -695,7 +695,7 @@ func (c *Core) fetchACLTokenEntryAndEntity(clientToken string) (*ACL, *TokenEntr
if te.EntityID != "" {
//c.logger.Debug("core: entity set on the token", "entity_id", te.EntityID)
// Fetch entity for the entity ID in the token entry
entity, err = c.identityStore.memDBEntityByID(te.EntityID, false)
entity, err = c.identityStore.MemDBEntityByID(te.EntityID, false)
if err != nil {
c.logger.Error("core: failed to lookup entity using its ID", "error", err)
return nil, nil, nil, ErrInternalError
@ -705,7 +705,7 @@ func (c *Core) fetchACLTokenEntryAndEntity(clientToken string) (*ACL, *TokenEntr
// If there was no corresponding entity object found, it is
// possible that the entity got merged into another entity. Try
// finding entity based on the merged entity index.
entity, err = c.identityStore.memDBEntityByMergedEntityID(te.EntityID, false)
entity, err = c.identityStore.MemDBEntityByMergedEntityID(te.EntityID, false)
if err != nil {
c.logger.Error("core: failed to lookup entity in merged entity ID index", "error", err)
return nil, nil, nil, ErrInternalError

View File

@ -15,13 +15,13 @@ func lookupPaths(i *IdentityStore) []*framework.Path {
Fields: map[string]*framework.FieldSchema{
"type": {
Type: framework.TypeString,
Description: "Type of lookup. Current supported values are 'by_id' and 'by_name'",
Description: "Type of lookup. Current supported values are 'id' and 'name'",
},
"group_name": {
"name": {
Type: framework.TypeString,
Description: "Name of the group.",
},
"group_id": {
"id": {
Type: framework.TypeString,
Description: "ID of the group.",
},
@ -33,6 +33,68 @@ func lookupPaths(i *IdentityStore) []*framework.Path {
HelpSynopsis: strings.TrimSpace(lookupHelp["lookup-group"][0]),
HelpDescription: strings.TrimSpace(lookupHelp["lookup-group"][1]),
},
{
Pattern: "lookup/entity-alias$",
Fields: map[string]*framework.FieldSchema{
"type": {
Type: framework.TypeString,
Description: "Type of lookup. Current supported values are 'id', 'canonical_id' and 'factors'.",
},
"id": {
Type: framework.TypeString,
Description: "ID of the entity.",
},
"canonical_id": {
Type: framework.TypeString,
Description: "ID of the entity to which the alias belongs to.",
},
"name": {
Type: framework.TypeString,
Description: "Name of the alias.",
},
"mount_accessor": {
Type: framework.TypeString,
Description: "Accessor of the mount to which the entity alias belongs to.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: i.pathLookupEntityAliasUpdate,
},
HelpSynopsis: strings.TrimSpace(lookupHelp["lookup-entity-alias"][0]),
HelpDescription: strings.TrimSpace(lookupHelp["lookup-entity-alias"][1]),
},
{
Pattern: "lookup/group-alias$",
Fields: map[string]*framework.FieldSchema{
"type": {
Type: framework.TypeString,
Description: "Type of lookup. Current supported values are 'id', 'canonical_id' and 'factors'.",
},
"id": {
Type: framework.TypeString,
Description: "ID of the group.",
},
"canonical_id": {
Type: framework.TypeString,
Description: "ID of the group to which the alias belongs to.",
},
"name": {
Type: framework.TypeString,
Description: "Name of the alias.",
},
"mount_accessor": {
Type: framework.TypeString,
Description: "Accessor of the mount to which the group alias belongs to.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: i.pathLookupGroupAliasUpdate,
},
HelpSynopsis: strings.TrimSpace(lookupHelp["lookup-group-alias"][0]),
HelpDescription: strings.TrimSpace(lookupHelp["lookup-group-alias"][1]),
},
}
}
@ -43,22 +105,22 @@ func (i *IdentityStore) pathLookupGroupUpdate(req *logical.Request, d *framework
}
switch lookupType {
case "by_id":
groupID := d.Get("group_id").(string)
case "id":
groupID := d.Get("id").(string)
if groupID == "" {
return logical.ErrorResponse("empty group_id"), nil
return logical.ErrorResponse("empty ID"), nil
}
group, err := i.memDBGroupByID(groupID, false)
group, err := i.MemDBGroupByID(groupID, false)
if err != nil {
return nil, err
}
return i.handleGroupReadCommon(group)
case "by_name":
groupName := d.Get("group_name").(string)
case "name":
groupName := d.Get("name").(string)
if groupName == "" {
return logical.ErrorResponse("empty group_name"), nil
return logical.ErrorResponse("empty name"), nil
}
group, err := i.memDBGroupByName(groupName, false)
group, err := i.MemDBGroupByName(groupName, false)
if err != nil {
return nil, err
}
@ -70,9 +132,99 @@ func (i *IdentityStore) pathLookupGroupUpdate(req *logical.Request, d *framework
return nil, nil
}
func (i *IdentityStore) pathLookupEntityAliasUpdate(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
return i.handleLookupAliasUpdateCommon(req, d, false)
}
func (i *IdentityStore) pathLookupGroupAliasUpdate(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
return i.handleLookupAliasUpdateCommon(req, d, true)
}
func (i *IdentityStore) handleLookupAliasUpdateCommon(req *logical.Request, d *framework.FieldData, groupAlias bool) (*logical.Response, error) {
lookupType := d.Get("type").(string)
if lookupType == "" {
return logical.ErrorResponse("empty type"), nil
}
switch lookupType {
case "id":
aliasID := d.Get("id").(string)
if aliasID == "" {
return logical.ErrorResponse("empty ID"), nil
}
alias, err := i.MemDBAliasByID(aliasID, false, groupAlias)
if err != nil {
return nil, err
}
return i.handleAliasReadCommon(alias)
case "canonical_id":
canonicalID := d.Get("canonical_id").(string)
if canonicalID == "" {
return logical.ErrorResponse("empty canonical_id"), nil
}
alias, err := i.MemDBAliasByCanonicalID(canonicalID, false, groupAlias)
if err != nil {
return nil, err
}
return i.handleAliasReadCommon(alias)
case "factors":
aliasName := d.Get("name").(string)
if aliasName == "" {
return logical.ErrorResponse("empty name"), nil
}
mountAccessor := d.Get("mount_accessor").(string)
if mountAccessor == "" {
return logical.ErrorResponse("empty 'mount_accessor'"), nil
}
alias, err := i.MemDBAliasByFactors(mountAccessor, aliasName, false, groupAlias)
if err != nil {
return nil, err
}
return i.handleAliasReadCommon(alias)
default:
return logical.ErrorResponse(fmt.Sprintf("unrecognized type %q", lookupType)), nil
}
}
var lookupHelp = map[string][2]string{
"lookup-group": {
"Query groups based on factors.",
"Currently this supports querying groups by its name or ID.",
"Query groups based on types.",
`Supported types:
- 'id'
To query the group by its ID. This requires 'id' parameter to be set.
- 'name'
To query the group by its name. This requires 'name' parameter to be set.
`,
},
"lookup-group-alias": {
"Query group alias based on types.",
`Supported types:
- 'id'
To query the group alias by its ID. This requires 'id' parameter to be set.
- 'canonical_id'
To query the group alias by the ID of the group it belongs to. This requires the 'canonical_id' parameter to be set.
- 'factors'
To query the group alias using the factors that uniquely identifies a group alias; its name and the mount accessor. This requires the 'name' and 'mount_accessor' parameters to be set.
`,
},
"lookup-entity-alias": {
"Query entity alias based on types.",
`Supported types:
- 'id'
To query the entity alias by its ID. This requires 'id' parameter to be set.
- 'canonical_id'
To query the entity alias by the ID of the entity it belongs to. This requires the 'canonical_id' parameter to be set.
- 'factors'
To query the entity alias using the factors that uniquely identifies an entity alias; its name and the mount accessor. This requires the 'name' and 'mount_accessor' parameters to be set.
`,
},
}

View File

@ -0,0 +1,219 @@
package vault
import (
"testing"
"github.com/hashicorp/vault/logical"
)
func TestIdentityStore_Lookup_EntityAlias(t *testing.T) {
var err error
var resp *logical.Response
i, accessor, _ := testIdentityStoreWithGithubAuth(t)
entityReq := &logical.Request{
Path: "entity",
Operation: logical.UpdateOperation,
}
resp, err = i.HandleRequest(entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
entityID := resp.Data["id"].(string)
entityAliasReq := &logical.Request{
Path: "entity-alias",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"canonical_id": entityID,
"name": "testentityaliasname",
"mount_type": "ldap",
"mount_accessor": accessor,
},
}
resp, err = i.HandleRequest(entityAliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
entityAliasID := resp.Data["id"].(string)
lookupReq := &logical.Request{
Path: "lookup/entity-alias",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"type": "id",
"id": entityAliasID,
},
}
resp, err = i.HandleRequest(lookupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
if resp.Data["id"].(string) != entityAliasID {
t.Fatalf("bad: group alias: %#v\n", resp.Data)
}
lookupReq.Data = map[string]interface{}{
"type": "factors",
"mount_accessor": accessor,
"name": "testentityaliasname",
}
resp, err = i.HandleRequest(lookupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
if resp.Data["id"].(string) != entityAliasID {
t.Fatalf("bad: entity alias: %#v\n", resp.Data)
}
entityReq = &logical.Request{
Path: "entity/id/" + entityID,
Operation: logical.ReadOperation,
}
resp, err = i.HandleRequest(entityReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
lookupReq.Data = map[string]interface{}{
"type": "canonical_id",
"canonical_id": entityID,
}
resp, err = i.HandleRequest(lookupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
if resp.Data["id"].(string) != entityAliasID {
t.Fatalf("bad: entity alias: %#v\n", resp.Data)
}
}
func TestIdentityStore_Lookup_GroupAlias(t *testing.T) {
var err error
var resp *logical.Response
i, accessor, _ := testIdentityStoreWithGithubAuth(t)
groupReq := &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"type": "external",
},
}
resp, err = i.HandleRequest(groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
groupID := resp.Data["id"].(string)
groupAliasReq := &logical.Request{
Path: "group-alias",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"canonical_id": groupID,
"name": "testgroupaliasname",
"mount_type": "ldap",
"mount_accessor": accessor,
},
}
resp, err = i.HandleRequest(groupAliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
groupAliasID := resp.Data["id"].(string)
lookupReq := &logical.Request{
Path: "lookup/group-alias",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"type": "id",
"id": groupAliasID,
},
}
resp, err = i.HandleRequest(lookupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
if resp.Data["id"].(string) != groupAliasID {
t.Fatalf("bad: group alias: %#v\n", resp.Data)
}
lookupReq.Data = map[string]interface{}{
"type": "factors",
"mount_accessor": accessor,
"name": "testgroupaliasname",
}
resp, err = i.HandleRequest(lookupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
if resp.Data["id"].(string) != groupAliasID {
t.Fatalf("bad: group alias: %#v\n", resp.Data)
}
lookupReq.Data = map[string]interface{}{
"type": "canonical_id",
"canonical_id": groupID,
}
resp, err = i.HandleRequest(lookupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
if resp.Data["id"].(string) != groupAliasID {
t.Fatalf("bad: group alias: %#v\n", resp.Data)
}
}
func TestIdentityStore_Lookup_Group(t *testing.T) {
var err error
var resp *logical.Response
i, _, _ := testIdentityStoreWithGithubAuth(t)
groupReq := &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
}
resp, err = i.HandleRequest(groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
groupID := resp.Data["id"].(string)
groupName := resp.Data["name"].(string)
lookupGroupReq := &logical.Request{
Path: "lookup/group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"type": "id",
"id": groupID,
},
}
resp, err = i.HandleRequest(lookupGroupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
if resp.Data["id"].(string) != groupID {
t.Fatalf("failed to lookup group")
}
lookupGroupReq.Data = map[string]interface{}{
"type": "name",
"name": groupName,
}
resp, err = i.HandleRequest(lookupGroupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\n err: %#v\n", resp, err)
}
if resp.Data["id"].(string) != groupID {
t.Fatalf("failed to lookup group")
}
}

View File

@ -17,6 +17,10 @@ const (
groupBucketsPrefix = "packer/group/buckets/"
)
func (c *Core) IdentityStore() *IdentityStore {
return c.identityStore
}
// NewIdentityStore creates a new identity store
func NewIdentityStore(core *Core, config *logical.BackendConfig) (*IdentityStore, error) {
var err error
@ -50,6 +54,7 @@ func NewIdentityStore(core *Core, config *logical.BackendConfig) (*IdentityStore
Paths: framework.PathAppend(
entityPaths(iStore),
aliasPaths(iStore),
groupAliasPaths(iStore),
groupPaths(iStore),
lookupPaths(iStore),
upgradePaths(iStore),
@ -90,7 +95,7 @@ func (i *IdentityStore) Invalidate(key string) {
// entry key of the entity bucket. Fetch all the entities that
// belong to this bucket using the hash value. Remove these entities
// from MemDB along with all the aliases of each entity.
entitiesFetched, err := i.memDBEntitiesByBucketEntryKeyHashInTxn(txn, string(bucketKeyHash))
entitiesFetched, err := i.MemDBEntitiesByBucketEntryKeyHashInTxn(txn, string(bucketKeyHash))
if err != nil {
i.logger.Error("failed to fetch entities using the bucket entry key hash", "bucket_entry_key_hash", bucketKeyHash)
return
@ -106,7 +111,7 @@ func (i *IdentityStore) Invalidate(key string) {
}
// Delete the entity using the same transaction
err = i.memDBDeleteEntityByIDInTxn(txn, entity.ID)
err = i.MemDBDeleteEntityByIDInTxn(txn, entity.ID)
if err != nil {
i.logger.Error("failed to delete entity from MemDB", "entity_id", entity.ID, "error", err)
return
@ -160,7 +165,7 @@ func (i *IdentityStore) Invalidate(key string) {
txn := i.db.Txn(true)
defer txn.Abort()
groupsFetched, err := i.memDBGroupsByBucketEntryKeyHashInTxn(txn, string(bucketKeyHash))
groupsFetched, err := i.MemDBGroupsByBucketEntryKeyHashInTxn(txn, string(bucketKeyHash))
if err != nil {
i.logger.Error("failed to fetch groups using the bucket entry key hash", "bucket_entry_key_hash", bucketKeyHash)
return
@ -168,7 +173,7 @@ func (i *IdentityStore) Invalidate(key string) {
for _, group := range groupsFetched {
// Delete the group using the same transaction
err = i.memDBDeleteGroupByIDInTxn(txn, group.ID)
err = i.MemDBDeleteGroupByIDInTxn(txn, group.ID)
if err != nil {
i.logger.Error("failed to delete group from MemDB", "group_id", group.ID, "error", err)
return
@ -232,9 +237,9 @@ func (i *IdentityStore) parseGroupFromBucketItem(item *storagepacker.Item) (*ide
return &group, nil
}
// EntityByAliasFactors fetches the entity based on factors of alias, i.e mount
// entityByAliasFactors fetches the entity based on factors of alias, i.e mount
// accessor and the alias name.
func (i *IdentityStore) EntityByAliasFactors(mountAccessor, aliasName string, clone bool) (*identity.Entity, error) {
func (i *IdentityStore) entityByAliasFactors(mountAccessor, aliasName string, clone bool) (*identity.Entity, error) {
if mountAccessor == "" {
return nil, fmt.Errorf("missing mount accessor")
}
@ -243,7 +248,7 @@ func (i *IdentityStore) EntityByAliasFactors(mountAccessor, aliasName string, cl
return nil, fmt.Errorf("missing alias name")
}
alias, err := i.memDBAliasByFactors(mountAccessor, aliasName, false)
alias, err := i.MemDBAliasByFactors(mountAccessor, aliasName, false, false)
if err != nil {
return nil, err
}
@ -252,11 +257,11 @@ func (i *IdentityStore) EntityByAliasFactors(mountAccessor, aliasName string, cl
return nil, nil
}
return i.memDBEntityByAliasID(alias.ID, clone)
return i.MemDBEntityByAliasID(alias.ID, clone)
}
// CreateEntity creates a new entity. This is used by core to
// associate each login attempt by a alias to a unified entity in Vault.
// associate each login attempt by an alias to a unified entity in Vault.
func (i *IdentityStore) CreateEntity(alias *logical.Alias) (*identity.Entity, error) {
var entity *identity.Entity
var err error
@ -279,7 +284,7 @@ func (i *IdentityStore) CreateEntity(alias *logical.Alias) (*identity.Entity, er
}
// Check if an entity already exists for the given alais
entity, err = i.EntityByAliasFactors(alias.MountAccessor, alias.Name, false)
entity, err = i.entityByAliasFactors(alias.MountAccessor, alias.Name, false)
if err != nil {
return nil, err
}
@ -296,7 +301,7 @@ func (i *IdentityStore) CreateEntity(alias *logical.Alias) (*identity.Entity, er
// Create a new alias
newAlias := &identity.Alias{
EntityID: entity.ID,
CanonicalID: entity.ID,
Name: alias.Name,
MountAccessor: alias.MountAccessor,
MountPath: mountValidationResp.MountPath,

View File

@ -13,10 +13,47 @@ import (
// aliasPaths returns the API endpoints to operate on aliases.
// Following are the paths supported:
// alias - To register/modify a alias
// alias - To register/modify an alias
// alias/id - To lookup, delete and list aliases based on ID
func aliasPaths(i *IdentityStore) []*framework.Path {
return []*framework.Path{
{
Pattern: "entity-alias$",
Fields: map[string]*framework.FieldSchema{
"id": {
Type: framework.TypeString,
Description: "ID of the alias",
},
// entity_id is deprecated
"entity_id": {
Type: framework.TypeString,
Description: "Entity ID to which this alias belongs to",
},
"canonical_id": {
Type: framework.TypeString,
Description: "Entity ID to which this alias belongs to",
},
"mount_accessor": {
Type: framework.TypeString,
Description: "Mount accessor to which this alias belongs to",
},
"name": {
Type: framework.TypeString,
Description: "Name of the alias",
},
"metadata": {
Type: framework.TypeStringSlice,
Description: "Metadata to be associated with the alias. Format should be a list of `key=value` pairs.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: i.pathAliasRegister,
},
HelpSynopsis: strings.TrimSpace(aliasHelp["alias"][0]),
HelpDescription: strings.TrimSpace(aliasHelp["alias"][1]),
},
// BC path for identity/entity-alias
{
Pattern: "alias$",
Fields: map[string]*framework.FieldSchema{
@ -24,10 +61,15 @@ func aliasPaths(i *IdentityStore) []*framework.Path {
Type: framework.TypeString,
Description: "ID of the alias",
},
// entity_id is deprecated
"entity_id": {
Type: framework.TypeString,
Description: "Entity ID to which this alias belongs to",
},
"canonical_id": {
Type: framework.TypeString,
Description: "Entity ID to which this alias belongs to",
},
"mount_accessor": {
Type: framework.TypeString,
Description: "Mount accessor to which this alias belongs to",
@ -48,6 +90,45 @@ func aliasPaths(i *IdentityStore) []*framework.Path {
HelpSynopsis: strings.TrimSpace(aliasHelp["alias"][0]),
HelpDescription: strings.TrimSpace(aliasHelp["alias"][1]),
},
{
Pattern: "entity-alias/id/" + framework.GenericNameRegex("id"),
Fields: map[string]*framework.FieldSchema{
"id": {
Type: framework.TypeString,
Description: "ID of the alias",
},
// entity_id is deprecated
"entity_id": {
Type: framework.TypeString,
Description: "Entity ID to which this alias belongs to",
},
"canonical_id": {
Type: framework.TypeString,
Description: "Entity ID to which this alias should be tied to",
},
"mount_accessor": {
Type: framework.TypeString,
Description: "Mount accessor to which this alias belongs to",
},
"name": {
Type: framework.TypeString,
Description: "Name of the alias",
},
"metadata": {
Type: framework.TypeStringSlice,
Description: "Metadata to be associated with the alias. Format should be a comma separated list of `key=value` pairs.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: i.pathAliasIDUpdate,
logical.ReadOperation: i.pathAliasIDRead,
logical.DeleteOperation: i.pathAliasIDDelete,
},
HelpSynopsis: strings.TrimSpace(aliasHelp["alias-id"][0]),
HelpDescription: strings.TrimSpace(aliasHelp["alias-id"][1]),
},
// BC path for identity/entity-alias/id/<id>
{
Pattern: "alias/id/" + framework.GenericNameRegex("id"),
Fields: map[string]*framework.FieldSchema{
@ -55,10 +136,15 @@ func aliasPaths(i *IdentityStore) []*framework.Path {
Type: framework.TypeString,
Description: "ID of the alias",
},
// entity_id is deprecated
"entity_id": {
Type: framework.TypeString,
Description: "Entity ID to which this alias should be tied to",
},
"canonical_id": {
Type: framework.TypeString,
Description: "Entity ID to which this alias should be tied to",
},
"mount_accessor": {
Type: framework.TypeString,
Description: "Mount accessor to which this alias belongs to",
@ -81,6 +167,16 @@ func aliasPaths(i *IdentityStore) []*framework.Path {
HelpSynopsis: strings.TrimSpace(aliasHelp["alias-id"][0]),
HelpDescription: strings.TrimSpace(aliasHelp["alias-id"][1]),
},
{
Pattern: "entity-alias/id/?$",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: i.pathAliasIDList,
},
HelpSynopsis: strings.TrimSpace(aliasHelp["alias-id-list"][0]),
HelpDescription: strings.TrimSpace(aliasHelp["alias-id-list"][1]),
},
// BC path for identity/alias/id
{
Pattern: "alias/id/?$",
Callbacks: map[logical.Operation]framework.OperationFunc{
@ -103,17 +199,17 @@ func (i *IdentityStore) pathAliasRegister(req *logical.Request, d *framework.Fie
return i.handleAliasUpdateCommon(req, d, nil)
}
// pathAliasIDUpdate is used to update a alias based on the given
// pathAliasIDUpdate is used to update an alias based on the given
// alias ID
func (i *IdentityStore) pathAliasIDUpdate(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
// Get alias id
aliasID := d.Get("id").(string)
if aliasID == "" {
return logical.ErrorResponse("missing alias ID"), nil
return logical.ErrorResponse("empty alias ID"), nil
}
alias, err := i.memDBAliasByID(aliasID, true)
alias, err := i.MemDBAliasByID(aliasID, true, false)
if err != nil {
return nil, err
}
@ -124,7 +220,7 @@ func (i *IdentityStore) pathAliasIDUpdate(req *logical.Request, d *framework.Fie
return i.handleAliasUpdateCommon(req, d, alias)
}
// handleAliasUpdateCommon is used to update a alias
// handleAliasUpdateCommon is used to update an alias
func (i *IdentityStore) handleAliasUpdateCommon(req *logical.Request, d *framework.FieldData, alias *identity.Alias) (*logical.Response, error) {
var err error
var newAlias bool
@ -139,9 +235,13 @@ func (i *IdentityStore) handleAliasUpdateCommon(req *logical.Request, d *framewo
}
// Get entity id
entityID := d.Get("entity_id").(string)
if entityID != "" {
entity, err = i.memDBEntityByID(entityID, true)
canonicalID := d.Get("entity_id").(string)
if canonicalID == "" {
canonicalID = d.Get("canonical_id").(string)
}
if canonicalID != "" {
entity, err = i.MemDBEntityByID(canonicalID, true)
if err != nil {
return nil, err
}
@ -179,7 +279,7 @@ func (i *IdentityStore) handleAliasUpdateCommon(req *logical.Request, d *framewo
}
}
aliasByFactors, err := i.memDBAliasByFactors(mountValidationResp.MountAccessor, aliasName, false)
aliasByFactors, err := i.MemDBAliasByFactors(mountValidationResp.MountAccessor, aliasName, false, false)
if err != nil {
return nil, err
}
@ -191,7 +291,7 @@ func (i *IdentityStore) handleAliasUpdateCommon(req *logical.Request, d *framewo
return logical.ErrorResponse("combination of mount and alias name is already in use"), nil
}
// If this is a alias being tied to a non-existent entity, create
// If this is an alias being tied to a non-existent entity, create
// a new entity for it.
if entity == nil {
entity = &identity.Entity{
@ -210,7 +310,7 @@ func (i *IdentityStore) handleAliasUpdateCommon(req *logical.Request, d *framewo
}
// Fetch the entity to which the alias is tied to
existingEntity, err := i.memDBEntityByAliasID(alias.ID, true)
existingEntity, err := i.MemDBEntityByAliasID(alias.ID, true)
if err != nil {
return nil, err
}
@ -253,9 +353,9 @@ func (i *IdentityStore) handleAliasUpdateCommon(req *logical.Request, d *framewo
alias.MountAccessor = mountValidationResp.MountAccessor
alias.MountPath = mountValidationResp.MountPath
// Set the entity ID in the alias index. This should be done after
// Set the canonical ID in the alias index. This should be done after
// sanitizing entity.
alias.EntityID = entity.ID
alias.CanonicalID = entity.ID
// ID creation and other validations
err = i.sanitizeAlias(alias)
@ -274,14 +374,14 @@ func (i *IdentityStore) handleAliasUpdateCommon(req *logical.Request, d *framewo
// Return ID of both alias and entity
resp.Data = map[string]interface{}{
"id": alias.ID,
"entity_id": entity.ID,
"id": alias.ID,
"canonical_id": entity.ID,
}
return resp, nil
}
// pathAliasIDRead returns the properties of a alias for a given
// pathAliasIDRead returns the properties of an alias for a given
// alias ID
func (i *IdentityStore) pathAliasIDRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
aliasID := d.Get("id").(string)
@ -289,24 +389,28 @@ func (i *IdentityStore) pathAliasIDRead(req *logical.Request, d *framework.Field
return logical.ErrorResponse("missing alias id"), nil
}
alias, err := i.memDBAliasByID(aliasID, false)
alias, err := i.MemDBAliasByID(aliasID, false, false)
if err != nil {
return nil, err
}
return i.handleAliasReadCommon(alias)
}
func (i *IdentityStore) handleAliasReadCommon(alias *identity.Alias) (*logical.Response, error) {
if alias == nil {
return nil, nil
}
respData := map[string]interface{}{}
respData["id"] = alias.ID
respData["entity_id"] = alias.EntityID
respData["canonical_id"] = alias.CanonicalID
respData["mount_type"] = alias.MountType
respData["mount_accessor"] = alias.MountAccessor
respData["mount_path"] = alias.MountPath
respData["metadata"] = alias.Metadata
respData["name"] = alias.Name
respData["merged_from_entity_ids"] = alias.MergedFromEntityIDs
respData["merged_from_canonical_ids"] = alias.MergedFromCanonicalIDs
// Convert protobuf timestamp into RFC3339 format
respData["creation_time"] = ptypes.TimestampString(alias.CreationTime)
@ -317,7 +421,7 @@ func (i *IdentityStore) pathAliasIDRead(req *logical.Request, d *framework.Field
}, nil
}
// pathAliasIDDelete deleted the alias for a given alias ID
// pathAliasIDDelete deletes the alias for a given alias ID
func (i *IdentityStore) pathAliasIDDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
aliasID := d.Get("id").(string)
if aliasID == "" {
@ -331,7 +435,7 @@ func (i *IdentityStore) pathAliasIDDelete(req *logical.Request, d *framework.Fie
// store
func (i *IdentityStore) pathAliasIDList(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
ws := memdb.NewWatchSet()
iter, err := i.memDBAliases(ws)
iter, err := i.MemDBAliases(ws, false)
if err != nil {
return nil, fmt.Errorf("failed to fetch iterator for aliases in memdb: %v", err)
}
@ -350,15 +454,15 @@ func (i *IdentityStore) pathAliasIDList(req *logical.Request, d *framework.Field
var aliasHelp = map[string][2]string{
"alias": {
"Create a new alias",
"Create a new alias.",
"",
},
"alias-id": {
"Update, read or delete an entity using alias ID",
"Update, read or delete an alias ID.",
"",
},
"alias-id-list": {
"List all the entity IDs",
"List all the entity IDs.",
"",
},
}

View File

@ -27,7 +27,7 @@ func TestIdentityStore_ListAlias(t *testing.T) {
}
entityID := resp.Data["id"].(string)
// Create a alias
// Create an alias
aliasData := map[string]interface{}{
"name": "testaliasname",
"mount_accessor": githubAccessor,
@ -82,7 +82,7 @@ func TestIdentityStore_AliasSameAliasNames(t *testing.T) {
Data: aliasData,
}
// Register a alias
// Register an alias
resp, err = is.HandleRequest(aliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
@ -118,13 +118,13 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
entity.BucketKeyHash = is.entityPacker.BucketKeyHashByItemID(entity.ID)
err = is.memDBUpsertEntity(entity)
err = is.MemDBUpsertEntity(entity)
if err != nil {
t.Fatal(err)
}
alias := &identity.Alias{
EntityID: entity.ID,
CanonicalID: entity.ID,
ID: "testaliasid",
MountAccessor: githubAccessor,
MountType: validateMountResp.MountType,
@ -135,12 +135,12 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
},
}
err = is.memDBUpsertAlias(alias)
err = is.MemDBUpsertAlias(alias, false)
if err != nil {
t.Fatal(err)
}
aliasFetched, err := is.memDBAliasByID("testaliasid", false)
aliasFetched, err := is.MemDBAliasByID("testaliasid", false, false)
if err != nil {
t.Fatal(err)
}
@ -149,7 +149,7 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched)
}
aliasFetched, err = is.memDBAliasByEntityID(entity.ID, false)
aliasFetched, err = is.MemDBAliasByCanonicalID(entity.ID, false, false)
if err != nil {
t.Fatal(err)
}
@ -158,7 +158,7 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched)
}
aliasFetched, err = is.memDBAliasByFactors(validateMountResp.MountAccessor, "testaliasname", false)
aliasFetched, err = is.MemDBAliasByFactors(validateMountResp.MountAccessor, "testaliasname", false, false)
if err != nil {
t.Fatal(err)
}
@ -167,9 +167,9 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched)
}
aliasesFetched, err := is.memDBAliasesByMetadata(map[string]string{
aliasesFetched, err := is.MemDBAliasesByMetadata(map[string]string{
"testkey1": "testmetadatavalue1",
}, false)
}, false, false)
if err != nil {
t.Fatal(err)
}
@ -182,9 +182,9 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched)
}
aliasesFetched, err = is.memDBAliasesByMetadata(map[string]string{
aliasesFetched, err = is.MemDBAliasesByMetadata(map[string]string{
"testkey2": "testmetadatavalue2",
}, false)
}, false, false)
if err != nil {
t.Fatal(err)
}
@ -197,10 +197,10 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched)
}
aliasesFetched, err = is.memDBAliasesByMetadata(map[string]string{
aliasesFetched, err = is.MemDBAliasesByMetadata(map[string]string{
"testkey1": "testmetadatavalue1",
"testkey2": "testmetadatavalue2",
}, false)
}, false, false)
if err != nil {
t.Fatal(err)
}
@ -214,7 +214,7 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
}
alias2 := &identity.Alias{
EntityID: entity.ID,
CanonicalID: entity.ID,
ID: "testaliasid2",
MountAccessor: validateMountResp.MountAccessor,
MountType: validateMountResp.MountType,
@ -225,14 +225,14 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
},
}
err = is.memDBUpsertAlias(alias2)
err = is.MemDBUpsertAlias(alias2, false)
if err != nil {
t.Fatal(err)
}
aliasesFetched, err = is.memDBAliasesByMetadata(map[string]string{
aliasesFetched, err = is.MemDBAliasesByMetadata(map[string]string{
"testkey1": "testmetadatavalue1",
}, false)
}, false, false)
if err != nil {
t.Fatal(err)
}
@ -241,9 +241,9 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
t.Fatalf("bad: length of aliases; expected: 2, actual: %d", len(aliasesFetched))
}
aliasesFetched, err = is.memDBAliasesByMetadata(map[string]string{
aliasesFetched, err = is.MemDBAliasesByMetadata(map[string]string{
"testkey3": "testmetadatavalue3",
}, false)
}, false, false)
if err != nil {
t.Fatal(err)
}
@ -252,12 +252,12 @@ func TestIdentityStore_MemDBAliasIndexes(t *testing.T) {
t.Fatalf("bad: length of aliases; expected: 1, actual: %d", len(aliasesFetched))
}
err = is.memDBDeleteAliasByID("testaliasid")
err = is.MemDBDeleteAliasByID("testaliasid", false)
if err != nil {
t.Fatal(err)
}
aliasFetched, err = is.memDBAliasByID("testaliasid", false)
aliasFetched, err = is.MemDBAliasByID("testaliasid", false, false)
if err != nil {
t.Fatal(err)
}
@ -305,7 +305,7 @@ func TestIdentityStore_AliasRegister(t *testing.T) {
t.Fatalf("invalid alias id in alias register response")
}
entityIDRaw, ok := resp.Data["entity_id"]
entityIDRaw, ok := resp.Data["canonical_id"]
if !ok {
t.Fatalf("entity id not present in alias register response")
}
@ -333,7 +333,7 @@ func TestIdentityStore_AliasUpdate(t *testing.T) {
Data: aliasData,
}
// This will create a alias and a corresponding entity
// This will create an alias and a corresponding entity
resp, err = is.HandleRequest(aliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("err:%v resp:%#v", err, resp)
@ -508,7 +508,7 @@ func TestIdentityStore_AliasReadDelete(t *testing.T) {
}
if resp.Data["id"].(string) == "" ||
resp.Data["entity_id"].(string) == "" ||
resp.Data["canonical_id"].(string) == "" ||
resp.Data["name"].(string) != registerData["name"] ||
resp.Data["mount_type"].(string) != "github" {
t.Fatalf("bad: alias read response; \nexpected: %#v \nactual: %#v\n", registerData, resp.Data)

View File

@ -125,7 +125,7 @@ func (i *IdentityStore) pathEntityMergeID(req *logical.Request, d *framework.Fie
force := d.Get("force").(bool)
toEntityForLocking, err := i.memDBEntityByID(toEntityID, false)
toEntityForLocking, err := i.MemDBEntityByID(toEntityID, false)
if err != nil {
return nil, err
}
@ -144,7 +144,7 @@ func (i *IdentityStore) pathEntityMergeID(req *logical.Request, d *framework.Fie
defer txn.Abort()
// Re-read post lock acquisition
toEntity, err := i.memDBEntityByID(toEntityID, true)
toEntity, err := i.MemDBEntityByID(toEntityID, true)
if err != nil {
return nil, err
}
@ -163,7 +163,7 @@ func (i *IdentityStore) pathEntityMergeID(req *logical.Request, d *framework.Fie
return logical.ErrorResponse("to_entity_id should not be present in from_entity_ids"), nil
}
lockFromEntity, err := i.memDBEntityByID(fromEntityID, false)
lockFromEntity, err := i.MemDBEntityByID(fromEntityID, false)
if err != nil {
return nil, err
}
@ -186,7 +186,7 @@ func (i *IdentityStore) pathEntityMergeID(req *logical.Request, d *framework.Fie
}
// Re-read the entities post lock acquisition
fromEntity, err := i.memDBEntityByID(fromEntityID, false)
fromEntity, err := i.MemDBEntityByID(fromEntityID, false)
if err != nil {
if fromLockHeld {
fromEntityLock.Unlock()
@ -209,13 +209,12 @@ func (i *IdentityStore) pathEntityMergeID(req *logical.Request, d *framework.Fie
}
for _, alias := range fromEntity.Aliases {
// Set the desired entity id
alias.EntityID = toEntity.ID
// Set the desired canonical ID
alias.CanonicalID = toEntity.ID
// Set the entity id of which this alias is now an alias to
alias.MergedFromEntityIDs = append(alias.MergedFromEntityIDs, fromEntity.ID)
alias.MergedFromCanonicalIDs = append(alias.MergedFromCanonicalIDs, fromEntity.ID)
err = i.memDBUpsertAliasInTxn(txn, alias)
err = i.MemDBUpsertAliasInTxn(txn, alias, false)
if err != nil {
if fromLockHeld {
fromEntityLock.Unlock()
@ -237,7 +236,7 @@ func (i *IdentityStore) pathEntityMergeID(req *logical.Request, d *framework.Fie
toEntity.MergedEntityIDs = append(toEntity.MergedEntityIDs, fromEntity.ID)
// Delete the entity which we are merging from in MemDB using the same transaction
err = i.memDBDeleteEntityByIDInTxn(txn, fromEntity.ID)
err = i.MemDBDeleteEntityByIDInTxn(txn, fromEntity.ID)
if err != nil {
if fromLockHeld {
fromEntityLock.Unlock()
@ -264,7 +263,7 @@ func (i *IdentityStore) pathEntityMergeID(req *logical.Request, d *framework.Fie
}
// Update MemDB with changes to the entity we are merging to
err = i.memDBUpsertEntityInTxn(txn, toEntity)
err = i.MemDBUpsertEntityInTxn(txn, toEntity)
if err != nil {
return nil, err
}
@ -310,7 +309,7 @@ func (i *IdentityStore) pathEntityIDUpdate(req *logical.Request, d *framework.Fi
return logical.ErrorResponse("missing entity id"), nil
}
entity, err := i.memDBEntityByID(entityID, true)
entity, err := i.MemDBEntityByID(entityID, true)
if err != nil {
return nil, err
}
@ -342,7 +341,7 @@ func (i *IdentityStore) handleEntityUpdateCommon(req *logical.Request, d *framew
// Get the name
entityName := d.Get("name").(string)
if entityName != "" {
entityByName, err := i.memDBEntityByName(entityName, false)
entityByName, err := i.MemDBEntityByName(entityName, false)
if err != nil {
return nil, err
}
@ -403,7 +402,7 @@ func (i *IdentityStore) pathEntityIDRead(req *logical.Request, d *framework.Fiel
return logical.ErrorResponse("missing entity id"), nil
}
entity, err := i.memDBEntityByID(entityID, false)
entity, err := i.MemDBEntityByID(entityID, false)
if err != nil {
return nil, err
}
@ -427,13 +426,13 @@ func (i *IdentityStore) pathEntityIDRead(req *logical.Request, d *framework.Fiel
for aliasIdx, alias := range entity.Aliases {
aliasMap := map[string]interface{}{}
aliasMap["id"] = alias.ID
aliasMap["entity_id"] = alias.EntityID
aliasMap["canonical_id"] = alias.CanonicalID
aliasMap["mount_type"] = alias.MountType
aliasMap["mount_accessor"] = alias.MountAccessor
aliasMap["mount_path"] = alias.MountPath
aliasMap["metadata"] = alias.Metadata
aliasMap["name"] = alias.Name
aliasMap["merged_from_entity_ids"] = alias.MergedFromEntityIDs
aliasMap["merged_from_canonical_ids"] = alias.MergedFromCanonicalIDs
aliasMap["creation_time"] = ptypes.TimestampString(alias.CreationTime)
aliasMap["last_update_time"] = ptypes.TimestampString(alias.LastUpdateTime)
aliasesToReturn[aliasIdx] = aliasMap
@ -464,7 +463,7 @@ func (i *IdentityStore) pathEntityIDDelete(req *logical.Request, d *framework.Fi
// store
func (i *IdentityStore) pathEntityIDList(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
ws := memdb.NewWatchSet()
iter, err := i.memDBEntities(ws)
iter, err := i.MemDBEntities(ws)
if err != nil {
return nil, fmt.Errorf("failed to fetch iterator for entities in memdb: %v", err)
}

View File

@ -70,9 +70,9 @@ func TestIdentityStore_EntityCreateUpdate(t *testing.T) {
func TestIdentityStore_CloneImmutability(t *testing.T) {
alias := &identity.Alias{
ID: "testaliasid",
Name: "testaliasname",
MergedFromEntityIDs: []string{"entityid1"},
ID: "testaliasid",
Name: "testaliasname",
MergedFromCanonicalIDs: []string{"entityid1"},
}
entity := &identity.Entity{
@ -100,9 +100,9 @@ func TestIdentityStore_CloneImmutability(t *testing.T) {
t.Fatal(err)
}
alias.MergedFromEntityIDs[0] = "invalidid"
alias.MergedFromCanonicalIDs[0] = "invalidid"
if clonedAlias.MergedFromEntityIDs[0] == "invalidid" {
if clonedAlias.MergedFromCanonicalIDs[0] == "invalidid" {
t.Fatalf("cloned alias is mutated")
}
}
@ -117,7 +117,7 @@ func TestIdentityStore_MemDBImmutability(t *testing.T) {
}
alias1 := &identity.Alias{
EntityID: "testentityid",
CanonicalID: "testentityid",
ID: "testaliasid",
MountAccessor: githubAccessor,
MountType: validateMountResp.MountType,
@ -141,12 +141,12 @@ func TestIdentityStore_MemDBImmutability(t *testing.T) {
entity.BucketKeyHash = is.entityPacker.BucketKeyHashByItemID(entity.ID)
err = is.memDBUpsertEntity(entity)
err = is.MemDBUpsertEntity(entity)
if err != nil {
t.Fatal(err)
}
entityFetched, err := is.memDBEntityByID(entity.ID, true)
entityFetched, err := is.MemDBEntityByID(entity.ID, true)
if err != nil {
t.Fatal(err)
}
@ -154,7 +154,7 @@ func TestIdentityStore_MemDBImmutability(t *testing.T) {
// Modify the fetched entity outside of a transaction
entityFetched.Aliases[0].ID = "invalidaliasid"
entityFetched, err = is.memDBEntityByID(entity.ID, false)
entityFetched, err = is.MemDBEntityByID(entity.ID, false)
if err != nil {
t.Fatal(err)
}
@ -360,7 +360,7 @@ func TestIdentityStore_MemDBEntityIndexes(t *testing.T) {
}
alias1 := &identity.Alias{
EntityID: "testentityid",
CanonicalID: "testentityid",
ID: "testaliasid",
MountAccessor: githubAccessor,
MountType: validateMountResp.MountType,
@ -372,7 +372,7 @@ func TestIdentityStore_MemDBEntityIndexes(t *testing.T) {
}
alias2 := &identity.Alias{
EntityID: "testentityid",
CanonicalID: "testentityid",
ID: "testaliasid2",
MountAccessor: validateMountResp.MountAccessor,
MountType: validateMountResp.MountType,
@ -397,13 +397,13 @@ func TestIdentityStore_MemDBEntityIndexes(t *testing.T) {
entity.BucketKeyHash = is.entityPacker.BucketKeyHashByItemID(entity.ID)
err = is.memDBUpsertEntity(entity)
err = is.MemDBUpsertEntity(entity)
if err != nil {
t.Fatal(err)
}
// Fetch the entity using its ID
entityFetched, err := is.memDBEntityByID(entity.ID, false)
entityFetched, err := is.MemDBEntityByID(entity.ID, false)
if err != nil {
t.Fatal(err)
}
@ -413,7 +413,7 @@ func TestIdentityStore_MemDBEntityIndexes(t *testing.T) {
}
// Fetch the entity using its name
entityFetched, err = is.memDBEntityByName(entity.Name, false)
entityFetched, err = is.MemDBEntityByName(entity.Name, false)
if err != nil {
t.Fatal(err)
}
@ -423,7 +423,7 @@ func TestIdentityStore_MemDBEntityIndexes(t *testing.T) {
}
// Fetch entities using the metadata
entitiesFetched, err := is.memDBEntitiesByMetadata(map[string]string{
entitiesFetched, err := is.MemDBEntitiesByMetadata(map[string]string{
"someusefulkey": "someusefulvalue",
}, false)
if err != nil {
@ -438,7 +438,7 @@ func TestIdentityStore_MemDBEntityIndexes(t *testing.T) {
t.Fatalf("entity mismatch; entity: %#v\n entitiesFetched[0]: %#v\n", entity, entitiesFetched[0])
}
entitiesFetched, err = is.memDBEntitiesByBucketEntryKeyHash(entity.BucketKeyHash)
entitiesFetched, err = is.MemDBEntitiesByBucketEntryKeyHash(entity.BucketKeyHash)
if err != nil {
t.Fatal(err)
}
@ -447,12 +447,12 @@ func TestIdentityStore_MemDBEntityIndexes(t *testing.T) {
t.Fatalf("bad: length of entities; expected: 1, actual: %d", len(entitiesFetched))
}
err = is.memDBDeleteEntityByID(entity.ID)
err = is.MemDBDeleteEntityByID(entity.ID)
if err != nil {
t.Fatal(err)
}
entityFetched, err = is.memDBEntityByID(entity.ID, false)
entityFetched, err = is.MemDBEntityByID(entity.ID, false)
if err != nil {
t.Fatal(err)
}
@ -461,7 +461,7 @@ func TestIdentityStore_MemDBEntityIndexes(t *testing.T) {
t.Fatalf("bad: entity; expected: nil, actual: %#v\n", entityFetched)
}
entityFetched, err = is.memDBEntityByName(entity.Name, false)
entityFetched, err = is.MemDBEntityByName(entity.Name, false)
if err != nil {
t.Fatal(err)
}
@ -678,7 +678,7 @@ func TestIdentityStore_MergeEntitiesByID(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entity1, err := is.memDBEntityByID(entityID1, false)
entity1, err := is.MemDBEntityByID(entityID1, false)
if err != nil {
t.Fatal(err)
}
@ -720,7 +720,7 @@ func TestIdentityStore_MergeEntitiesByID(t *testing.T) {
t.Fatalf("err:%v resp:%#v", err, resp)
}
entity2, err := is.memDBEntityByID(entityID2, false)
entity2, err := is.MemDBEntityByID(entityID2, false)
if err != nil {
t.Fatal(err)
}
@ -772,7 +772,7 @@ func TestIdentityStore_MergeEntitiesByID(t *testing.T) {
for _, aliasRaw := range entity2Aliases {
alias := aliasRaw.(map[string]interface{})
aliasLookedUp, err := is.memDBAliasByID(alias["id"].(string), false)
aliasLookedUp, err := is.MemDBAliasByID(alias["id"].(string), false, false)
if err != nil {
t.Fatal(err)
}

View File

@ -0,0 +1,279 @@
package vault
import (
"fmt"
"strings"
memdb "github.com/hashicorp/go-memdb"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
func groupAliasPaths(i *IdentityStore) []*framework.Path {
return []*framework.Path{
{
Pattern: "group-alias$",
Fields: map[string]*framework.FieldSchema{
"id": {
Type: framework.TypeString,
Description: "ID of the group alias.",
},
"name": {
Type: framework.TypeString,
Description: "Alias of the group.",
},
"mount_accessor": {
Type: framework.TypeString,
Description: "Mount accessor to which this alias belongs to.",
},
"canonical_id": {
Type: framework.TypeString,
Description: "ID of the group to which this is an alias.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: i.pathGroupAliasRegister,
},
HelpSynopsis: strings.TrimSpace(groupAliasHelp["group-alias"][0]),
HelpDescription: strings.TrimSpace(groupAliasHelp["group-alias"][1]),
},
{
Pattern: "group-alias/id/" + framework.GenericNameRegex("id"),
Fields: map[string]*framework.FieldSchema{
"id": {
Type: framework.TypeString,
Description: "ID of the group alias.",
},
"name": {
Type: framework.TypeString,
Description: "Alias of the group.",
},
"mount_accessor": {
Type: framework.TypeString,
Description: "Mount accessor to which this alias belongs to.",
},
"canonical_id": {
Type: framework.TypeString,
Description: "ID of the group to which this is an alias.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: i.pathGroupAliasIDUpdate,
logical.ReadOperation: i.pathGroupAliasIDRead,
logical.DeleteOperation: i.pathGroupAliasIDDelete,
},
HelpSynopsis: strings.TrimSpace(groupAliasHelp["group-alias-by-id"][0]),
HelpDescription: strings.TrimSpace(groupHelp["group-alias-by-id"][1]),
},
{
Pattern: "group-alias/id/?$",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: i.pathGroupAliasIDList,
},
HelpSynopsis: strings.TrimSpace(entityHelp["group-alias-id-list"][0]),
HelpDescription: strings.TrimSpace(entityHelp["group-alias-id-list"][1]),
},
}
}
func (i *IdentityStore) pathGroupAliasRegister(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
_, ok := d.GetOk("id")
if ok {
return i.pathGroupAliasIDUpdate(req, d)
}
i.groupLock.Lock()
defer i.groupLock.Unlock()
return i.handleGroupAliasUpdateCommon(req, d, nil)
}
func (i *IdentityStore) pathGroupAliasIDUpdate(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
groupAliasID := d.Get("id").(string)
if groupAliasID == "" {
return logical.ErrorResponse("empty group alias ID"), nil
}
i.groupLock.Lock()
defer i.groupLock.Unlock()
groupAlias, err := i.MemDBAliasByID(groupAliasID, true, true)
if err != nil {
return nil, err
}
if groupAlias == nil {
return logical.ErrorResponse("invalid group alias ID"), nil
}
return i.handleGroupAliasUpdateCommon(req, d, groupAlias)
}
func (i *IdentityStore) handleGroupAliasUpdateCommon(req *logical.Request, d *framework.FieldData, groupAlias *identity.Alias) (*logical.Response, error) {
var err error
var newGroupAlias bool
var group *identity.Group
if groupAlias == nil {
groupAlias = &identity.Alias{}
newGroupAlias = true
}
groupID := d.Get("canonical_id").(string)
if groupID != "" {
group, err = i.MemDBGroupByID(groupID, true)
if err != nil {
return nil, err
}
if group == nil {
return logical.ErrorResponse("invalid group ID"), nil
}
if group.Type != groupTypeExternal {
return logical.ErrorResponse("alias can't be set on an internal group"), nil
}
}
// Get group alias name
groupAliasName := d.Get("name").(string)
if groupAliasName == "" {
return logical.ErrorResponse("missing alias name"), nil
}
mountAccessor := d.Get("mount_accessor").(string)
if mountAccessor == "" {
return logical.ErrorResponse("missing mount_accessor"), nil
}
mountValidationResp := i.validateMountAccessorFunc(mountAccessor)
if mountValidationResp == nil {
return logical.ErrorResponse(fmt.Sprintf("invalid mount accessor %q", mountAccessor)), nil
}
groupAliasByFactors, err := i.MemDBAliasByFactors(mountValidationResp.MountAccessor, groupAliasName, false, true)
if err != nil {
return nil, err
}
resp := &logical.Response{}
if newGroupAlias {
if groupAliasByFactors != nil {
return logical.ErrorResponse("combination of mount and group alias name is already in use"), nil
}
// If this is an alias being tied to a non-existent group, create
// a new group for it.
if group == nil {
group = &identity.Group{
Type: groupTypeExternal,
Alias: groupAlias,
}
} else {
group.Alias = groupAlias
}
} else {
// Verify that the combination of group alias name and mount is not
// already tied to a different alias
if groupAliasByFactors != nil && groupAliasByFactors.ID != groupAlias.ID {
return logical.ErrorResponse("combination of mount and group alias name is already in use"), nil
}
// Fetch the group to which the alias is tied to
existingGroup, err := i.MemDBGroupByAliasID(groupAlias.ID, true)
if err != nil {
return nil, err
}
if existingGroup == nil {
return nil, fmt.Errorf("group alias is not associated with a group")
}
if group != nil && group.ID != existingGroup.ID {
return logical.ErrorResponse("alias is already tied to a different group"), nil
}
group = existingGroup
group.Alias = groupAlias
}
group.Alias.Name = groupAliasName
group.Alias.MountType = mountValidationResp.MountType
group.Alias.MountAccessor = mountValidationResp.MountAccessor
err = i.sanitizeAndUpsertGroup(group, nil)
if err != nil {
return nil, err
}
resp.Data = map[string]interface{}{
"id": groupAlias.ID,
"canonical_id": group.ID,
}
return resp, nil
}
// pathGroupAliasIDRead returns the properties of an alias for a given
// alias ID
func (i *IdentityStore) pathGroupAliasIDRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
groupAliasID := d.Get("id").(string)
if groupAliasID == "" {
return logical.ErrorResponse("empty group alias id"), nil
}
groupAlias, err := i.MemDBAliasByID(groupAliasID, false, true)
if err != nil {
return nil, err
}
return i.handleAliasReadCommon(groupAlias)
}
// pathGroupAliasIDDelete deletes the group's alias for a given group alias ID
func (i *IdentityStore) pathGroupAliasIDDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
groupAliasID := d.Get("id").(string)
if groupAliasID == "" {
return logical.ErrorResponse("missing group alias ID"), nil
}
return nil, i.deleteGroupAlias(groupAliasID)
}
// pathGroupAliasIDList lists the IDs of all the valid group aliases in the
// identity store
func (i *IdentityStore) pathGroupAliasIDList(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
ws := memdb.NewWatchSet()
iter, err := i.MemDBAliases(ws, true)
if err != nil {
return nil, fmt.Errorf("failed to fetch iterator for group aliases in memdb: %v", err)
}
var groupAliasIDs []string
for {
raw := iter.Next()
if raw == nil {
break
}
groupAliasIDs = append(groupAliasIDs, raw.(*identity.Alias).ID)
}
return logical.ListResponse(groupAliasIDs), nil
}
var groupAliasHelp = map[string][2]string{
"group-alias": {
"Creates a new group alias, or updates an existing one.",
"",
},
"group-alias-id": {
"Update, read or delete a group alias using ID.",
"",
},
"group-alias-id-list": {
"List all the entity IDs.",
"",
},
}

View File

@ -0,0 +1,163 @@
package vault
import (
"testing"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/logical"
)
func TestIdentityStore_GroupAliases_CRUD(t *testing.T) {
var resp *logical.Response
var err error
i, accessor, _ := testIdentityStoreWithGithubAuth(t)
groupReq := &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"type": "external",
},
}
resp, err = i.HandleRequest(groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
}
groupID := resp.Data["id"].(string)
groupAliasReq := &logical.Request{
Path: "group-alias",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": "testgroupalias",
"mount_accessor": accessor,
"canonical_id": groupID,
"mount_type": "ldap",
},
}
resp, err = i.HandleRequest(groupAliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
}
groupAliasID := resp.Data["id"].(string)
groupAliasReq.Path = "group-alias/id/" + groupAliasID
groupAliasReq.Operation = logical.ReadOperation
resp, err = i.HandleRequest(groupAliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
}
if resp.Data["id"].(string) != groupAliasID {
t.Fatalf("bad: group alias: %#v\n", resp.Data)
}
groupAliasReq.Operation = logical.DeleteOperation
resp, err = i.HandleRequest(groupAliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
}
groupAliasReq.Operation = logical.ReadOperation
resp, err = i.HandleRequest(groupAliasReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err)
}
if resp != nil {
t.Fatalf("failed to delete group alias")
}
}
func TestIdentityStore_GroupAliases_MemDBIndexes(t *testing.T) {
var err error
i, accessor, _ := testIdentityStoreWithGithubAuth(t)
group := &identity.Group{
ID: "testgroupid",
Name: "testgroupname",
Metadata: map[string]string{
"testmetadatakey1": "testmetadatavalue1",
"testmetadatakey2": "testmetadatavalue2",
},
Alias: &identity.Alias{
ID: "testgroupaliasid",
Name: "testalias",
MountAccessor: accessor,
CanonicalID: "testgroupid",
MountType: "ldap",
},
ParentGroupIDs: []string{"testparentgroupid1", "testparentgroupid2"},
MemberEntityIDs: []string{"testentityid1", "testentityid2"},
Policies: []string{"testpolicy1", "testpolicy2"},
BucketKeyHash: i.groupPacker.BucketKeyHashByItemID("testgroupid"),
}
err = i.MemDBUpsertAlias(group.Alias, true)
if err != nil {
t.Fatal(err)
}
err = i.MemDBUpsertGroup(group)
if err != nil {
t.Fatal(err)
}
alias, err := i.MemDBAliasByID("testgroupaliasid", false, true)
if err != nil {
t.Fatal(err)
}
if alias.ID != "testgroupaliasid" {
t.Fatalf("bad: group alias: %#v\n", alias)
}
group, err = i.MemDBGroupByAliasID("testgroupaliasid", false)
if err != nil {
t.Fatal(err)
}
if group.ID != "testgroupid" {
t.Fatalf("bad: group: %#v\n", group)
}
aliasByFactors, err := i.MemDBAliasByFactors(group.Alias.MountAccessor, group.Alias.Name, false, true)
if err != nil {
t.Fatal(err)
}
if aliasByFactors.ID != "testgroupaliasid" {
t.Fatalf("bad: group alias: %#v\n", aliasByFactors)
}
}
func TestIdentityStore_GroupAliases_AliasOnInternalGroup(t *testing.T) {
var err error
var resp *logical.Response
i, accessor, _ := testIdentityStoreWithGithubAuth(t)
groupReq := &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
}
resp, err = i.HandleRequest(groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v; err: %v", resp, err)
}
groupID := resp.Data["id"].(string)
aliasReq := &logical.Request{
Path: "group-alias",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"name": "testname",
"mount_accessor": accessor,
"canonical_id": groupID,
},
}
resp, err = i.HandleRequest(aliasReq)
if err != nil {
t.Fatal(err)
}
if !resp.IsError() {
t.Fatalf("expected an error")
}
}

View File

@ -11,6 +11,11 @@ import (
"github.com/hashicorp/vault/logical/framework"
)
const (
groupTypeInternal = "internal"
groupTypeExternal = "external"
)
func groupPaths(i *IdentityStore) []*framework.Path {
return []*framework.Path{
{
@ -20,6 +25,10 @@ func groupPaths(i *IdentityStore) []*framework.Path {
Type: framework.TypeString,
Description: "ID of the group.",
},
"type": {
Type: framework.TypeString,
Description: "Type of the group, 'internal' or 'external'. Defaults to 'internal'",
},
"name": {
Type: framework.TypeString,
Description: "Name of the group.",
@ -55,6 +64,11 @@ func groupPaths(i *IdentityStore) []*framework.Path {
Type: framework.TypeString,
Description: "ID of the group.",
},
"type": {
Type: framework.TypeString,
Default: groupTypeInternal,
Description: "Type of the group, 'internal' or 'external'. Defaults to 'internal'",
},
"name": {
Type: framework.TypeString,
Description: "Name of the group.",
@ -118,7 +132,7 @@ func (i *IdentityStore) pathGroupIDUpdate(req *logical.Request, d *framework.Fie
i.groupLock.Lock()
defer i.groupLock.Unlock()
group, err := i.memDBGroupByID(groupID, true)
group, err := i.MemDBGroupByID(groupID, true)
if err != nil {
return nil, err
}
@ -143,11 +157,30 @@ func (i *IdentityStore) handleGroupUpdateCommon(req *logical.Request, d *framewo
group.Policies = policiesRaw.([]string)
}
groupTypeRaw, ok := d.GetOk("type")
if ok {
groupType := groupTypeRaw.(string)
if group.Type != "" && groupType != group.Type {
return logical.ErrorResponse(fmt.Sprintf("group type cannot be changed")), nil
}
group.Type = groupType
}
// If group type is not set, default to internal type
if group.Type == "" {
group.Type = groupTypeInternal
}
if group.Type != groupTypeInternal && group.Type != groupTypeExternal {
return logical.ErrorResponse(fmt.Sprintf("invalid group type %q", group.Type)), nil
}
// Get the name
groupName := d.Get("name").(string)
if groupName != "" {
// Check if there is a group already existing for the given name
groupByName, err := i.memDBGroupByName(groupName, false)
groupByName, err := i.MemDBGroupByName(groupName, false)
if err != nil {
return nil, err
}
@ -173,6 +206,9 @@ func (i *IdentityStore) handleGroupUpdateCommon(req *logical.Request, d *framewo
memberEntityIDsRaw, ok := d.GetOk("member_entity_ids")
if ok {
if group.Type == groupTypeExternal {
return logical.ErrorResponse("member entities can't be set manually for external groups"), nil
}
group.MemberEntityIDs = memberEntityIDsRaw.([]string)
if len(group.MemberEntityIDs) > 512 {
return logical.ErrorResponse("member entity IDs exceeding the limit of 512"), nil
@ -182,6 +218,9 @@ func (i *IdentityStore) handleGroupUpdateCommon(req *logical.Request, d *framewo
memberGroupIDsRaw, ok := d.GetOk("member_group_ids")
var memberGroupIDs []string
if ok {
if group.Type == groupTypeExternal {
return logical.ErrorResponse("member groups can't be set for external groups"), nil
}
memberGroupIDs = memberGroupIDsRaw.([]string)
}
@ -205,20 +244,17 @@ func (i *IdentityStore) pathGroupIDRead(req *logical.Request, d *framework.Field
return logical.ErrorResponse("empty group id"), nil
}
group, err := i.memDBGroupByID(groupID, false)
group, err := i.MemDBGroupByID(groupID, false)
if err != nil {
return nil, err
}
if group == nil {
return nil, nil
}
return i.handleGroupReadCommon(group)
}
func (i *IdentityStore) handleGroupReadCommon(group *identity.Group) (*logical.Response, error) {
if group == nil {
return nil, fmt.Errorf("nil group")
return nil, nil
}
respData := map[string]interface{}{}
@ -230,6 +266,23 @@ func (i *IdentityStore) handleGroupReadCommon(group *identity.Group) (*logical.R
respData["creation_time"] = ptypes.TimestampString(group.CreationTime)
respData["last_update_time"] = ptypes.TimestampString(group.LastUpdateTime)
respData["modify_index"] = group.ModifyIndex
respData["type"] = group.Type
aliasMap := map[string]interface{}{}
if group.Alias != nil {
aliasMap["id"] = group.Alias.ID
aliasMap["canonical_id"] = group.Alias.CanonicalID
aliasMap["mount_type"] = group.Alias.MountType
aliasMap["mount_accessor"] = group.Alias.MountAccessor
aliasMap["mount_path"] = group.Alias.MountPath
aliasMap["metadata"] = group.Alias.Metadata
aliasMap["name"] = group.Alias.Name
aliasMap["merged_from_canonical_ids"] = group.Alias.MergedFromCanonicalIDs
aliasMap["creation_time"] = ptypes.TimestampString(group.Alias.CreationTime)
aliasMap["last_update_time"] = ptypes.TimestampString(group.Alias.LastUpdateTime)
}
respData["alias"] = aliasMap
memberGroupIDs, err := i.memberGroupIDsByID(group.ID)
if err != nil {
@ -253,7 +306,7 @@ func (i *IdentityStore) pathGroupIDDelete(req *logical.Request, d *framework.Fie
// pathGroupIDList lists the IDs of all the groups in the identity store
func (i *IdentityStore) pathGroupIDList(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
ws := memdb.NewWatchSet()
iter, err := i.memDBGroupIterator(ws)
iter, err := i.MemDBGroupIterator(ws)
if err != nil {
return nil, fmt.Errorf("failed to fetch iterator for group in memdb: %v", err)
}

View File

@ -9,6 +9,94 @@ import (
"github.com/hashicorp/vault/logical"
)
func TestIdentityStore_Groups_TypeMembershipAdditions(t *testing.T) {
var err error
var resp *logical.Response
i, _, _ := testIdentityStoreWithGithubAuth(t)
groupReq := &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"type": "external",
"member_entity_ids": "sampleentityid",
},
}
resp, err = i.HandleRequest(groupReq)
if err != nil {
t.Fatal(err)
}
if !resp.IsError() {
t.Fatalf("expected an error")
}
groupReq.Data = map[string]interface{}{
"type": "external",
"member_group_ids": "samplegroupid",
}
resp, err = i.HandleRequest(groupReq)
if err != nil {
t.Fatal(err)
}
if !resp.IsError() {
t.Fatalf("expected an error")
}
}
func TestIdentityStore_Groups_TypeImmutability(t *testing.T) {
var err error
var resp *logical.Response
i, _, _ := testIdentityStoreWithGithubAuth(t)
groupReq := &logical.Request{
Path: "group",
Operation: logical.UpdateOperation,
}
resp, err = i.HandleRequest(groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
internalGroupID := resp.Data["id"].(string)
groupReq.Data = map[string]interface{}{
"type": "external",
}
resp, err = i.HandleRequest(groupReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v, err: %v", resp, err)
}
externalGroupID := resp.Data["id"].(string)
// Try to mark internal group as external
groupReq.Data = map[string]interface{}{
"type": "external",
}
groupReq.Path = "group/id/" + internalGroupID
resp, err = i.HandleRequest(groupReq)
if err != nil {
t.Fatal(err)
}
if !resp.IsError() {
t.Fatalf("expected an error")
}
// Try to mark internal group as external
groupReq.Data = map[string]interface{}{
"type": "internal",
}
groupReq.Path = "group/id/" + externalGroupID
resp, err = i.HandleRequest(groupReq)
if err != nil {
t.Fatal(err)
}
if !resp.IsError() {
t.Fatalf("expected an error")
}
}
func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
var err error
i, _, _ := testIdentityStoreWithGithubAuth(t)
@ -28,7 +116,7 @@ func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
}
// Insert it into memdb
err = i.memDBUpsertGroup(group)
err = i.MemDBUpsertGroup(group)
if err != nil {
t.Fatal(err)
}
@ -48,7 +136,7 @@ func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
}
// Insert it into memdb
err = i.memDBUpsertGroup(group)
err = i.MemDBUpsertGroup(group)
if err != nil {
t.Fatal(err)
}
@ -56,7 +144,7 @@ func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
var fetchedGroup *identity.Group
// Fetch group given the name
fetchedGroup, err = i.memDBGroupByName("testgroupname", false)
fetchedGroup, err = i.MemDBGroupByName("testgroupname", false)
if err != nil {
t.Fatal(err)
}
@ -65,7 +153,7 @@ func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
}
// Fetch group given the ID
fetchedGroup, err = i.memDBGroupByID("testgroupid", false)
fetchedGroup, err = i.MemDBGroupByID("testgroupid", false)
if err != nil {
t.Fatal(err)
}
@ -75,7 +163,7 @@ func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
var fetchedGroups []*identity.Group
// Fetch the subgroups of a given group ID
fetchedGroups, err = i.memDBGroupsByParentGroupID("testparentgroupid1", false)
fetchedGroups, err = i.MemDBGroupsByParentGroupID("testparentgroupid1", false)
if err != nil {
t.Fatal(err)
}
@ -83,7 +171,7 @@ func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
t.Fatalf("failed to fetch an indexed group")
}
fetchedGroups, err = i.memDBGroupsByParentGroupID("testparentgroupid2", false)
fetchedGroups, err = i.MemDBGroupsByParentGroupID("testparentgroupid2", false)
if err != nil {
t.Fatal(err)
}
@ -92,7 +180,7 @@ func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
}
// Fetch groups based on policy name
fetchedGroups, err = i.memDBGroupsByPolicy("testpolicy1", false)
fetchedGroups, err = i.MemDBGroupsByPolicy("testpolicy1", false)
if err != nil {
t.Fatal(err)
}
@ -100,7 +188,7 @@ func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
t.Fatalf("failed to fetch an indexed group")
}
fetchedGroups, err = i.memDBGroupsByPolicy("testpolicy2", false)
fetchedGroups, err = i.MemDBGroupsByPolicy("testpolicy2", false)
if err != nil {
t.Fatal(err)
}
@ -109,7 +197,7 @@ func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
}
// Fetch groups based on member entity ID
fetchedGroups, err = i.memDBGroupsByMemberEntityID("testentityid1", false)
fetchedGroups, err = i.MemDBGroupsByMemberEntityID("testentityid1", false, false)
if err != nil {
t.Fatal(err)
}
@ -117,7 +205,7 @@ func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
t.Fatalf("failed to fetch an indexed group")
}
fetchedGroups, err = i.memDBGroupsByMemberEntityID("testentityid2", false)
fetchedGroups, err = i.MemDBGroupsByMemberEntityID("testentityid2", false, false)
if err != nil {
t.Fatal(err)
}
@ -203,12 +291,14 @@ func TestIdentityStore_GroupsCreateUpdate(t *testing.T) {
},
}
expectedData["id"] = resp.Data["id"]
expectedData["type"] = resp.Data["type"]
expectedData["name"] = resp.Data["name"]
expectedData["member_group_ids"] = resp.Data["member_group_ids"]
expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]
expectedData["creation_time"] = resp.Data["creation_time"]
expectedData["last_update_time"] = resp.Data["last_update_time"]
expectedData["modify_index"] = resp.Data["modify_index"]
expectedData["alias"] = resp.Data["alias"]
if !reflect.DeepEqual(expectedData, resp.Data) {
t.Fatalf("bad: group data;\nexpected: %#v\n actual: %#v\n", expectedData, resp.Data)
@ -321,12 +411,14 @@ func TestIdentityStore_GroupsCRUD_ByID(t *testing.T) {
},
}
expectedData["id"] = resp.Data["id"]
expectedData["type"] = resp.Data["type"]
expectedData["name"] = resp.Data["name"]
expectedData["member_group_ids"] = resp.Data["member_group_ids"]
expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]
expectedData["creation_time"] = resp.Data["creation_time"]
expectedData["last_update_time"] = resp.Data["last_update_time"]
expectedData["modify_index"] = resp.Data["modify_index"]
expectedData["alias"] = resp.Data["alias"]
if !reflect.DeepEqual(expectedData, resp.Data) {
t.Fatalf("bad: group data;\nexpected: %#v\n actual: %#v\n", expectedData, resp.Data)
@ -496,7 +588,7 @@ func TestIdentityStore_GroupHierarchyCases(t *testing.T) {
var memberGroupIDs []string
// Fetch 'eng' group
engGroup, err := is.memDBGroupByID(engGroupID, false)
engGroup, err := is.MemDBGroupByID(engGroupID, false)
if err != nil {
t.Fatal(err)
}
@ -510,7 +602,7 @@ func TestIdentityStore_GroupHierarchyCases(t *testing.T) {
t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", engMemberGroupIDs, memberGroupIDs)
}
vaultGroup, err := is.memDBGroupByID(vaultGroupID, false)
vaultGroup, err := is.MemDBGroupByID(vaultGroupID, false)
if err != nil {
t.Fatal(err)
}
@ -524,7 +616,7 @@ func TestIdentityStore_GroupHierarchyCases(t *testing.T) {
t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", vaultMemberGroupIDs, memberGroupIDs)
}
opsGroup, err := is.memDBGroupByID(opsGroupID, false)
opsGroup, err := is.MemDBGroupByID(opsGroupID, false)
if err != nil {
t.Fatal(err)
}

View File

@ -6,15 +6,23 @@ import (
memdb "github.com/hashicorp/go-memdb"
)
const (
entitiesTable = "entities"
entityAliasesTable = "entity_aliases"
groupsTable = "groups"
groupAliasesTable = "group_aliases"
)
func identityStoreSchema() *memdb.DBSchema {
iStoreSchema := &memdb.DBSchema{
Tables: make(map[string]*memdb.TableSchema),
}
schemas := []func() *memdb.TableSchema{
entityTableSchema,
entitiesTableSchema,
aliasesTableSchema,
groupTableSchema,
groupsTableSchema,
groupAliasesTableSchema,
}
for _, schemaFunc := range schemas {
@ -30,7 +38,7 @@ func identityStoreSchema() *memdb.DBSchema {
func aliasesTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "aliases",
Name: entityAliasesTable,
Indexes: map[string]*memdb.IndexSchema{
"id": &memdb.IndexSchema{
Name: "id",
@ -39,11 +47,11 @@ func aliasesTableSchema() *memdb.TableSchema {
Field: "ID",
},
},
"entity_id": &memdb.IndexSchema{
Name: "entity_id",
"canonical_id": &memdb.IndexSchema{
Name: "canonical_id",
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "EntityID",
Field: "CanonicalID",
},
},
"mount_type": &memdb.IndexSchema{
@ -79,9 +87,9 @@ func aliasesTableSchema() *memdb.TableSchema {
}
}
func entityTableSchema() *memdb.TableSchema {
func entitiesTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "entities",
Name: entitiesTable,
Indexes: map[string]*memdb.IndexSchema{
"id": &memdb.IndexSchema{
Name: "id",
@ -125,9 +133,9 @@ func entityTableSchema() *memdb.TableSchema {
}
}
func groupTableSchema() *memdb.TableSchema {
func groupsTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "groups",
Name: groupsTable,
Indexes: map[string]*memdb.IndexSchema{
"id": {
Name: "id",
@ -178,3 +186,46 @@ func groupTableSchema() *memdb.TableSchema {
},
}
}
func groupAliasesTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: groupAliasesTable,
Indexes: map[string]*memdb.IndexSchema{
"id": &memdb.IndexSchema{
Name: "id",
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "ID",
},
},
"canonical_id": &memdb.IndexSchema{
Name: "canonical_id",
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "CanonicalID",
},
},
"mount_type": &memdb.IndexSchema{
Name: "mount_type",
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "MountType",
},
},
"factors": &memdb.IndexSchema{
Name: "factors",
Unique: true,
Indexer: &memdb.CompoundIndex{
Indexes: []memdb.Indexer{
&memdb.StringFieldIndex{
Field: "MountAccessor",
},
&memdb.StringFieldIndex{
Field: "Name",
},
},
},
},
},
}
}

View File

@ -5,6 +5,7 @@ import (
"sync"
memdb "github.com/hashicorp/go-memdb"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/locksutil"
"github.com/hashicorp/vault/helper/storagepacker"
"github.com/hashicorp/vault/logical"
@ -73,3 +74,9 @@ type IdentityStore struct {
// buckets
groupPacker *storagepacker.StoragePacker
}
type groupDiff struct {
New []*identity.Group
Deleted []*identity.Group
Unmodified []*identity.Group
}

View File

@ -90,7 +90,7 @@ func TestIdentityStore_EntityByAliasFactors(t *testing.T) {
t.Fatalf("expected a non-nil response")
}
entity, err := is.EntityByAliasFactors(ghAccessor, "alias_name", false)
entity, err := is.entityByAliasFactors(ghAccessor, "alias_name", false)
if err != nil {
t.Fatal(err)
}

File diff suppressed because it is too large Load Diff

View File

@ -284,6 +284,21 @@ func (c *Core) handleRequest(req *logical.Request) (retResp *logical.Response, r
}
}
// If the request was to renew a token, and if there are group aliases set
// in the auth object, then the group memberships should be refreshed
if strings.HasPrefix(req.Path, "auth/token/renew") &&
resp != nil &&
resp.Auth != nil &&
resp.Auth.EntityID != "" &&
resp.Auth.GroupAliases != nil {
err := c.identityStore.refreshExternalGroupMembershipsByEntityID(resp.Auth.EntityID, resp.Auth.GroupAliases)
if err != nil {
c.logger.Error("core: failed to refresh external group memberships", "error", err)
retErr = multierror.Append(retErr, ErrInternalError)
return nil, auth, retErr
}
}
// Only the token store is allowed to return an auth block, for any
// other request this is an internal error. We exclude renewal of a token,
// since it does not need to be re-registered
@ -322,6 +337,7 @@ func (c *Core) handleRequest(req *logical.Request) (retResp *logical.Response, r
if routeErr != nil {
retErr = multierror.Append(retErr, routeErr)
}
return resp, auth, retErr
}
@ -412,7 +428,7 @@ func (c *Core) handleLoginRequest(req *logical.Request) (retResp *logical.Respon
var err error
// Check if an entity already exists for the given alias
entity, err = c.identityStore.EntityByAliasFactors(auth.Alias.MountAccessor, auth.Alias.Name, false)
entity, err = c.identityStore.entityByAliasFactors(auth.Alias.MountAccessor, auth.Alias.Name, false)
if err != nil {
return nil, nil, err
}
@ -430,6 +446,12 @@ func (c *Core) handleLoginRequest(req *logical.Request) (retResp *logical.Respon
}
auth.EntityID = entity.ID
if auth.GroupAliases != nil {
err = c.identityStore.refreshExternalGroupMembershipsByEntityID(auth.EntityID, auth.GroupAliases)
if err != nil {
return nil, nil, err
}
}
}
if strutil.StrListSubset(auth.Policies, []string{"root"}) {

View File

@ -471,10 +471,26 @@ func (r *Router) routeCommon(req *logical.Request, existenceCheck bool) (*logica
return nil, ok, exists, err
} else {
resp, err := re.backend.HandleRequest(req)
// When a token gets renewed, the request hits this path and reaches
// token store. Token store delegates the renewal to the expiration
// manager. Expiration manager in-turn creates a different logical
// request and forwards the request to the auth backend that had
// initially authenticated the login request. The forwarding to auth
// backend will make this code path hit for the second time for the
// same renewal request. The accessors in the Alias structs should be
// of the auth backend and not of the token store. Therefore, avoiding
// the overwriting of accessors by having a check for path prefix
// having "renew". This gets applied for "renew" and "renew-self"
// requests.
if resp != nil &&
resp.Auth != nil &&
resp.Auth.Alias != nil {
resp.Auth.Alias.MountAccessor = re.mountEntry.Accessor
!strings.HasPrefix(req.Path, "renew") {
if resp.Auth.Alias != nil {
resp.Auth.Alias.MountAccessor = re.mountEntry.Accessor
}
for _, alias := range resp.Auth.GroupAliases {
alias.MountAccessor = re.mountEntry.Accessor
}
}
return resp, false, false, err
}