open-vault/builtin/credential/aws/path_role_test.go
Joel Thompson 3e2006eb13 Allow non-prefix-matched IAM role and instance profile ARNs in AWS auth backend (#4071)
* Update aws auth docs with new semantics

Moving away from implicitly globbed bound_iam_role_arn and
bound_iam_instance_profile_arn variables to make them explicit

* Refactor tests to reduce duplication

auth/aws EC2 login tests had the same flow duplicated a few times, so
refactoring to reduce duplication

* Add tests for aws auth explicit wildcard constraints

* Remove implicit prefix matching from AWS auth backend

In the aws auth backend, bound_iam_role_arn and
bound_iam_instance_profile_arn were ALWAYS prefix matched, and there was
no way to opt out of this implicit prefix matching. This now makes the
implicit prefix matching an explicit opt-in feature by requiring users
to specify a * at the end of an ARN if they want the prefix matching.
2018-03-17 21:24:49 -04:00

754 lines
22 KiB
Go

package awsauth
import (
"context"
"reflect"
"strings"
"testing"
"time"
"github.com/hashicorp/vault/helper/policyutil"
"github.com/hashicorp/vault/helper/strutil"
"github.com/hashicorp/vault/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": time.Duration(600),
"max_ttl": time.Duration(1200),
"policies": []string{"testpolicy1", "testpolicy2"},
"disallow_reauthentication": false,
"period": time.Duration(60),
}
if !reflect.DeepEqual(expected, resp.Data) {
t.Fatalf("bad: role data: expected: %#v\n actual: %#v", expected, resp.Data)
}
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)
}
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")
}
}
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 int64(resp.Data["ttl"].(time.Duration)) != 10 {
t.Fatalf("bad: period; expected: 10, actual: %d", resp.Data["ttl"])
}
if int64(resp.Data["max_ttl"].(time.Duration)) != 20 {
t.Fatalf("bad: period; expected: 20, actual: %d", resp.Data["max_ttl"])
}
if int64(resp.Data["period"].(time.Duration)) != 30 {
t.Fatalf("bad: period; expected: 30, actual: %d", resp.Data["period"])
}
}
func TestRoleEntryUpgradeV1(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.upgradeRoleEntry(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 !reflect.DeepEqual(*roleEntryToUpgrade, *expected) {
t.Fatalf("bad: expected upgraded role of %#v, got %#v instead", expected, roleEntryToUpgrade)
}
}
func resolveArnToFakeUniqueId(ctx context.Context, s logical.Storage, arn string) (string, error) {
return "FakeUniqueId1", nil
}