Skip to content

Commit dd4b95b

Browse files
committed
VAULT-42443 CE changes
1 parent 3017247 commit dd4b95b

File tree

6 files changed

+150
-23
lines changed

6 files changed

+150
-23
lines changed

sdk/logical/identity.proto

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ message Alias {
6161
// created. If true, the alias will be stored in a location that are ignored
6262
// by the performance replication subsystem.
6363
bool local = 8;
64+
65+
// external_id is the unique external identifier for an entity managed via
66+
// an external IdP. This field does not always map 1:1 to a claim external_id.
67+
// This mapping is done via configuration.
68+
string external_id = 9;
69+
70+
// issuer is the issuer claim for this alias
71+
string issuer = 10;
6472
}
6573

6674
message Group {

vault/identity_store_aliases.go

Lines changed: 89 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package vault
55

66
import (
77
"context"
8+
"errors"
89
"fmt"
910
"strings"
1011

@@ -78,6 +79,14 @@ This field is deprecated, use canonical_id.`,
7879
Type: framework.TypeKVPairs,
7980
Description: "User provided key-value pairs",
8081
},
82+
"external_id": {
83+
Type: framework.TypeString,
84+
Description: "Unique external identifier from external IdP.",
85+
},
86+
"issuer": {
87+
Type: framework.TypeString,
88+
Description: "Issuer name associated with this alias.",
89+
},
8190
},
8291

8392
Operations: map[logical.Operation]framework.OperationHandler{
@@ -151,6 +160,14 @@ This field is deprecated, use canonical_id.`,
151160
Type: framework.TypeKVPairs,
152161
Description: "User provided key-value pairs",
153162
},
163+
"external_id": {
164+
Type: framework.TypeString,
165+
Description: "Unique external identifier from external IdP.",
166+
},
167+
"issuer": {
168+
Type: framework.TypeString,
169+
Description: "Issuer name associated with this alias.",
170+
},
154171
}
155172
}
156173

@@ -194,6 +211,12 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
194211
}
195212
}
196213

214+
// Get external_id if provided
215+
externalID := d.Get("external_id").(string)
216+
217+
// Get issuer if provided
218+
issuer := d.Get("issuer").(string)
219+
197220
i.lock.Lock()
198221
defer i.lock.Unlock()
199222

@@ -225,16 +248,24 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
225248
}
226249
name = alias.Name
227250
mountAccessor = alias.MountAccessor
251+
// Issuer can't be modified after initial creation
252+
issuer = alias.Issuer
253+
// ExternalID can't be modified after initial creation
254+
externalID = alias.ExternalID
228255
case mountAccessor == "":
229256
// No change to mount accessor
230257
mountAccessor = alias.MountAccessor
231258
case name == "":
232259
// No change to mount name
233260
name = alias.Name
261+
case externalID == "":
262+
externalID = alias.ExternalID
263+
case issuer == "":
264+
issuer = alias.Issuer
234265
default:
235266
// mountAccessor, name and customMetadata provided
236267
}
237-
return i.handleAliasUpdate(ctx, canonicalID, name, mountAccessor, alias, customMetadata)
268+
return i.handleAliasUpdate(ctx, canonicalID, name, mountAccessor, externalID, issuer, alias, customMetadata)
238269
}
239270
}
240271

@@ -254,26 +285,33 @@ func (i *IdentityStore) handleAliasCreateUpdate() framework.OperationFunc {
254285
localMount := mountEntry.Local
255286

256287
// Look up the alias by factors; if it's found it's an update
257-
return i.handleAliasCreateUpdateCommon(ctx, ns, mountAccessor, name, canonicalID, customMetadata, localMount, "")
288+
return i.handleAliasCreateUpdateCommon(ctx, ns, mountAccessor, name, canonicalID, externalID, issuer, customMetadata, localMount, "")
258289
}
259290
}
260291

261-
func (i *IdentityStore) handleAliasCreateUpdateCommon(ctx context.Context, ns *namespace.Namespace, mountAccessor string, name string, canonicalID string, customMetadata map[string]string, localMount bool, scimClientID string) (*logical.Response, error) {
292+
func (i *IdentityStore) handleAliasCreateUpdateCommon(ctx context.Context, ns *namespace.Namespace, mountAccessor string, name string, canonicalID string, externalID string, issuer string, customMetadata map[string]string, localMount bool, scimClientID string) (*logical.Response, error) {
262293
alias, err := i.MemDBAliasByFactors(mountAccessor, name, true, false)
263294
if err != nil {
264295
return nil, err
265296
}
297+
266298
if alias != nil {
267299
if alias.NamespaceID != ns.ID {
268300
return logical.ErrorResponse("cannot modify aliases across namespaces"), logical.ErrPermissionDenied
269301
}
270-
return i.handleAliasUpdate(ctx, canonicalID, name, mountAccessor, alias, customMetadata)
302+
return i.handleAliasUpdate(ctx, canonicalID, name, mountAccessor, externalID, issuer, alias, customMetadata)
271303
}
272304
// At this point we know it's a new creation request
273-
return i.handleAliasCreate(ctx, canonicalID, name, mountAccessor, localMount, customMetadata, scimClientID)
305+
return i.handleAliasCreate(ctx, canonicalID, name, mountAccessor, externalID, issuer, localMount, customMetadata, scimClientID)
306+
}
307+
308+
// doesAliasHaveExternalIdIssuerMountAccessor returns true if the given alias has
309+
// the given externalId, issuer,and mount accessor
310+
func (i *IdentityStore) doesAliasHaveExternalIdIssuerMountAccessor(alias *identity.Alias, externalId, issuer, mountAccessor string) bool {
311+
return alias.MountAccessor == mountAccessor && alias.ExternalID == externalId && alias.Issuer == issuer
274312
}
275313

276-
func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name, mountAccessor string, local bool, customMetadata map[string]string, scimClientID string) (*logical.Response, error) {
314+
func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name, mountAccessor, externalID, issuer string, local bool, customMetadata map[string]string, scimClientID string) (*logical.Response, error) {
277315
ns, err := namespace.FromContext(ctx)
278316
if err != nil {
279317
return nil, err
@@ -301,6 +339,17 @@ func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name
301339
}
302340
}
303341

342+
// Validate that an alias doesn't already exist with passed in issuer and external_id
343+
if issuer != "" && externalID != "" {
344+
existingAlias, err := i.MemDBAliasByIssuerAndExternalId(issuer, externalID, false)
345+
if err != nil && !errors.Is(err, ErrNoAliasFound) {
346+
return nil, err
347+
}
348+
if existingAlias != nil {
349+
return logical.ErrorResponse("alias already exists for issuer and external_id"), nil
350+
}
351+
}
352+
304353
persist := false
305354
// If the request was not forwarded, then this is the active node of the
306355
// primary. Create the entity here itself.
@@ -314,19 +363,26 @@ func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name
314363
}
315364

316365
for _, currentAlias := range entity.Aliases {
366+
if i.doesAliasHaveExternalIdIssuerMountAccessor(currentAlias, externalID, issuer, mountAccessor) {
367+
return logical.ErrorResponse("alias already exists for requested entity, external ID, and issuer"), nil
368+
}
369+
317370
if currentAlias.MountAccessor == mountAccessor {
318-
return logical.ErrorResponse("Alias already exists for requested entity and mount accessor"), nil
371+
return logical.ErrorResponse("alias already exists for requested entity and mount accessor"), nil
319372
}
320373
}
321374

322375
var alias *identity.Alias
376+
323377
switch local {
324378
case true:
325379
alias, err = i.processLocalAlias(ctx, &logical.Alias{
326380
MountAccessor: mountAccessor,
327381
Name: name,
328382
Local: local,
329383
CustomMetadata: customMetadata,
384+
Issuer: issuer,
385+
ExternalID: externalID,
330386
}, entity, false)
331387
if err != nil {
332388
return nil, err
@@ -338,6 +394,8 @@ func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name
338394
CustomMetadata: customMetadata,
339395
CanonicalID: entity.ID,
340396
ScimClientID: scimClientID,
397+
Issuer: issuer,
398+
ExternalID: externalID,
341399
}
342400
err = i.sanitizeAlias(ctx, alias)
343401
if err != nil {
@@ -362,10 +420,14 @@ func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name
362420
}, nil
363421
}
364422

365-
func (i *IdentityStore) handleAliasUpdate(ctx context.Context, canonicalID, name, mountAccessor string, alias *identity.Alias, customMetadata map[string]string) (*logical.Response, error) {
423+
func (i *IdentityStore) handleAliasUpdate(ctx context.Context, canonicalID, name, mountAccessor, externalID, issuer string, alias *identity.Alias, customMetadata map[string]string) (*logical.Response, error) {
424+
// Fast return if nothing to be updated
366425
if name == alias.Name &&
367426
mountAccessor == alias.MountAccessor &&
368-
(canonicalID == alias.CanonicalID || canonicalID == "") && (strutil.EqualStringMaps(customMetadata, alias.CustomMetadata)) {
427+
(canonicalID == alias.CanonicalID || canonicalID == "") &&
428+
(strutil.EqualStringMaps(customMetadata, alias.CustomMetadata)) &&
429+
(externalID == alias.ExternalID) &&
430+
(issuer == alias.Issuer) {
369431
// Nothing to do; return nil to be idempotent
370432
return nil, nil
371433
}
@@ -391,14 +453,15 @@ func (i *IdentityStore) handleAliasUpdate(ctx context.Context, canonicalID, name
391453
if mountAccessor != alias.MountAccessor && (canonicalID == "" || canonicalID == alias.CanonicalID) {
392454
for _, currentAlias := range currentEntity.Aliases {
393455
if currentAlias.MountAccessor == mountAccessor {
394-
return logical.ErrorResponse("Alias cannot be updated as the entity already has an alias for the given 'mount_accessor' "), nil
456+
return logical.ErrorResponse("alias cannot be updated as the entity already has an alias for the given 'mount_accessor' "), nil
395457
}
396458
}
397459
}
398-
// If we're changing one or the other or both of these, make sure that
399-
// there isn't a matching alias already, and make sure it's in the same
400-
// namespace.
401-
if name != alias.Name || mountAccessor != alias.MountAccessor || !strutil.EqualStringMaps(customMetadata, alias.CustomMetadata) {
460+
// If we're changing these, make sure that there isn't a matching alias already,
461+
// and make sure it's in the same namespace.
462+
if name != alias.Name || mountAccessor != alias.MountAccessor ||
463+
!strutil.EqualStringMaps(customMetadata, alias.CustomMetadata) ||
464+
issuer != alias.Issuer || externalID != alias.ExternalID {
402465
// Check here to see if such an alias already exists, if so bail
403466
mountEntry := i.router.MatchingMountByAccessor(mountAccessor)
404467
if mountEntry == nil {
@@ -418,6 +481,13 @@ func (i *IdentityStore) handleAliasUpdate(ctx context.Context, canonicalID, name
418481
return logical.ErrorResponse("alias with combination of mount accessor and name already exists"), nil
419482
}
420483

484+
// Bail if issuer and/or external_id differ. These fields are not to be changed after alias creation.
485+
if existingAlias != nil {
486+
if existingAlias.Issuer != issuer || existingAlias.ExternalID != externalID {
487+
return logical.ErrorResponse("changes to issuer and/or external_id prohibited"), nil
488+
}
489+
}
490+
421491
// Update the values in the alias
422492
alias.Name = name
423493
alias.MountAccessor = mountAccessor
@@ -450,7 +520,7 @@ func (i *IdentityStore) handleAliasUpdate(ctx context.Context, canonicalID, name
450520
// Check if the entity the alias is being updated to, already has an alias for the mount
451521
for _, alias := range newEntity.Aliases {
452522
if alias.MountAccessor == mountAccessor {
453-
return logical.ErrorResponse("Alias cannot be updated as the given entity already has an alias for this mount "), nil
523+
return logical.ErrorResponse("alias cannot be updated as the given entity already has an alias for this mount "), nil
454524
}
455525
}
456526

@@ -484,6 +554,8 @@ func (i *IdentityStore) handleAliasUpdate(ctx context.Context, canonicalID, name
484554
Name: name,
485555
Local: mountValidationResp.MountLocal,
486556
CustomMetadata: customMetadata,
557+
Issuer: issuer,
558+
ExternalID: externalID,
487559
}, newEntity, true)
488560
if err != nil {
489561
return nil, err
@@ -555,6 +627,8 @@ func (i *IdentityStore) handleAliasReadCommon(ctx context.Context, alias *identi
555627
respData["merged_from_canonical_ids"] = alias.MergedFromCanonicalIDs
556628
respData["namespace_id"] = alias.NamespaceID
557629
respData["local"] = alias.Local
630+
respData["issuer"] = alias.Issuer
631+
respData["external_id"] = alias.ExternalID
558632

559633
if mountValidationResp := i.router.ValidateMountByAccessor(alias.MountAccessor); mountValidationResp != nil {
560634
respData["mount_path"] = mountValidationResp.MountPath

vault/identity_store_schema.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import (
1010
)
1111

1212
const (
13-
entitiesTable = "entities"
14-
entityAliasesTable = "entity_aliases"
15-
groupsTable = "groups"
16-
groupAliasesTable = "group_aliases"
17-
oidcClientsTable = "oidc_clients"
18-
scimClientsTable = "scim_clients"
13+
entitiesTable = "entities"
14+
entityAliasesTable = "entity_aliases"
15+
groupsTable = "groups"
16+
groupAliasesTable = "group_aliases"
17+
oidcClientsTable = "oidc_clients"
18+
scimClientsTable = "scim_clients"
19+
factorsIndex = "factors"
20+
issuerAndExternalIdFactors = "issuer_externalid_factors"
1921
)
2022

2123
func identityStoreSchema(lowerCaseName bool) *memdb.DBSchema {
@@ -54,8 +56,8 @@ func aliasesTableSchema(lowerCaseName bool) *memdb.TableSchema {
5456
Field: "ID",
5557
},
5658
},
57-
"factors": {
58-
Name: "factors",
59+
factorsIndex: {
60+
Name: factorsIndex,
5961
Unique: true,
6062
Indexer: &memdb.CompoundIndex{
6163
Indexes: []memdb.Indexer{
@@ -69,6 +71,20 @@ func aliasesTableSchema(lowerCaseName bool) *memdb.TableSchema {
6971
},
7072
},
7173
},
74+
issuerAndExternalIdFactors: {
75+
Name: issuerAndExternalIdFactors,
76+
Indexer: &memdb.CompoundIndex{
77+
Indexes: []memdb.Indexer{
78+
&memdb.StringFieldIndex{
79+
Field: "Issuer",
80+
},
81+
&memdb.StringFieldIndex{
82+
Field: "ExternalID",
83+
},
84+
},
85+
},
86+
AllowMissing: true,
87+
},
7288
"namespace_id": {
7389
Name: "namespace_id",
7490
Indexer: &memdb.StringFieldIndex{

vault/identity_store_upgrade.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ vault <command> <path> metadata=key1=value1 metadata=key2=value2
156156
Type: framework.TypeString,
157157
Description: "Name of the alias",
158158
},
159+
"external_id": {
160+
Type: framework.TypeString,
161+
Description: "Unique external identifier from external IdP.",
162+
},
163+
"issuer": {
164+
Type: framework.TypeString,
165+
Description: "Issuer name associated with this alias.",
166+
},
159167
},
160168
Callbacks: map[logical.Operation]framework.OperationFunc{
161169
logical.UpdateOperation: i.handleAliasCreateUpdate(),

vault/identity_store_util.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,8 @@ func (i *IdentityStore) processLocalAlias(ctx context.Context, lAlias *logical.A
10581058
alias.MountType = mountValidationResp.MountType
10591059
alias.Local = lAlias.Local
10601060
alias.CustomMetadata = lAlias.CustomMetadata
1061+
alias.Issuer = lAlias.Issuer
1062+
alias.ExternalID = lAlias.ExternalID
10611063

10621064
if err := i.sanitizeAlias(ctx, alias); err != nil {
10631065
return nil, err
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright IBM Corp. 2016, 2025
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
//go:build !enterprise
5+
6+
package vault
7+
8+
import (
9+
"github.com/hashicorp/go-memdb"
10+
"github.com/hashicorp/vault/helper/identity"
11+
)
12+
13+
func (i *IdentityStore) MemDBAliasByIssuerAndExternalId(_, _ string, _ bool) (*identity.Alias, error) {
14+
return nil, nil
15+
}
16+
17+
func (i *IdentityStore) MemDBAliasByIssuerAndExternalIdInTxn(_ *memdb.Txn, _, _ string, _ bool) (*identity.Alias, error) {
18+
return nil, nil
19+
}

0 commit comments

Comments
 (0)