| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package awsauth |
| |
| import ( |
| "context" |
| "os" |
| "reflect" |
| "strings" |
| "testing" |
| |
| "github.com/go-test/deep" |
| "github.com/hashicorp/go-hclog" |
| "github.com/hashicorp/go-secure-stdlib/awsutil" |
| "github.com/hashicorp/go-secure-stdlib/strutil" |
| vlttesting "github.com/hashicorp/vault/helper/testhelpers/logical" |
| "github.com/hashicorp/vault/sdk/helper/logging" |
| "github.com/hashicorp/vault/sdk/helper/policyutil" |
| "github.com/hashicorp/vault/sdk/logical" |
| ) |
| |
| func TestBackend_pathRoleEc2(t *testing.T) { |
| config := logical.TestBackendConfig() |
| storage := &logical.InmemStorage{} |
| config.StorageView = storage |
| |
| b, err := Backend(config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| err = b.Setup(context.Background(), config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| data := map[string]interface{}{ |
| "auth_type": "ec2", |
| "policies": "p,q,r,s", |
| "max_ttl": "2h", |
| "bound_ami_id": "ami-abcd123", |
| } |
| resp, err := b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.CreateOperation, |
| Path: "role/ami-abcd123", |
| Data: data, |
| Storage: storage, |
| }) |
| if resp != nil && resp.IsError() { |
| t.Fatalf("failed to create role") |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.ReadOperation, |
| Path: "role/ami-abcd123", |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp == nil || resp.IsError() { |
| t.Fatal("failed to read the role entry") |
| } |
| if !policyutil.EquivalentPolicies(strings.Split(data["policies"].(string), ","), resp.Data["policies"].([]string)) { |
| t.Fatalf("bad: policies: expected: %#v\ngot: %#v\n", data, resp.Data) |
| } |
| |
| data["allow_instance_migration"] = true |
| data["disallow_reauthentication"] = true |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "role/ami-abcd123", |
| Data: data, |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp == nil || !resp.IsError() { |
| t.Fatalf("expected failure to create role with both allow_instance_migration true and disallow_reauthentication true") |
| } |
| data["disallow_reauthentication"] = false |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "role/ami-abcd123", |
| Data: data, |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp != nil && resp.IsError() { |
| t.Fatalf("failure to update role: %v", resp.Data["error"]) |
| } |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.ReadOperation, |
| Path: "role/ami-abcd123", |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !resp.Data["allow_instance_migration"].(bool) { |
| t.Fatal("bad: expected allow_instance_migration:true got:false\n") |
| } |
| |
| if resp.Data["disallow_reauthentication"].(bool) { |
| t.Fatal("bad: expected disallow_reauthentication: false got:true\n") |
| } |
| |
| // add another entry, to test listing of role entries |
| data["bound_ami_id"] = "ami-abcd456" |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.CreateOperation, |
| Path: "role/ami-abcd456", |
| Data: data, |
| Storage: storage, |
| }) |
| if resp != nil && resp.IsError() { |
| t.Fatalf("failed to create role: %s", resp.Data["error"]) |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| data["bound_iam_principal_arn"] = "" |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Path: "role/ami-abcd456", |
| Data: data, |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp != nil && resp.IsError() { |
| t.Fatalf("failed to update role with empty bound_iam_principal_arn: %s", resp.Data["error"]) |
| } |
| |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.ListOperation, |
| Path: "roles", |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp == nil || resp.Data == nil || resp.IsError() { |
| t.Fatalf("failed to list the role entries") |
| } |
| keys := resp.Data["keys"].([]string) |
| if len(keys) != 2 { |
| t.Fatalf("bad: keys: %#v\n", keys) |
| } |
| |
| _, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.DeleteOperation, |
| Path: "role/ami-abcd123", |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.ReadOperation, |
| Path: "role/ami-abcd123", |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp != nil { |
| t.Fatalf("bad: response: expected:nil actual:%#v\n", resp) |
| } |
| } |
| |
| func Test_enableIamIDResolution(t *testing.T) { |
| config := logical.TestBackendConfig() |
| storage := &logical.InmemStorage{} |
| config.StorageView = storage |
| |
| b, err := Backend(config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| err = b.Setup(context.Background(), config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| roleName := "upgradable_role" |
| |
| b.resolveArnToUniqueIDFunc = resolveArnToFakeUniqueId |
| |
| boundIamRoleARNs := []string{"arn:aws:iam::123456789012:role/MyRole", "arn:aws:iam::123456789012:role/path/*"} |
| data := map[string]interface{}{ |
| "auth_type": iamAuthType, |
| "policies": "p,q", |
| "bound_iam_principal_arn": boundIamRoleARNs, |
| "resolve_aws_unique_ids": false, |
| } |
| |
| submitRequest := func(roleName string, op logical.Operation) (*logical.Response, error) { |
| return b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: op, |
| Path: "role/" + roleName, |
| Data: data, |
| Storage: storage, |
| }) |
| } |
| |
| resp, err := submitRequest(roleName, logical.CreateOperation) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp != nil && resp.IsError() { |
| t.Fatalf("failed to create role: %#v", resp) |
| } |
| |
| resp, err = submitRequest(roleName, logical.ReadOperation) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp == nil || resp.IsError() { |
| t.Fatalf("failed to read role: resp:%#v,\nerr:%#v", resp, err) |
| } |
| if resp.Data["bound_iam_principal_id"] != nil && len(resp.Data["bound_iam_principal_id"].([]string)) > 0 { |
| t.Fatalf("expected to get no unique ID in role, but got %q", resp.Data["bound_iam_principal_id"]) |
| } |
| |
| data = map[string]interface{}{ |
| "resolve_aws_unique_ids": true, |
| } |
| resp, err = submitRequest(roleName, logical.UpdateOperation) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp != nil && resp.IsError() { |
| t.Fatalf("unable to upgrade role to resolve internal IDs: resp:%#v", resp) |
| } |
| |
| resp, err = submitRequest(roleName, logical.ReadOperation) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp == nil || resp.IsError() { |
| t.Fatalf("failed to read role: resp:%#v,\nerr:%#v", resp, err) |
| } |
| principalIDs := resp.Data["bound_iam_principal_id"].([]string) |
| if len(principalIDs) != 1 || principalIDs[0] != "FakeUniqueId1" { |
| t.Fatalf("bad: expected upgrade of role resolve principal ID to %q, but got %q instead", "FakeUniqueId1", resp.Data["bound_iam_principal_id"]) |
| } |
| returnedARNs := resp.Data["bound_iam_principal_arn"].([]string) |
| if !strutil.EquivalentSlices(returnedARNs, boundIamRoleARNs) { |
| t.Fatalf("bad: expected to return bound_iam_principal_arn of %q, but got %q instead", boundIamRoleARNs, returnedARNs) |
| } |
| } |
| |
| func TestBackend_pathIam(t *testing.T) { |
| config := logical.TestBackendConfig() |
| storage := &logical.InmemStorage{} |
| config.StorageView = storage |
| |
| b, err := Backend(config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| err = b.Setup(context.Background(), config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // make sure we start with empty roles, which gives us confidence that the read later |
| // actually is the two roles we created |
| resp, err := b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.ListOperation, |
| Path: "roles", |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp == nil || resp.Data == nil || resp.IsError() { |
| t.Fatalf("failed to list role entries") |
| } |
| if resp.Data["keys"] != nil { |
| t.Fatalf("Received roles when expected none") |
| } |
| |
| data := map[string]interface{}{ |
| "auth_type": iamAuthType, |
| "policies": "p,q,r,s", |
| "max_ttl": "2h", |
| "bound_iam_principal_arn": "n:aws:iam::123456789012:user/MyUserName", |
| "resolve_aws_unique_ids": false, |
| } |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.CreateOperation, |
| Path: "role/MyRoleName", |
| Data: data, |
| Storage: storage, |
| }) |
| |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp != nil && resp.IsError() { |
| t.Fatalf("failed to create the role entry; resp: %#v", resp) |
| } |
| |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.ReadOperation, |
| Path: "role/MyRoleName", |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp == nil || resp.IsError() { |
| t.Fatal("failed to read the role entry") |
| } |
| if !policyutil.EquivalentPolicies(strings.Split(data["policies"].(string), ","), resp.Data["policies"].([]string)) { |
| t.Fatalf("bad: policies: expected %#v\ngot: %#v\n", data, resp.Data) |
| } |
| |
| data["inferred_entity_type"] = "invalid" |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.CreateOperation, |
| Path: "role/ShouldNeverExist", |
| Data: data, |
| Storage: storage, |
| }) |
| if resp == nil || !resp.IsError() { |
| t.Fatalf("Created role with invalid inferred_entity_type") |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| data["inferred_entity_type"] = ec2EntityType |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.CreateOperation, |
| Path: "role/ShouldNeverExist", |
| Data: data, |
| Storage: storage, |
| }) |
| if resp == nil || !resp.IsError() { |
| t.Fatalf("Created role without necessary inferred_aws_region") |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| delete(data, "bound_iam_principal_arn") |
| data["inferred_aws_region"] = "us-east-1" |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.CreateOperation, |
| Path: "role/ShouldNeverExist", |
| Data: data, |
| Storage: storage, |
| }) |
| if resp == nil || !resp.IsError() { |
| t.Fatalf("Created role without anything bound") |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // generate a second role, ensure we're able to list both |
| data["bound_ami_id"] = "ami-abcd123" |
| secondRole := &logical.Request{ |
| Operation: logical.CreateOperation, |
| Path: "role/MyOtherRoleName", |
| Data: data, |
| Storage: storage, |
| } |
| resp, err = b.HandleRequest(context.Background(), secondRole) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp != nil && resp.IsError() { |
| t.Fatalf("failed to create additional role: %v", *secondRole) |
| } |
| |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.ListOperation, |
| Path: "roles", |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp == nil || resp.Data == nil || resp.IsError() { |
| t.Fatalf("failed to list role entries") |
| } |
| keys := resp.Data["keys"].([]string) |
| if len(keys) != 2 { |
| t.Fatalf("bad: keys %#v\n", keys) |
| } |
| |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.DeleteOperation, |
| Path: "role/MyOtherRoleName", |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.ReadOperation, |
| Path: "role/MyOtherRoleName", |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp != nil { |
| t.Fatalf("bad: response: expected: nil actual:%3v\n", resp) |
| } |
| } |
| |
| func TestBackend_pathRoleMixedTypes(t *testing.T) { |
| config := logical.TestBackendConfig() |
| storage := &logical.InmemStorage{} |
| config.StorageView = storage |
| |
| b, err := Backend(config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| err = b.Setup(context.Background(), config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| data := map[string]interface{}{ |
| "policies": "p,q,r,s", |
| "bound_ami_id": "ami-abc1234", |
| "auth_type": "ec2,invalid", |
| } |
| |
| submitRequest := func(roleName string, op logical.Operation) (*logical.Response, error) { |
| return b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: op, |
| Path: "role/" + roleName, |
| Data: data, |
| Storage: storage, |
| }) |
| } |
| |
| resp, err := submitRequest("shouldNeverExist", logical.CreateOperation) |
| if resp == nil || !resp.IsError() { |
| t.Fatalf("created role with invalid auth_type; resp: %#v", resp) |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| data["auth_type"] = "ec2,,iam" |
| resp, err = submitRequest("shouldNeverExist", logical.CreateOperation) |
| if resp == nil || !resp.IsError() { |
| t.Fatalf("created role mixed auth types") |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| data["auth_type"] = ec2AuthType |
| resp, err = submitRequest("ec2_to_iam", logical.CreateOperation) |
| if resp != nil && resp.IsError() { |
| t.Fatalf("failed to create valid role; resp: %#v", resp) |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| data["auth_type"] = iamAuthType |
| delete(data, "bound_ami_id") |
| boundIamPrincipalARNs := []string{"arn:aws:iam::123456789012:role/MyRole", "arn:aws:iam::123456789012:role/path/*"} |
| data["bound_iam_principal_arn"] = boundIamPrincipalARNs |
| resp, err = submitRequest("ec2_to_iam", logical.UpdateOperation) |
| if resp == nil || !resp.IsError() { |
| t.Fatalf("changed auth type on the role") |
| } |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| data["inferred_entity_type"] = ec2EntityType |
| data["inferred_aws_region"] = "us-east-1" |
| data["resolve_aws_unique_ids"] = false |
| resp, err = submitRequest("multipleTypesInferred", logical.CreateOperation) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp.IsError() { |
| t.Fatalf("didn't allow creation of roles with only inferred bindings") |
| } |
| |
| b.resolveArnToUniqueIDFunc = resolveArnToFakeUniqueId |
| data["resolve_aws_unique_ids"] = true |
| resp, err = submitRequest("withInternalIdResolution", logical.CreateOperation) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp.IsError() { |
| t.Fatalf("didn't allow creation of role resolving unique IDs") |
| } |
| resp, err = submitRequest("withInternalIdResolution", logical.ReadOperation) |
| if err != nil { |
| t.Fatal(err) |
| } |
| principalIDs := resp.Data["bound_iam_principal_id"].([]string) |
| if len(principalIDs) != 1 || principalIDs[0] != "FakeUniqueId1" { |
| t.Fatalf("expected fake unique ID of FakeUniqueId1, got %q", resp.Data["bound_iam_principal_id"]) |
| } |
| returnedARNs := resp.Data["bound_iam_principal_arn"].([]string) |
| if !strutil.EquivalentSlices(returnedARNs, boundIamPrincipalARNs) { |
| t.Fatalf("bad: expected to return bound_iam_principal_arn of %q, but got %q instead", boundIamPrincipalARNs, returnedARNs) |
| } |
| data["resolve_aws_unique_ids"] = false |
| resp, err = submitRequest("withInternalIdResolution", logical.UpdateOperation) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !resp.IsError() { |
| t.Fatalf("allowed changing resolve_aws_unique_ids from true to false") |
| } |
| } |
| |
| func TestAwsEc2_RoleCrud(t *testing.T) { |
| var err error |
| var resp *logical.Response |
| config := logical.TestBackendConfig() |
| storage := &logical.InmemStorage{} |
| config.StorageView = storage |
| |
| b, err := Backend(config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| err = b.Setup(context.Background(), config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| role1Data := map[string]interface{}{ |
| "auth_type": "ec2", |
| "bound_vpc_id": "testvpcid", |
| "allow_instance_migration": true, |
| "policies": "testpolicy1,testpolicy2", |
| } |
| roleReq := &logical.Request{ |
| Operation: logical.UpdateOperation, |
| Storage: storage, |
| Path: "role/role1", |
| Data: role1Data, |
| } |
| |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| |
| roleData := map[string]interface{}{ |
| "auth_type": "ec2", |
| "bound_ami_id": "testamiid", |
| "bound_account_id": "testaccountid", |
| "bound_region": "testregion", |
| "bound_iam_role_arn": "arn:aws:iam::123456789012:role/MyRole", |
| "bound_iam_instance_profile_arn": "arn:aws:iam::123456789012:instance-profile/MyInstancePro*", |
| "bound_subnet_id": "testsubnetid", |
| "bound_vpc_id": "testvpcid", |
| "bound_ec2_instance_id": "i-12345678901234567,i-76543210987654321", |
| "role_tag": "testtag", |
| "resolve_aws_unique_ids": false, |
| "allow_instance_migration": true, |
| "ttl": "10m", |
| "max_ttl": "20m", |
| "policies": "testpolicy1,testpolicy2", |
| "disallow_reauthentication": false, |
| "hmac_key": "testhmackey", |
| "period": "1m", |
| } |
| |
| roleReq.Path = "role/testrole" |
| roleReq.Data = roleData |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| |
| roleReq.Operation = logical.ReadOperation |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| |
| expected := map[string]interface{}{ |
| "auth_type": ec2AuthType, |
| "bound_ami_id": []string{"testamiid"}, |
| "bound_account_id": []string{"testaccountid"}, |
| "bound_region": []string{"testregion"}, |
| "bound_ec2_instance_id": []string{"i-12345678901234567", "i-76543210987654321"}, |
| "bound_iam_principal_arn": []string{}, |
| "bound_iam_principal_id": []string{}, |
| "bound_iam_role_arn": []string{"arn:aws:iam::123456789012:role/MyRole"}, |
| "bound_iam_instance_profile_arn": []string{"arn:aws:iam::123456789012:instance-profile/MyInstancePro*"}, |
| "bound_subnet_id": []string{"testsubnetid"}, |
| "bound_vpc_id": []string{"testvpcid"}, |
| "inferred_entity_type": "", |
| "inferred_aws_region": "", |
| "resolve_aws_unique_ids": false, |
| "role_tag": "testtag", |
| "allow_instance_migration": true, |
| "ttl": int64(600), |
| "token_ttl": int64(600), |
| "max_ttl": int64(1200), |
| "token_max_ttl": int64(1200), |
| "token_explicit_max_ttl": int64(0), |
| "policies": []string{"testpolicy1", "testpolicy2"}, |
| "token_policies": []string{"testpolicy1", "testpolicy2"}, |
| "disallow_reauthentication": false, |
| "period": int64(60), |
| "token_period": int64(60), |
| "token_bound_cidrs": []string{}, |
| "token_no_default_policy": false, |
| "token_num_uses": 0, |
| "token_type": "default", |
| } |
| |
| if resp.Data["role_id"] == nil { |
| t.Fatal("role_id not found in repsonse") |
| } |
| expected["role_id"] = resp.Data["role_id"] |
| if diff := deep.Equal(expected, resp.Data); diff != nil { |
| t.Fatal(diff) |
| } |
| |
| roleData["bound_vpc_id"] = "newvpcid" |
| roleReq.Operation = logical.UpdateOperation |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| |
| roleReq.Operation = logical.ReadOperation |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| expected["bound_vpc_id"] = []string{"newvpcid"} |
| if !reflect.DeepEqual(expected, resp.Data) { |
| t.Fatalf("bad: role data: expected: %#v\n actual: %#v", expected, resp.Data) |
| } |
| |
| // Create a new backend so we have a new cache (thus populating from disk). |
| // Then test reading (reading from disk + lock), writing, reading, |
| // deleting, reading. |
| b, err = Backend(config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| err = b.Setup(context.Background(), config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Read again, make sure things are what we expect |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| if !reflect.DeepEqual(expected, resp.Data) { |
| t.Fatalf("bad: role data: expected: %#v\n actual: %#v", expected, resp.Data) |
| } |
| |
| roleReq.Operation = logical.UpdateOperation |
| roleData["bound_ami_id"] = "testamiid2" |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| |
| roleReq.Operation = logical.ReadOperation |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| |
| expected["bound_ami_id"] = []string{"testamiid2"} |
| if diff := deep.Equal(expected, resp.Data); diff != nil { |
| t.Fatal(diff) |
| } |
| |
| // Delete which should remove from disk and also cache |
| roleReq.Operation = logical.DeleteOperation |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| if resp != nil { |
| t.Fatalf("failed to delete role entry") |
| } |
| |
| // Verify it was deleted, e.g. it isn't found in the role cache |
| roleReq.Operation = logical.ReadOperation |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| if resp != nil { |
| t.Fatal("expected nil") |
| } |
| } |
| |
| func TestAwsEc2_RoleDurationSeconds(t *testing.T) { |
| config := logical.TestBackendConfig() |
| storage := &logical.InmemStorage{} |
| config.StorageView = storage |
| |
| b, err := Backend(config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| err = b.Setup(context.Background(), config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| roleData := map[string]interface{}{ |
| "auth_type": "ec2", |
| "bound_iam_instance_profile_arn": "arn:aws:iam::123456789012:instance-profile/test-profile-name", |
| "resolve_aws_unique_ids": false, |
| "ttl": "10s", |
| "max_ttl": "20s", |
| "period": "30s", |
| } |
| |
| roleReq := &logical.Request{ |
| Operation: logical.CreateOperation, |
| Storage: storage, |
| Path: "role/testrole", |
| Data: roleData, |
| } |
| |
| resp, err := b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| |
| roleReq.Operation = logical.ReadOperation |
| |
| resp, err = b.HandleRequest(context.Background(), roleReq) |
| if err != nil || (resp != nil && resp.IsError()) { |
| t.Fatalf("resp: %#v, err: %v", resp, err) |
| } |
| |
| if resp.Data["ttl"].(int64) != 10 { |
| t.Fatalf("bad: ttl; expected: 10, actual: %d", resp.Data["ttl"]) |
| } |
| if resp.Data["max_ttl"].(int64) != 20 { |
| t.Fatalf("bad: max_ttl; expected: 20, actual: %d", resp.Data["max_ttl"]) |
| } |
| if resp.Data["period"].(int64) != 30 { |
| t.Fatalf("bad: period; expected: 30, actual: %d", resp.Data["period"]) |
| } |
| } |
| |
| func TestRoleEntryUpgradeV(t *testing.T) { |
| config := logical.TestBackendConfig() |
| storage := &logical.InmemStorage{} |
| config.StorageView = storage |
| b, err := Backend(config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| err = b.Setup(context.Background(), config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| roleEntryToUpgrade := &awsRoleEntry{ |
| BoundIamRoleARNs: []string{"arn:aws:iam::123456789012:role/my_role_prefix"}, |
| BoundIamInstanceProfileARNs: []string{"arn:aws:iam::123456789012:instance-profile/my_profile-prefix"}, |
| Version: 1, |
| } |
| expected := &awsRoleEntry{ |
| BoundIamRoleARNs: []string{"arn:aws:iam::123456789012:role/my_role_prefix*"}, |
| BoundIamInstanceProfileARNs: []string{"arn:aws:iam::123456789012:instance-profile/my_profile-prefix*"}, |
| Version: currentRoleStorageVersion, |
| } |
| |
| upgraded, err := b.upgradeRole(context.Background(), storage, roleEntryToUpgrade) |
| if err != nil { |
| t.Fatalf("error upgrading role entry: %#v", err) |
| } |
| if !upgraded { |
| t.Fatalf("expected to upgrade role entry %#v but got no upgrade", roleEntryToUpgrade) |
| } |
| if roleEntryToUpgrade.RoleID == "" { |
| t.Fatal("expected role ID to be populated") |
| } |
| expected.RoleID = roleEntryToUpgrade.RoleID |
| if diff := deep.Equal(*roleEntryToUpgrade, *expected); diff != nil { |
| t.Fatal(diff) |
| } |
| } |
| |
| func TestRoleInitialize(t *testing.T) { |
| config := logical.TestBackendConfig() |
| storage := &logical.InmemStorage{} |
| config.StorageView = storage |
| b, err := Backend(config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| ctx := context.Background() |
| err = b.Setup(ctx, config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // create some role entries, some of which will need to be upgraded |
| type testData struct { |
| name string |
| entry *awsRoleEntry |
| } |
| |
| before := []testData{ |
| { |
| name: "role1", |
| entry: &awsRoleEntry{ |
| BoundIamRoleARNs: []string{"arn:aws:iam::000000000001:role/my_role_prefix"}, |
| BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000001:instance-profile/my_profile-prefix"}, |
| Version: 1, |
| }, |
| }, |
| { |
| name: "role2", |
| entry: &awsRoleEntry{ |
| BoundIamRoleARNs: []string{"arn:aws:iam::000000000002:role/my_role_prefix"}, |
| BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000002:instance-profile/my_profile-prefix"}, |
| Version: 2, |
| }, |
| }, |
| { |
| name: "role3", |
| entry: &awsRoleEntry{ |
| BoundIamRoleARNs: []string{"arn:aws:iam::000000000003:role/my_role_prefix"}, |
| BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000003:instance-profile/my_profile-prefix"}, |
| Version: currentRoleStorageVersion, |
| }, |
| }, |
| } |
| |
| // put the entries in storage |
| for _, role := range before { |
| err = b.setRole(ctx, storage, role.name, role.entry) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| // upgrade all the entries |
| upgraded, err := b.upgrade(ctx, storage) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !upgraded { |
| t.Fatalf("expected upgrade") |
| } |
| |
| // read the entries from storage |
| after := make([]testData, 0) |
| names, err := storage.List(ctx, "role/") |
| if err != nil { |
| t.Fatal(err) |
| } |
| for _, name := range names { |
| entry, err := b.role(ctx, storage, name) |
| if err != nil { |
| t.Fatal(err) |
| } |
| after = append(after, testData{name: name, entry: entry}) |
| } |
| |
| // make sure each entry is at the current version |
| expected := []testData{ |
| { |
| name: "role1", |
| entry: &awsRoleEntry{ |
| BoundIamRoleARNs: []string{"arn:aws:iam::000000000001:role/my_role_prefix"}, |
| BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000001:instance-profile/my_profile-prefix"}, |
| Version: currentRoleStorageVersion, |
| }, |
| }, |
| { |
| name: "role2", |
| entry: &awsRoleEntry{ |
| BoundIamRoleARNs: []string{"arn:aws:iam::000000000002:role/my_role_prefix"}, |
| BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000002:instance-profile/my_profile-prefix"}, |
| Version: currentRoleStorageVersion, |
| }, |
| }, |
| { |
| name: "role3", |
| entry: &awsRoleEntry{ |
| BoundIamRoleARNs: []string{"arn:aws:iam::000000000003:role/my_role_prefix"}, |
| BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000003:instance-profile/my_profile-prefix"}, |
| Version: currentRoleStorageVersion, |
| }, |
| }, |
| } |
| if diff := deep.Equal(expected, after); diff != nil { |
| t.Fatal(diff) |
| } |
| |
| // run it again -- nothing will happen |
| upgraded, err = b.upgrade(ctx, storage) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if upgraded { |
| t.Fatalf("expected no upgrade") |
| } |
| |
| // make sure saved role version is correct |
| entry, err := storage.Get(ctx, "config/version") |
| if err != nil { |
| t.Fatal(err) |
| } |
| var version awsVersion |
| err = entry.DecodeJSON(&version) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if version.Version != currentAwsVersion { |
| t.Fatalf("expected version %d, got %d", currentAwsVersion, version.Version) |
| } |
| |
| // stomp on the saved version |
| version.Version = 0 |
| e2, err := logical.StorageEntryJSON("config/version", version) |
| if err != nil { |
| t.Fatal(err) |
| } |
| err = storage.Put(ctx, e2) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // run it again -- now an upgrade will happen |
| upgraded, err = b.upgrade(ctx, storage) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !upgraded { |
| t.Fatalf("expected upgrade") |
| } |
| } |
| |
| func TestAwsVersion(t *testing.T) { |
| before := awsVersion{ |
| Version: 42, |
| } |
| |
| entry, err := logical.StorageEntryJSON("config/version", &before) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var after awsVersion |
| err = entry.DecodeJSON(&after) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if diff := deep.Equal(before, after); diff != nil { |
| t.Fatal(diff) |
| } |
| } |
| |
| // This test was used to reproduce https://github.com/hashicorp/vault/issues/7418 |
| // and verify its fix. |
| // Please run it at least 3 times to ensure that passing tests are due to actually |
| // passing, rather than the region being randomly chosen tying to the one in the |
| // test through luck. |
| func TestRoleResolutionWithSTSEndpointConfigured(t *testing.T) { |
| if enabled := os.Getenv(vlttesting.TestEnvVar); enabled == "" { |
| t.Skip() |
| } |
| |
| /* ARN of an AWS role that Vault can query during testing. |
| This role should exist in your current AWS account and your credentials |
| should have iam:GetRole permissions to query it. |
| */ |
| assumableRoleArn := os.Getenv("AWS_ASSUMABLE_ROLE_ARN") |
| if assumableRoleArn == "" { |
| t.Skip("skipping because AWS_ASSUMABLE_ROLE_ARN is unset") |
| } |
| |
| // Ensure aws credentials are available locally for testing. |
| logger := logging.NewVaultLogger(hclog.Debug) |
| credsConfig := &awsutil.CredentialsConfig{Logger: logger} |
| credsChain, err := credsConfig.GenerateCredentialChain() |
| if err != nil { |
| t.Fatal(err) |
| } |
| _, err = credsChain.Get() |
| if err != nil { |
| t.SkipNow() |
| } |
| |
| config := logical.TestBackendConfig() |
| storage := &logical.InmemStorage{} |
| config.StorageView = storage |
| |
| b, err := Backend(config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| err = b.Setup(context.Background(), config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // configure the client with an sts endpoint that should be used in creating the role |
| data := map[string]interface{}{ |
| "sts_endpoint": "https://sts.eu-west-1.amazonaws.com", |
| // Note - if you comment this out, you can reproduce the error shown |
| // in the linked GH issue above. This essentially reproduces the problem |
| // we had when we didn't have an sts_region field. |
| "sts_region": "eu-west-1", |
| } |
| resp, err := b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.CreateOperation, |
| Path: "config/client", |
| Data: data, |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp != nil && resp.IsError() { |
| t.Fatalf("failed to create the role entry; resp: %#v", resp) |
| } |
| |
| data = map[string]interface{}{ |
| "auth_type": iamAuthType, |
| "bound_iam_principal_arn": assumableRoleArn, |
| "resolve_aws_unique_ids": true, |
| } |
| resp, err = b.HandleRequest(context.Background(), &logical.Request{ |
| Operation: logical.CreateOperation, |
| Path: "role/MyRoleName", |
| Data: data, |
| Storage: storage, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if resp != nil && resp.IsError() { |
| t.Fatalf("failed to create the role entry; resp: %#v", resp) |
| } |
| } |
| |
| func resolveArnToFakeUniqueId(_ context.Context, _ logical.Storage, _ string) (string, error) { |
| return "FakeUniqueId1", nil |
| } |