1package vault 2 3import ( 4 "reflect" 5 "strings" 6 "testing" 7 8 "github.com/hashicorp/vault/helper/identity" 9 "github.com/hashicorp/vault/helper/namespace" 10 "github.com/hashicorp/vault/sdk/logical" 11) 12 13// Issue 5729 14func TestIdentityStore_DuplicateAliases(t *testing.T) { 15 c, _, _ := TestCoreUnsealed(t) 16 17 resp, err := c.systemBackend.HandleRequest(namespace.RootContext(nil), &logical.Request{ 18 Path: "auth", 19 Operation: logical.ReadOperation, 20 }) 21 if err != nil || (resp != nil && resp.IsError()) { 22 t.Fatalf("bad: resp: %#v\nerr: %v", resp, err) 23 } 24 25 tokenMountAccessor := resp.Data["token/"].(map[string]interface{})["accessor"].(string) 26 27 // Create an entity and attach an alias to it 28 resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{ 29 Path: "entity-alias", 30 Operation: logical.UpdateOperation, 31 Data: map[string]interface{}{ 32 "mount_accessor": tokenMountAccessor, 33 "name": "testaliasname", 34 }, 35 }) 36 if err != nil || (resp != nil && resp.IsError()) { 37 t.Fatalf("bad: resp: %#v\nerr: %v", resp, err) 38 } 39 aliasID := resp.Data["id"].(string) 40 41 // Create another entity without an alias 42 resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{ 43 Path: "entity", 44 Operation: logical.UpdateOperation, 45 }) 46 if err != nil || (resp != nil && resp.IsError()) { 47 t.Fatalf("bad: resp: %#v\nerr: %v", resp, err) 48 } 49 entityID2 := resp.Data["id"].(string) 50 51 // Set the second entity ID as the canonical ID for the previous alias, 52 // initiating an alias transfer 53 resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{ 54 Path: "entity-alias/id/" + aliasID, 55 Operation: logical.UpdateOperation, 56 Data: map[string]interface{}{ 57 "canonical_id": entityID2, 58 }, 59 }) 60 if err != nil || (resp != nil && resp.IsError()) { 61 t.Fatalf("bad: resp: %#v\nerr: %v", resp, err) 62 } 63 64 // Read the new entity 65 resp, err = c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{ 66 Path: "entity/id/" + entityID2, 67 Operation: logical.ReadOperation, 68 }) 69 if err != nil || (resp != nil && resp.IsError()) { 70 t.Fatalf("bad: resp: %#v\nerr: %v", resp, err) 71 } 72 73 // Ensure that there is only one alias 74 aliases := resp.Data["aliases"].([]interface{}) 75 if len(aliases) != 1 { 76 t.Fatalf("bad: length of aliases; expected: %d, actual: %d", 1, len(aliases)) 77 } 78 79 // Ensure that no merging activity has taken place 80 if len(aliases[0].(map[string]interface{})["merged_from_canonical_ids"].([]string)) != 0 { 81 t.Fatalf("expected no merging to take place") 82 } 83} 84 85func TestIdentityStore_CaseInsensitiveEntityAliasName(t *testing.T) { 86 ctx := namespace.RootContext(nil) 87 i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 88 89 // Create an entity 90 resp, err := i.HandleRequest(ctx, &logical.Request{ 91 Path: "entity", 92 Operation: logical.UpdateOperation, 93 }) 94 if err != nil || (resp != nil && resp.IsError()) { 95 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 96 } 97 entityID := resp.Data["id"].(string) 98 99 testAliasName := "testAliasName" 100 // Create a case sensitive alias name 101 resp, err = i.HandleRequest(ctx, &logical.Request{ 102 Path: "entity-alias", 103 Operation: logical.UpdateOperation, 104 Data: map[string]interface{}{ 105 "mount_accessor": accessor, 106 "canonical_id": entityID, 107 "name": testAliasName, 108 }, 109 }) 110 if err != nil || (resp != nil && resp.IsError()) { 111 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 112 } 113 aliasID := resp.Data["id"].(string) 114 115 // Ensure that reading the alias returns case sensitive alias name 116 resp, err = i.HandleRequest(ctx, &logical.Request{ 117 Path: "entity-alias/id/" + aliasID, 118 Operation: logical.ReadOperation, 119 }) 120 if err != nil || (resp != nil && resp.IsError()) { 121 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 122 } 123 aliasName := resp.Data["name"].(string) 124 if aliasName != testAliasName { 125 t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName) 126 } 127 128 // Overwrite the alias using lower cased alias name. This shouldn't error. 129 resp, err = i.HandleRequest(ctx, &logical.Request{ 130 Path: "entity-alias/id/" + aliasID, 131 Operation: logical.UpdateOperation, 132 Data: map[string]interface{}{ 133 "mount_accessor": accessor, 134 "canonical_id": entityID, 135 "name": strings.ToLower(testAliasName), 136 }, 137 }) 138 if err != nil || (resp != nil && resp.IsError()) { 139 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 140 } 141 142 // Ensure that reading the alias returns lower cased alias name 143 resp, err = i.HandleRequest(ctx, &logical.Request{ 144 Path: "entity-alias/id/" + aliasID, 145 Operation: logical.ReadOperation, 146 }) 147 if err != nil || (resp != nil && resp.IsError()) { 148 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 149 } 150 aliasName = resp.Data["name"].(string) 151 if aliasName != strings.ToLower(testAliasName) { 152 t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName) 153 } 154 155 // Ensure that there is one entity alias 156 resp, err = i.HandleRequest(ctx, &logical.Request{ 157 Path: "entity-alias/id", 158 Operation: logical.ListOperation, 159 }) 160 if err != nil || (resp != nil && resp.IsError()) { 161 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 162 } 163 if len(resp.Data["keys"].([]string)) != 1 { 164 t.Fatalf("bad length of entity aliases; expected: 1, actual: %d", len(resp.Data["keys"].([]string))) 165 } 166} 167 168// This test is required because MemDB does not take care of ensuring 169// uniqueness of indexes that are marked unique. 170func TestIdentityStore_AliasSameAliasNames(t *testing.T) { 171 var err error 172 var resp *logical.Response 173 174 ctx := namespace.RootContext(nil) 175 is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 176 177 aliasData := map[string]interface{}{ 178 "name": "testaliasname", 179 "mount_accessor": githubAccessor, 180 } 181 182 aliasReq := &logical.Request{ 183 Operation: logical.UpdateOperation, 184 Path: "entity-alias", 185 Data: aliasData, 186 } 187 188 // Register an alias 189 resp, err = is.HandleRequest(ctx, aliasReq) 190 if err != nil || (resp != nil && resp.IsError()) { 191 t.Fatalf("err:%v resp:%#v", err, resp) 192 } 193 194 // Register another alias with same name 195 resp, err = is.HandleRequest(ctx, aliasReq) 196 if err != nil { 197 t.Fatal(err) 198 } 199 if resp != nil { 200 t.Fatalf("expected no response since this modification should be idempotent") 201 } 202} 203 204func TestIdentityStore_MemDBAliasIndexes(t *testing.T) { 205 var err error 206 207 ctx := namespace.RootContext(nil) 208 is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 209 if is == nil { 210 t.Fatal("failed to create test identity store") 211 } 212 213 validateMountResp := is.core.router.validateMountByAccessor(githubAccessor) 214 if validateMountResp == nil { 215 t.Fatal("failed to validate github auth mount") 216 } 217 218 entity := &identity.Entity{ 219 ID: "testentityid", 220 Name: "testentityname", 221 } 222 223 entity.BucketKey = is.entityPacker.BucketKey(entity.ID) 224 225 txn := is.db.Txn(true) 226 defer txn.Abort() 227 err = is.MemDBUpsertEntityInTxn(txn, entity) 228 if err != nil { 229 t.Fatal(err) 230 } 231 txn.Commit() 232 233 alias := &identity.Alias{ 234 CanonicalID: entity.ID, 235 ID: "testaliasid", 236 MountAccessor: githubAccessor, 237 MountType: validateMountResp.MountType, 238 Name: "testaliasname", 239 Metadata: map[string]string{ 240 "testkey1": "testmetadatavalue1", 241 "testkey2": "testmetadatavalue2", 242 }, 243 } 244 245 txn = is.db.Txn(true) 246 defer txn.Abort() 247 err = is.MemDBUpsertAliasInTxn(txn, alias, false) 248 if err != nil { 249 t.Fatal(err) 250 } 251 txn.Commit() 252 253 aliasFetched, err := is.MemDBAliasByID("testaliasid", false, false) 254 if err != nil { 255 t.Fatal(err) 256 } 257 258 if !reflect.DeepEqual(alias, aliasFetched) { 259 t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched) 260 } 261 262 aliasFetched, err = is.MemDBAliasByFactors(validateMountResp.MountAccessor, "testaliasname", false, false) 263 if err != nil { 264 t.Fatal(err) 265 } 266 267 if !reflect.DeepEqual(alias, aliasFetched) { 268 t.Fatalf("bad: mismatched aliases; expected: %#v\n actual: %#v\n", alias, aliasFetched) 269 } 270 271 alias2 := &identity.Alias{ 272 CanonicalID: entity.ID, 273 ID: "testaliasid2", 274 MountAccessor: validateMountResp.MountAccessor, 275 MountType: validateMountResp.MountType, 276 Name: "testaliasname2", 277 Metadata: map[string]string{ 278 "testkey1": "testmetadatavalue1", 279 "testkey3": "testmetadatavalue3", 280 }, 281 } 282 283 txn = is.db.Txn(true) 284 defer txn.Abort() 285 err = is.MemDBUpsertAliasInTxn(txn, alias2, false) 286 if err != nil { 287 t.Fatal(err) 288 } 289 err = is.MemDBDeleteAliasByIDInTxn(txn, "testaliasid", false) 290 if err != nil { 291 t.Fatal(err) 292 } 293 txn.Commit() 294 295 aliasFetched, err = is.MemDBAliasByID("testaliasid", false, false) 296 if err != nil { 297 t.Fatal(err) 298 } 299 300 if aliasFetched != nil { 301 t.Fatalf("expected a nil alias") 302 } 303} 304 305func TestIdentityStore_AliasRegister(t *testing.T) { 306 var err error 307 var resp *logical.Response 308 309 ctx := namespace.RootContext(nil) 310 is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 311 312 if is == nil { 313 t.Fatal("failed to create test alias store") 314 } 315 316 aliasData := map[string]interface{}{ 317 "name": "testaliasname", 318 "mount_accessor": githubAccessor, 319 "metadata": []string{"organization=hashicorp", "team=vault"}, 320 } 321 322 aliasReq := &logical.Request{ 323 Operation: logical.UpdateOperation, 324 Path: "entity-alias", 325 Data: aliasData, 326 } 327 328 // Register the alias 329 resp, err = is.HandleRequest(ctx, aliasReq) 330 if err != nil || (resp != nil && resp.IsError()) { 331 t.Fatalf("err:%v resp:%#v", err, resp) 332 } 333 334 idRaw, ok := resp.Data["id"] 335 if !ok { 336 t.Fatalf("alias id not present in alias register response") 337 } 338 339 id := idRaw.(string) 340 if id == "" { 341 t.Fatalf("invalid alias id in alias register response") 342 } 343 344 entityIDRaw, ok := resp.Data["canonical_id"] 345 if !ok { 346 t.Fatalf("entity id not present in alias register response") 347 } 348 349 entityID := entityIDRaw.(string) 350 if entityID == "" { 351 t.Fatalf("invalid entity id in alias register response") 352 } 353} 354 355func TestIdentityStore_AliasUpdate(t *testing.T) { 356 var err error 357 var resp *logical.Response 358 ctx := namespace.RootContext(nil) 359 is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 360 361 aliasData := map[string]interface{}{ 362 "name": "testaliasname", 363 "mount_accessor": githubAccessor, 364 } 365 366 aliasReq := &logical.Request{ 367 Operation: logical.UpdateOperation, 368 Path: "entity-alias", 369 Data: aliasData, 370 } 371 372 // This will create an alias and a corresponding entity 373 resp, err = is.HandleRequest(ctx, aliasReq) 374 if err != nil || (resp != nil && resp.IsError()) { 375 t.Fatalf("err:%v resp:%#v", err, resp) 376 } 377 aliasID := resp.Data["id"].(string) 378 379 updateData := map[string]interface{}{ 380 "name": "updatedaliasname", 381 "mount_accessor": githubAccessor, 382 } 383 384 aliasReq.Data = updateData 385 aliasReq.Path = "entity-alias/id/" + aliasID 386 resp, err = is.HandleRequest(ctx, aliasReq) 387 if err != nil || (resp != nil && resp.IsError()) { 388 t.Fatalf("err:%v resp:%#v", err, resp) 389 } 390 391 aliasReq.Operation = logical.ReadOperation 392 resp, err = is.HandleRequest(ctx, aliasReq) 393 if err != nil || (resp != nil && resp.IsError()) { 394 t.Fatalf("err:%v resp:%#v", err, resp) 395 } 396 397 if resp.Data["name"] != "updatedaliasname" { 398 t.Fatalf("failed to update alias information; \n response data: %#v\n", resp.Data) 399 } 400} 401 402func TestIdentityStore_AliasUpdate_ByID(t *testing.T) { 403 var err error 404 var resp *logical.Response 405 ctx := namespace.RootContext(nil) 406 is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 407 408 updateData := map[string]interface{}{ 409 "name": "updatedaliasname", 410 "mount_accessor": githubAccessor, 411 } 412 413 updateReq := &logical.Request{ 414 Operation: logical.UpdateOperation, 415 Path: "entity-alias/id/invalidaliasid", 416 Data: updateData, 417 } 418 419 // Try to update an non-existent alias 420 resp, err = is.HandleRequest(ctx, updateReq) 421 if err != nil { 422 t.Fatal(err) 423 } 424 if resp == nil || !resp.IsError() { 425 t.Fatalf("expected an error due to invalid alias id") 426 } 427 428 registerData := map[string]interface{}{ 429 "name": "testaliasname", 430 "mount_accessor": githubAccessor, 431 } 432 433 registerReq := &logical.Request{ 434 Operation: logical.UpdateOperation, 435 Path: "entity-alias", 436 Data: registerData, 437 } 438 439 resp, err = is.HandleRequest(ctx, registerReq) 440 if err != nil || (resp != nil && resp.IsError()) { 441 t.Fatalf("err:%v resp:%#v", err, resp) 442 } 443 444 idRaw, ok := resp.Data["id"] 445 if !ok { 446 t.Fatalf("alias id not present in response") 447 } 448 id := idRaw.(string) 449 if id == "" { 450 t.Fatalf("invalid alias id") 451 } 452 453 updateReq.Path = "entity-alias/id/" + id 454 resp, err = is.HandleRequest(ctx, updateReq) 455 if err != nil || (resp != nil && resp.IsError()) { 456 t.Fatalf("err:%v resp:%#v", err, resp) 457 } 458 459 readReq := &logical.Request{ 460 Operation: logical.ReadOperation, 461 Path: updateReq.Path, 462 } 463 resp, err = is.HandleRequest(ctx, readReq) 464 if err != nil || (resp != nil && resp.IsError()) { 465 t.Fatalf("err:%v resp:%#v", err, resp) 466 } 467 468 if resp.Data["name"] != "updatedaliasname" { 469 t.Fatalf("failed to update alias information; \n response data: %#v\n", resp.Data) 470 } 471 472 delete(registerReq.Data, "name") 473 474 resp, err = is.HandleRequest(ctx, registerReq) 475 if err != nil { 476 t.Fatal(err) 477 } 478 if resp == nil || !resp.IsError() { 479 t.Fatalf("expected error due to missing alias name") 480 } 481 482 registerReq.Data["name"] = "testaliasname" 483 delete(registerReq.Data, "mount_accessor") 484 485 resp, err = is.HandleRequest(ctx, registerReq) 486 if err != nil { 487 t.Fatal(err) 488 } 489 if resp == nil || !resp.IsError() { 490 t.Fatalf("expected error due to missing mount accessor") 491 } 492} 493 494func TestIdentityStore_AliasReadDelete(t *testing.T) { 495 var err error 496 var resp *logical.Response 497 498 ctx := namespace.RootContext(nil) 499 is, githubAccessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 500 501 registerData := map[string]interface{}{ 502 "name": "testaliasname", 503 "mount_accessor": githubAccessor, 504 "metadata": []string{"organization=hashicorp", "team=vault"}, 505 } 506 507 registerReq := &logical.Request{ 508 Operation: logical.UpdateOperation, 509 Path: "entity-alias", 510 Data: registerData, 511 } 512 513 resp, err = is.HandleRequest(ctx, registerReq) 514 if err != nil || (resp != nil && resp.IsError()) { 515 t.Fatalf("err:%v resp:%#v", err, resp) 516 } 517 518 idRaw, ok := resp.Data["id"] 519 if !ok { 520 t.Fatalf("alias id not present in response") 521 } 522 id := idRaw.(string) 523 if id == "" { 524 t.Fatalf("invalid alias id") 525 } 526 527 // Read it back using alias id 528 aliasReq := &logical.Request{ 529 Operation: logical.ReadOperation, 530 Path: "entity-alias/id/" + id, 531 } 532 resp, err = is.HandleRequest(ctx, aliasReq) 533 if err != nil || (resp != nil && resp.IsError()) { 534 t.Fatalf("err:%v resp:%#v", err, resp) 535 } 536 537 if resp.Data["id"].(string) == "" || 538 resp.Data["canonical_id"].(string) == "" || 539 resp.Data["name"].(string) != registerData["name"] || 540 resp.Data["mount_type"].(string) != "github" { 541 t.Fatalf("bad: alias read response; \nexpected: %#v \nactual: %#v\n", registerData, resp.Data) 542 } 543 544 aliasReq.Operation = logical.DeleteOperation 545 resp, err = is.HandleRequest(ctx, aliasReq) 546 if err != nil || (resp != nil && resp.IsError()) { 547 t.Fatalf("err:%v resp:%#v", err, resp) 548 } 549 550 aliasReq.Operation = logical.ReadOperation 551 resp, err = is.HandleRequest(ctx, aliasReq) 552 if err != nil || (resp != nil && resp.IsError()) { 553 t.Fatalf("err:%v resp:%#v", err, resp) 554 } 555 if resp != nil { 556 t.Fatalf("bad: alias read response; expected: nil, actual: %#v\n", resp) 557 } 558} 559