52581cd472
Adds debug and warn logging around AWS credential chain generation, specifically to help users debugging auto-unseal problems on AWS, by logging which role is being used in the case of a webidentity token. Adds a deferred call to flush the log output as well, to ensure logs are output in the event of an initialization failure.
1082 lines
31 KiB
Go
1082 lines
31 KiB
Go
package awsauth
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-test/deep"
|
|
"github.com/hashicorp/go-hclog"
|
|
vlttesting "github.com/hashicorp/vault/helper/testhelpers/logical"
|
|
"github.com/hashicorp/vault/sdk/helper/awsutil"
|
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
|
"github.com/hashicorp/vault/sdk/helper/policyutil"
|
|
"github.com/hashicorp/vault/sdk/helper/strutil"
|
|
"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: period; expected: 10, actual: %d", resp.Data["ttl"])
|
|
}
|
|
if resp.Data["max_ttl"].(int64) != 20 {
|
|
t.Fatalf("bad: period; 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
|
|
}
|