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 } i.groupLock.Lock() defer i.groupLock.Unlock() txn := i.db.Txn(true) defer txn.Abort() alias, err := i.MemDBAliasByIDInTxn(txn, groupAliasID, false, true) if err != nil { return nil, err } if alias == nil { return nil, nil } group, err := i.MemDBGroupByAliasIDInTxn(txn, alias.ID, true) if err != nil { return nil, err } // If there is no group tied to a valid alias, something is wrong if group == nil { return nil, fmt.Errorf("alias not associated to a group") } // Delete group alias in memdb err = i.MemDBDeleteAliasByIDInTxn(txn, group.Alias.ID, true) if err != nil { return nil, err } // Delete the alias group.Alias = nil err = i.UpsertGroupInTxn(txn, group, true) if err != nil { return nil, err } txn.Commit() return nil, nil } } // 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.", "", }, }