1package vault 2 3import ( 4 "strings" 5 "testing" 6 7 credLdap "github.com/hashicorp/vault/builtin/credential/ldap" 8 credUserpass "github.com/hashicorp/vault/builtin/credential/userpass" 9 "github.com/hashicorp/vault/helper/identity" 10 "github.com/hashicorp/vault/helper/namespace" 11 "github.com/hashicorp/vault/sdk/logical" 12 "github.com/kr/pretty" 13) 14 15func TestIdentityStore_CaseInsensitiveGroupAliasName(t *testing.T) { 16 ctx := namespace.RootContext(nil) 17 i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 18 19 // Create a group 20 resp, err := i.HandleRequest(ctx, &logical.Request{ 21 Path: "group", 22 Operation: logical.UpdateOperation, 23 Data: map[string]interface{}{ 24 "type": "external", 25 }, 26 }) 27 if err != nil || (resp != nil && resp.IsError()) { 28 t.Fatalf("bad: err: %v\nresp: %#v", err, resp) 29 } 30 groupID := resp.Data["id"].(string) 31 32 testAliasName := "testAliasName" 33 34 // Create a case sensitive alias name 35 resp, err = i.HandleRequest(ctx, &logical.Request{ 36 Path: "group-alias", 37 Operation: logical.UpdateOperation, 38 Data: map[string]interface{}{ 39 "mount_accessor": accessor, 40 "canonical_id": groupID, 41 "name": testAliasName, 42 }, 43 }) 44 if err != nil || (resp != nil && resp.IsError()) { 45 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 46 } 47 aliasID := resp.Data["id"].(string) 48 49 // Ensure that reading the alias returns case sensitive alias name 50 resp, err = i.HandleRequest(ctx, &logical.Request{ 51 Path: "group-alias/id/" + aliasID, 52 Operation: logical.ReadOperation, 53 }) 54 if err != nil || (resp != nil && resp.IsError()) { 55 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 56 } 57 aliasName := resp.Data["name"].(string) 58 if aliasName != testAliasName { 59 t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName) 60 } 61 62 // Overwrite the alias using lower cased alias name. This shouldn't error. 63 resp, err = i.HandleRequest(ctx, &logical.Request{ 64 Path: "group-alias/id/" + aliasID, 65 Operation: logical.UpdateOperation, 66 Data: map[string]interface{}{ 67 "mount_accessor": accessor, 68 "canonical_id": groupID, 69 "name": strings.ToLower(testAliasName), 70 }, 71 }) 72 if err != nil || (resp != nil && resp.IsError()) { 73 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 74 } 75 76 // Ensure that reading the alias returns lower cased alias name 77 resp, err = i.HandleRequest(ctx, &logical.Request{ 78 Path: "group-alias/id/" + aliasID, 79 Operation: logical.ReadOperation, 80 }) 81 if err != nil || (resp != nil && resp.IsError()) { 82 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 83 } 84 aliasName = resp.Data["name"].(string) 85 if aliasName != strings.ToLower(testAliasName) { 86 t.Fatalf("bad alias name; expected: %q, actual: %q", testAliasName, aliasName) 87 } 88} 89 90func TestIdentityStore_EnsureNoDanglingGroupAlias(t *testing.T) { 91 err := AddTestCredentialBackend("userpass", credUserpass.Factory) 92 if err != nil { 93 t.Fatal(err) 94 } 95 96 err = AddTestCredentialBackend("ldap", credLdap.Factory) 97 if err != nil { 98 t.Fatal(err) 99 } 100 101 c, _, _ := TestCoreUnsealed(t) 102 103 ctx := namespace.RootContext(nil) 104 105 userpassMe := &MountEntry{ 106 Table: credentialTableType, 107 Path: "userpass/", 108 Type: "userpass", 109 Description: "userpass", 110 } 111 err = c.enableCredential(ctx, userpassMe) 112 if err != nil { 113 t.Fatal(err) 114 } 115 116 ldapMe := &MountEntry{ 117 Table: credentialTableType, 118 Path: "ldap/", 119 Type: "ldap", 120 Description: "ldap", 121 } 122 err = c.enableCredential(ctx, ldapMe) 123 if err != nil { 124 t.Fatal(err) 125 } 126 127 // Create a group 128 resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{ 129 Path: "group", 130 Operation: logical.UpdateOperation, 131 Data: map[string]interface{}{ 132 "type": "external", 133 }, 134 }) 135 if err != nil || (resp != nil && resp.IsError()) { 136 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 137 } 138 groupID := resp.Data["id"].(string) 139 140 // Add an alias to the group from the userpass auth method 141 resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{ 142 Path: "group-alias", 143 Operation: logical.UpdateOperation, 144 Data: map[string]interface{}{ 145 "name": "testgroupalias", 146 "mount_accessor": userpassMe.Accessor, 147 "canonical_id": groupID, 148 }, 149 }) 150 if err != nil || (resp != nil && resp.IsError()) { 151 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 152 } 153 userpassGroupAliasID := resp.Data["id"].(string) 154 155 // Ensure that the alias is readable 156 resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{ 157 Path: "group-alias/id/" + userpassGroupAliasID, 158 Operation: logical.ReadOperation, 159 }) 160 if err != nil || (resp != nil && resp.IsError()) { 161 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 162 } 163 if resp == nil || resp.Data["id"].(string) != userpassGroupAliasID { 164 t.Fatalf("failed to read userpass group alias") 165 } 166 167 // Attach a different alias to the same group, overriding the previous one 168 resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{ 169 Path: "group-alias", 170 Operation: logical.UpdateOperation, 171 Data: map[string]interface{}{ 172 "name": "testgroupalias", 173 "mount_accessor": ldapMe.Accessor, 174 "canonical_id": groupID, 175 }, 176 }) 177 if err != nil || (resp != nil && resp.IsError()) { 178 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 179 } 180 ldapGroupAliasID := resp.Data["id"].(string) 181 182 // Ensure that the new alias is readable 183 resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{ 184 Path: "group-alias/id/" + ldapGroupAliasID, 185 Operation: logical.ReadOperation, 186 }) 187 if err != nil || (resp != nil && resp.IsError()) { 188 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 189 } 190 if resp == nil || resp.Data["id"].(string) != ldapGroupAliasID { 191 t.Fatalf("failed to read ldap group alias") 192 } 193 194 // Ensure previous alias is gone 195 resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{ 196 Path: "group-alias/id/" + userpassGroupAliasID, 197 Operation: logical.ReadOperation, 198 }) 199 if err != nil || (resp != nil && resp.IsError()) { 200 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 201 } 202 if resp != nil { 203 t.Fatalf("expected a nil response") 204 } 205} 206 207func TestIdentityStore_GroupAliasDeletionOnGroupDeletion(t *testing.T) { 208 var resp *logical.Response 209 var err error 210 211 ctx := namespace.RootContext(nil) 212 i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 213 214 resp, err = i.HandleRequest(ctx, &logical.Request{ 215 Path: "group", 216 Operation: logical.UpdateOperation, 217 Data: map[string]interface{}{ 218 "type": "external", 219 }, 220 }) 221 if err != nil || (resp != nil && resp.IsError()) { 222 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 223 } 224 groupID := resp.Data["id"].(string) 225 226 resp, err = i.HandleRequest(ctx, &logical.Request{ 227 Path: "group-alias", 228 Operation: logical.UpdateOperation, 229 Data: map[string]interface{}{ 230 "name": "testgroupalias", 231 "mount_accessor": accessor, 232 "canonical_id": groupID, 233 }, 234 }) 235 if err != nil || (resp != nil && resp.IsError()) { 236 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 237 } 238 groupAliasID := resp.Data["id"].(string) 239 240 resp, err = i.HandleRequest(ctx, &logical.Request{ 241 Path: "group/id/" + groupID, 242 Operation: logical.DeleteOperation, 243 }) 244 if err != nil || (resp != nil && resp.IsError()) { 245 t.Fatalf("bad: resp: %#v\nerr: %v", resp, err) 246 } 247 248 resp, err = i.HandleRequest(ctx, &logical.Request{ 249 Path: "group-alias/id/" + groupAliasID, 250 Operation: logical.ReadOperation, 251 }) 252 if err != nil || (resp != nil && resp.IsError()) { 253 t.Fatalf("bad: resp: %#v\nerr: %v", resp, err) 254 } 255 if resp != nil { 256 t.Fatalf("expected a nil response") 257 } 258} 259 260func TestIdentityStore_GroupAliases_CRUD(t *testing.T) { 261 var resp *logical.Response 262 var err error 263 ctx := namespace.RootContext(nil) 264 i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 265 266 groupReq := &logical.Request{ 267 Path: "group", 268 Operation: logical.UpdateOperation, 269 Data: map[string]interface{}{ 270 "type": "external", 271 }, 272 } 273 resp, err = i.HandleRequest(ctx, groupReq) 274 if err != nil || (resp != nil && resp.IsError()) { 275 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 276 } 277 groupID := resp.Data["id"].(string) 278 279 groupAliasReq := &logical.Request{ 280 Path: "group-alias", 281 Operation: logical.UpdateOperation, 282 Data: map[string]interface{}{ 283 "name": "testgroupalias", 284 "mount_accessor": accessor, 285 "canonical_id": groupID, 286 "mount_type": "ldap", 287 }, 288 } 289 resp, err = i.HandleRequest(ctx, groupAliasReq) 290 if err != nil || (resp != nil && resp.IsError()) { 291 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 292 } 293 groupAliasID := resp.Data["id"].(string) 294 295 groupAliasReq.Path = "group-alias/id/" + groupAliasID 296 groupAliasReq.Operation = logical.ReadOperation 297 resp, err = i.HandleRequest(ctx, groupAliasReq) 298 if err != nil || (resp != nil && resp.IsError()) { 299 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 300 } 301 302 if resp.Data["id"].(string) != groupAliasID { 303 t.Fatalf("bad: group alias: %#v\n", resp.Data) 304 } 305 306 resp, err = i.HandleRequest(ctx, &logical.Request{ 307 Path: "group-alias/id/" + groupAliasID, 308 Operation: logical.UpdateOperation, 309 Data: map[string]interface{}{ 310 "name": "testupdatedgroupaliasname", 311 "mount_accessor": accessor, 312 "canonical_id": groupID, 313 "mount_type": "ldap", 314 }, 315 }) 316 if err != nil || (resp != nil && resp.IsError()) { 317 t.Fatalf("bad: err: %v; resp: %#v", err, resp) 318 } 319 if resp.Data["id"].(string) != groupAliasID { 320 t.Fatalf("bad: group alias: %#v\n", resp.Data) 321 } 322 323 groupAliasReq.Operation = logical.DeleteOperation 324 resp, err = i.HandleRequest(ctx, groupAliasReq) 325 if err != nil || (resp != nil && resp.IsError()) { 326 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 327 } 328 329 groupAliasReq.Operation = logical.ReadOperation 330 resp, err = i.HandleRequest(ctx, groupAliasReq) 331 if err != nil || (resp != nil && resp.IsError()) { 332 t.Fatalf("bad: resp: %#v\nerr: %v\n", resp, err) 333 } 334 335 if resp != nil { 336 t.Fatalf("failed to delete group alias") 337 } 338} 339 340func TestIdentityStore_GroupAliases_MemDBIndexes(t *testing.T) { 341 var err error 342 ctx := namespace.RootContext(nil) 343 i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 344 345 group := &identity.Group{ 346 ID: "testgroupid", 347 Name: "testgroupname", 348 Metadata: map[string]string{ 349 "testmetadatakey1": "testmetadatavalue1", 350 "testmetadatakey2": "testmetadatavalue2", 351 }, 352 Alias: &identity.Alias{ 353 ID: "testgroupaliasid", 354 Name: "testalias", 355 MountAccessor: accessor, 356 CanonicalID: "testgroupid", 357 MountType: "ldap", 358 }, 359 ParentGroupIDs: []string{"testparentgroupid1", "testparentgroupid2"}, 360 MemberEntityIDs: []string{"testentityid1", "testentityid2"}, 361 Policies: []string{"testpolicy1", "testpolicy2"}, 362 BucketKey: i.groupPacker.BucketKey("testgroupid"), 363 } 364 365 txn := i.db.Txn(true) 366 defer txn.Abort() 367 err = i.MemDBUpsertAliasInTxn(txn, group.Alias, true) 368 if err != nil { 369 t.Fatal(err) 370 } 371 err = i.MemDBUpsertGroupInTxn(txn, group) 372 if err != nil { 373 t.Fatal(err) 374 } 375 txn.Commit() 376 377 alias, err := i.MemDBAliasByID("testgroupaliasid", false, true) 378 if err != nil { 379 t.Fatal(err) 380 } 381 if alias.ID != "testgroupaliasid" { 382 t.Fatalf("bad: group alias: %#v\n", alias) 383 } 384 385 group, err = i.MemDBGroupByAliasID("testgroupaliasid", false) 386 if err != nil { 387 t.Fatal(err) 388 } 389 if group.ID != "testgroupid" { 390 t.Fatalf("bad: group: %#v\n", group) 391 } 392 393 aliasByFactors, err := i.MemDBAliasByFactors(group.Alias.MountAccessor, group.Alias.Name, false, true) 394 if err != nil { 395 t.Fatal(err) 396 } 397 if aliasByFactors.ID != "testgroupaliasid" { 398 t.Fatalf("bad: group alias: %#v\n", aliasByFactors) 399 } 400} 401 402func TestIdentityStore_GroupAliases_AliasOnInternalGroup(t *testing.T) { 403 var err error 404 var resp *logical.Response 405 406 ctx := namespace.RootContext(nil) 407 i, accessor, _ := testIdentityStoreWithGithubAuth(ctx, t) 408 409 groupReq := &logical.Request{ 410 Path: "group", 411 Operation: logical.UpdateOperation, 412 } 413 resp, err = i.HandleRequest(ctx, groupReq) 414 if err != nil || (resp != nil && resp.IsError()) { 415 t.Fatalf("bad: resp: %#v; err: %v", resp, err) 416 } 417 groupID := resp.Data["id"].(string) 418 419 aliasReq := &logical.Request{ 420 Path: "group-alias", 421 Operation: logical.UpdateOperation, 422 Data: map[string]interface{}{ 423 "name": "testname", 424 "mount_accessor": accessor, 425 "canonical_id": groupID, 426 }, 427 } 428 resp, err = i.HandleRequest(ctx, aliasReq) 429 if err != nil { 430 t.Fatal(err) 431 } 432 if !resp.IsError() { 433 t.Fatalf("expected an error") 434 } 435} 436 437func TestIdentityStore_GroupAliasesUpdate(t *testing.T) { 438 ctx := namespace.RootContext(nil) 439 i, accessor1, c := testIdentityStoreWithGithubAuth(ctx, t) 440 441 ghme2 := &MountEntry{ 442 Table: credentialTableType, 443 Path: "github2/", 444 Type: "github", 445 Description: "github auth", 446 } 447 448 err := c.enableCredential(ctx, ghme2) 449 if err != nil { 450 t.Fatal(err) 451 } 452 accessor2 := ghme2.Accessor 453 454 // Create two groups 455 resp, err := i.HandleRequest(ctx, &logical.Request{ 456 Path: "group", 457 Operation: logical.UpdateOperation, 458 Data: map[string]interface{}{ 459 "type": "external", 460 }, 461 }) 462 if err != nil || (resp != nil && resp.IsError()) { 463 t.Fatalf("bad: err: %v\nresp: %#v", err, resp) 464 } 465 groupID1 := resp.Data["id"].(string) 466 467 resp, err = i.HandleRequest(ctx, &logical.Request{ 468 Path: "group", 469 Operation: logical.UpdateOperation, 470 Data: map[string]interface{}{ 471 "type": "external", 472 }, 473 }) 474 if err != nil || (resp != nil && resp.IsError()) { 475 t.Fatalf("bad: err: %v\nresp: %#v", err, resp) 476 } 477 groupID2 := resp.Data["id"].(string) 478 479 // Create an alias 480 resp, err = i.HandleRequest(ctx, &logical.Request{ 481 Path: "group-alias", 482 Operation: logical.UpdateOperation, 483 Data: map[string]interface{}{ 484 "mount_accessor": accessor1, 485 "canonical_id": groupID1, 486 "name": "testalias", 487 }, 488 }) 489 if err != nil || (resp != nil && resp.IsError()) { 490 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 491 } 492 aliasID := resp.Data["id"].(string) 493 494 // Ensure that reading the alias returns the right group 495 resp, err = i.HandleRequest(ctx, &logical.Request{ 496 Path: "group-alias/id/" + aliasID, 497 Operation: logical.ReadOperation, 498 }) 499 if err != nil || (resp != nil && resp.IsError()) { 500 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 501 } 502 aliasName := resp.Data["name"].(string) 503 if aliasName != "testalias" { 504 t.Fatalf("bad alias name; expected: testalias, actual: %q", aliasName) 505 } 506 mountAccessor := resp.Data["mount_accessor"].(string) 507 if mountAccessor != accessor1 { 508 t.Fatalf("bad mount accessor; expected: %q, actual: %q", accessor1, mountAccessor) 509 } 510 canonicalID := resp.Data["canonical_id"].(string) 511 if canonicalID != groupID1 { 512 t.Fatalf("bad canonical name; expected: %q, actual: %q", groupID1, canonicalID) 513 } 514 // Ensure that reading the group returns the alias 515 resp, err = i.HandleRequest(ctx, &logical.Request{ 516 Path: "group/id/" + groupID1, 517 Operation: logical.ReadOperation, 518 }) 519 if err != nil || (resp != nil && resp.IsError()) { 520 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 521 } 522 aliasName = resp.Data["alias"].(map[string]interface{})["name"].(string) 523 if aliasName != "testalias" { 524 t.Fatalf("bad alias name; expected: testalias, actual: %q", aliasName) 525 } 526 527 // Overwrite the alias 528 resp, err = i.HandleRequest(ctx, &logical.Request{ 529 Path: "group-alias/id/" + aliasID, 530 Operation: logical.UpdateOperation, 531 Data: map[string]interface{}{ 532 "mount_accessor": accessor2, 533 "canonical_id": groupID2, 534 "name": "testalias2", 535 }, 536 }) 537 if err != nil || (resp != nil && resp.IsError()) { 538 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 539 } 540 541 // Ensure that reading the alias returns the right group 542 resp, err = i.HandleRequest(ctx, &logical.Request{ 543 Path: "group-alias/id/" + aliasID, 544 Operation: logical.ReadOperation, 545 }) 546 if err != nil || (resp != nil && resp.IsError()) { 547 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 548 } 549 aliasName = resp.Data["name"].(string) 550 if aliasName != "testalias2" { 551 t.Fatalf("bad alias name; expected: testalias2, actual: %q", aliasName) 552 } 553 mountAccessor = resp.Data["mount_accessor"].(string) 554 if mountAccessor != accessor2 { 555 t.Fatalf("bad mount accessor; expected: %q, actual: %q", accessor2, mountAccessor) 556 } 557 canonicalID = resp.Data["canonical_id"].(string) 558 if canonicalID != groupID2 { 559 t.Fatalf("bad canonical name; expected: %q, actual: %q", groupID2, canonicalID) 560 } 561 // Ensure that reading the group returns the alias 562 resp, err = i.HandleRequest(ctx, &logical.Request{ 563 Path: "group/id/" + groupID2, 564 Operation: logical.ReadOperation, 565 }) 566 if err != nil || (resp != nil && resp.IsError()) { 567 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 568 } 569 aliasName = resp.Data["alias"].(map[string]interface{})["name"].(string) 570 if aliasName != "testalias2" { 571 t.Fatalf("bad alias name; expected: testalias, actual: %q", aliasName) 572 } 573 // Ensure the old group does not 574 resp, err = i.HandleRequest(ctx, &logical.Request{ 575 Path: "group/id/" + groupID1, 576 Operation: logical.ReadOperation, 577 }) 578 if err != nil || (resp != nil && resp.IsError()) { 579 t.Fatalf("bad: err:%v\nresp: %#v", err, resp) 580 } 581 aliasInfo := resp.Data["alias"].(map[string]interface{}) 582 if len(aliasInfo) > 0 { 583 t.Fatalf("still found alias with old group: %s", pretty.Sprint(resp.Data)) 584 } 585} 586