open-vault/vault/identity_store_group_aliases.go
Jeff Mitchell 835b355489
Add key information to list endpoints in identity. (#4634)
* Add key information to list endpoints in identity.

Also fixes some bugs from before where we were persisting data that we
should not have been (mount type/path).

* Add cached lookups of real time mount info
2018-05-25 14:34:24 -04:00

328 lines
9.3 KiB
Go

package vault
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/errwrap"
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(groupAliasHelp["group-alias-by-id"][1]),
},
{
Pattern: "group-alias/id/?$",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: i.pathGroupAliasIDList(),
},
HelpSynopsis: strings.TrimSpace(groupAliasHelp["group-alias-id-list"][0]),
HelpDescription: strings.TrimSpace(groupAliasHelp["group-alias-id-list"][1]),
},
}
}
func (i *IdentityStore) pathGroupAliasRegister() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
_, ok := d.GetOk("id")
if ok {
return i.pathGroupAliasIDUpdate()(ctx, req, d)
}
i.groupLock.Lock()
defer i.groupLock.Unlock()
return i.handleGroupAliasUpdateCommon(req, d, nil)
}
}
func (i *IdentityStore) pathGroupAliasIDUpdate() framework.OperationFunc {
return func(ctx context.Context, 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.core.router.validateMountByAccessor(mountAccessor)
if mountValidationResp == nil {
return logical.ErrorResponse(fmt.Sprintf("invalid mount accessor %q", mountAccessor)), nil
}
if mountValidationResp.MountLocal {
return logical.ErrorResponse(fmt.Sprintf("mount_accessor %q is of a local mount", 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.MountAccessor = mountValidationResp.MountAccessor
// Explicitly correct for previous versions that persisted this
group.Alias.MountType = ""
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() framework.OperationFunc {
return func(ctx context.Context, 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() framework.OperationFunc {
return func(ctx context.Context, 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() framework.OperationFunc {
return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
ws := memdb.NewWatchSet()
iter, err := i.MemDBAliases(ws, true)
if err != nil {
return nil, errwrap.Wrapf("failed to fetch iterator for group aliases in memdb: {{err}}", err)
}
var groupAliasIDs []string
aliasInfo := map[string]interface{}{}
type mountInfo struct {
MountType string
MountPath string
}
mountAccessorMap := map[string]mountInfo{}
for {
raw := iter.Next()
if raw == nil {
break
}
alias := raw.(*identity.Alias)
groupAliasIDs = append(groupAliasIDs, alias.ID)
entry := map[string]interface{}{
"name": alias.Name,
"canonical_id": alias.CanonicalID,
"mount_accessor": alias.MountAccessor,
}
mi, ok := mountAccessorMap[alias.MountAccessor]
if ok {
entry["mount_type"] = mi.MountType
entry["mount_path"] = mi.MountPath
} else {
mi = mountInfo{}
if mountValidationResp := i.core.router.validateMountByAccessor(alias.MountAccessor); mountValidationResp != nil {
mi.MountType = mountValidationResp.MountType
mi.MountPath = mountValidationResp.MountPath
entry["mount_type"] = mi.MountType
entry["mount_path"] = mi.MountPath
}
mountAccessorMap[alias.MountAccessor] = mi
}
aliasInfo[alias.ID] = entry
}
return logical.ListResponseWithInfo(groupAliasIDs, aliasInfo), 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 group alias IDs.",
"",
},
}