mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 10:37:56 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1888 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1888 lines
		
	
	
		
			56 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) HashiCorp, Inc.
 | |
| // SPDX-License-Identifier: BUSL-1.1
 | |
| 
 | |
| package aws
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/aws/aws-sdk-go-v2/config"
 | |
| 	"github.com/aws/aws-sdk-go/aws"
 | |
| 	"github.com/aws/aws-sdk-go/aws/awserr"
 | |
| 	"github.com/aws/aws-sdk-go/aws/credentials"
 | |
| 	"github.com/aws/aws-sdk-go/aws/request"
 | |
| 	"github.com/aws/aws-sdk-go/aws/session"
 | |
| 	"github.com/aws/aws-sdk-go/service/dynamodb"
 | |
| 	"github.com/aws/aws-sdk-go/service/ec2"
 | |
| 	"github.com/aws/aws-sdk-go/service/iam"
 | |
| 	"github.com/aws/aws-sdk-go/service/iam/iamiface"
 | |
| 	"github.com/aws/aws-sdk-go/service/s3"
 | |
| 	"github.com/aws/aws-sdk-go/service/sts"
 | |
| 	cleanhttp "github.com/hashicorp/go-cleanhttp"
 | |
| 	"github.com/hashicorp/vault/helper/testhelpers"
 | |
| 	logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
 | |
| 	"github.com/hashicorp/vault/sdk/logical"
 | |
| 	"github.com/mitchellh/mapstructure"
 | |
| )
 | |
| 
 | |
| var initSetup sync.Once
 | |
| 
 | |
| // This looks a bit curious. The policy document and the role document act
 | |
| // as a logical intersection of policies. The role allows ec2:Describe*
 | |
| // (among other permissions). This policy allows everything BUT
 | |
| // ec2:DescribeAvailabilityZones. Thus, the logical intersection of the two
 | |
| // is all ec2:Describe* EXCEPT ec2:DescribeAvailabilityZones, and so the
 | |
| // describeAZs call should fail
 | |
| const allowAllButDescribeAzs = `{
 | |
| 	"Version": "2012-10-17",
 | |
| 	"Statement": [
 | |
| 		{
 | |
| 			"Effect": "Allow",
 | |
| 			"NotAction": "ec2:DescribeAvailabilityZones",
 | |
| 			"Resource": "*"
 | |
| 		}
 | |
| 	]
 | |
| }`
 | |
| 
 | |
| type mockIAMClient struct {
 | |
| 	iamiface.IAMAPI
 | |
| }
 | |
| 
 | |
| func (m *mockIAMClient) CreateUserWithContext(_ aws.Context, input *iam.CreateUserInput, _ ...request.Option) (*iam.CreateUserOutput, error) {
 | |
| 	return nil, awserr.New("Throttling", "", nil)
 | |
| }
 | |
| 
 | |
| func getBackend(t *testing.T) logical.Backend {
 | |
| 	cfg := logical.TestBackendConfig()
 | |
| 	cfg.System = &testSystemView{}
 | |
| 	be, _ := Factory(context.Background(), cfg)
 | |
| 	return be
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_basic(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck:       func() { testAccPreCheck(t) },
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWritePolicy(t, "test", testDynamoPolicy),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{listDynamoTablesTest}),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_IamUserWithPermissionsBoundary(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"credential_type":          iamUserCred,
 | |
| 		"policy_arns":              adminAccessPolicyArn,
 | |
| 		"permissions_boundary_arn": iamPolicyArn,
 | |
| 	}
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck:       func() { testAccPreCheck(t) },
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{listIamUsersTest, describeAzsTestUnauthorized}),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_basicSTS(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	awsAccountID, err := getAccountID()
 | |
| 	if err != nil {
 | |
| 		t.Logf("Unable to retrive user via sts:GetCallerIdentity: %#v", err)
 | |
| 		t.Skip("Could not determine AWS account ID from sts:GetCallerIdentity for acceptance tests, skipping")
 | |
| 	}
 | |
| 	roleName := generateUniqueRoleName(t.Name())
 | |
| 	userName := generateUniqueUserName(t.Name())
 | |
| 	accessKey := &awsAccessKey{}
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createUser(t, userName, accessKey)
 | |
| 			createRole(t, roleName, awsAccountID, []string{ec2PolicyArn}, nil)
 | |
| 			// Sleep sometime because AWS is eventually consistent
 | |
| 			// Both the createUser and createRole depend on this
 | |
| 			log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfigWithCreds(t, accessKey),
 | |
| 			testAccStepRotateRoot(accessKey),
 | |
| 			testAccStepWritePolicy(t, "test", testDynamoPolicy),
 | |
| 			testAccStepRead(t, "sts", "test", []credentialTestFunc{listDynamoTablesTest}),
 | |
| 			testAccStepWriteArnPolicyRef(t, "test", ec2PolicyArn),
 | |
| 			testAccStepReadSTSWithArnPolicy(t, "test"),
 | |
| 			testAccStepWriteArnRoleRef(t, "test2", roleName, awsAccountID),
 | |
| 			testAccStepRead(t, "sts", "test2", []credentialTestFunc{describeInstancesTest}),
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			if err := deleteTestRole(roleName); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			return deleteTestUser(accessKey, userName)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestBackend_policyCRUD tests the CRUD operations for a policy.
 | |
| func TestBackend_policyCRUD(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	compacted, err := compactJSON(testDynamoPolicy)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("bad: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: false,
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWritePolicy(t, "test", testDynamoPolicy),
 | |
| 			testAccStepReadPolicy(t, "test", compacted),
 | |
| 			testAccStepDeletePolicy(t, "test"),
 | |
| 			testAccStepReadPolicy(t, "test", ""),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestBackend_throttled(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	config := logical.TestBackendConfig()
 | |
| 	config.StorageView = &logical.InmemStorage{}
 | |
| 
 | |
| 	b := Backend(config)
 | |
| 	if err := b.Setup(context.Background(), config); err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 
 | |
| 	connData := map[string]interface{}{
 | |
| 		"credential_type": "iam_user",
 | |
| 	}
 | |
| 
 | |
| 	confReq := &logical.Request{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "roles/something",
 | |
| 		Storage:   config.StorageView,
 | |
| 		Data:      connData,
 | |
| 	}
 | |
| 
 | |
| 	resp, err := b.HandleRequest(context.Background(), confReq)
 | |
| 	if err != nil || (resp != nil && resp.IsError()) {
 | |
| 		t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
 | |
| 	}
 | |
| 
 | |
| 	// Mock the IAM API call to return a throttled response to the CreateUser API
 | |
| 	// call
 | |
| 	b.iamClient = &mockIAMClient{}
 | |
| 
 | |
| 	credReq := &logical.Request{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "creds/something",
 | |
| 		Storage:   config.StorageView,
 | |
| 	}
 | |
| 
 | |
| 	credResp, err := b.HandleRequest(context.Background(), credReq)
 | |
| 	if err == nil {
 | |
| 		t.Fatalf("failed to trigger expected throttling error condition: resp:%#v", credResp)
 | |
| 	}
 | |
| 	rErr := credResp.Error()
 | |
| 	expected := "Error creating IAM user: Throttling: "
 | |
| 	if rErr.Error() != expected {
 | |
| 		t.Fatalf("error message did not match, expected (%s), got (%s)", expected, rErr.Error())
 | |
| 	}
 | |
| 
 | |
| 	// verify the error we got back is returned with a http.StatusBadGateway
 | |
| 	code, err := logical.RespondErrorCommon(credReq, credResp, err)
 | |
| 	if err == nil {
 | |
| 		t.Fatal("expected error after running req/resp/err through RespondErrorCommon, got nil")
 | |
| 	}
 | |
| 	if code != http.StatusBadGateway {
 | |
| 		t.Fatalf("expected HTTP status 'bad gateway', got: (%d)", code)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccPreCheck(t *testing.T) {
 | |
| 	if !hasAWSCredentials() {
 | |
| 		t.Skip("Skipping because AWS credentials could not be resolved. See https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials for information on how to set up AWS credentials.")
 | |
| 	}
 | |
| 
 | |
| 	initSetup.Do(func() {
 | |
| 		if v := os.Getenv("AWS_DEFAULT_REGION"); v == "" {
 | |
| 			log.Println("[INFO] Test: Using us-west-2 as test region")
 | |
| 			os.Setenv("AWS_DEFAULT_REGION", "us-west-2")
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func hasAWSCredentials() bool {
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	cfg, err := config.LoadDefaultConfig(ctx)
 | |
| 	if err != nil {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	creds, err := cfg.Credentials.Retrieve(ctx)
 | |
| 	if err != nil {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	return creds.HasKeys()
 | |
| }
 | |
| 
 | |
| func getAccountID() (string, error) {
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Region:     aws.String("us-east-1"),
 | |
| 		HTTPClient: cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	svc := sts.New(sess)
 | |
| 
 | |
| 	params := &sts.GetCallerIdentityInput{}
 | |
| 	res, err := svc.GetCallerIdentity(params)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	if res == nil {
 | |
| 		return "", fmt.Errorf("got nil response from GetCallerIdentity")
 | |
| 	}
 | |
| 
 | |
| 	return *res.Account, nil
 | |
| }
 | |
| 
 | |
| func createRole(t *testing.T, roleName, awsAccountID string, policyARNs, extraTrustPolicies []string) {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	trustPolicyStmts := append([]string{
 | |
| 		fmt.Sprintf(`
 | |
| 		{
 | |
| 		  "Effect":"Allow",
 | |
| 		  "Principal": {
 | |
| 			  "AWS": "arn:aws:iam::%s:root"
 | |
| 		  },
 | |
| 		  "Action": [
 | |
| 			  "sts:AssumeRole",
 | |
| 			  "sts:SetSourceIdentity"
 | |
| 		  ]
 | |
| 		}`, awsAccountID),
 | |
| 	},
 | |
| 		extraTrustPolicies...)
 | |
| 
 | |
| 	testRoleAssumePolicy := fmt.Sprintf(`{
 | |
|       "Version": "2012-10-17",
 | |
|       "Statement": [
 | |
| %s
 | |
|       ]
 | |
| }
 | |
| `, strings.Join(trustPolicyStmts, ","))
 | |
| 
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Region:     aws.String("us-east-1"),
 | |
| 		HTTPClient: cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	svc := iam.New(sess)
 | |
| 
 | |
| 	params := &iam.CreateRoleInput{
 | |
| 		AssumeRolePolicyDocument: aws.String(testRoleAssumePolicy),
 | |
| 		RoleName:                 aws.String(roleName),
 | |
| 		Path:                     aws.String("/"),
 | |
| 	}
 | |
| 
 | |
| 	log.Printf("[INFO] AWS CreateRole: %s", roleName)
 | |
| 	output, err := svc.CreateRole(params)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("AWS CreateRole failed: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	for _, policyARN := range policyARNs {
 | |
| 		attachment := &iam.AttachRolePolicyInput{
 | |
| 			PolicyArn: aws.String(policyARN),
 | |
| 			RoleName:  output.Role.RoleName,
 | |
| 		}
 | |
| 		_, err = svc.AttachRolePolicy(attachment)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("AWS AttachRolePolicy failed: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func createUser(t *testing.T, userName string, accessKey *awsAccessKey) {
 | |
| 	// The sequence of user creation actions is carefully chosen to minimize
 | |
| 	// impact of stolen IAM user credentials
 | |
| 	// 1. Create user, without any permissions or credentials. At this point,
 | |
| 	//	  nobody cares if creds compromised because this user can do nothing.
 | |
| 	// 2. Attach the timebomb policy. This grants no access but puts a time limit
 | |
| 	//	  on validity of compromised credentials. If this fails, nobody cares
 | |
| 	//	  because the user has no permissions to do anything anyway
 | |
| 	// 3. Attach the AdminAccess policy. The IAM user still has no credentials to
 | |
| 	//	  do anything
 | |
| 	// 4. Generate API creds to get an actual access key and secret key
 | |
| 	timebombPolicyTemplate := `{
 | |
| 	"Version": "2012-10-17",
 | |
| 	"Statement": [
 | |
| 		{
 | |
| 			"Effect": "Deny",
 | |
| 			"Action": "*",
 | |
| 			"Resource": "*",
 | |
| 			"Condition": {
 | |
| 				"DateGreaterThan": {
 | |
| 					"aws:CurrentTime": "%s"
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	]
 | |
| }
 | |
| `
 | |
| 	validity := time.Duration(2 * time.Hour)
 | |
| 	expiry := time.Now().Add(validity)
 | |
| 	timebombPolicy := fmt.Sprintf(timebombPolicyTemplate, expiry.Format(time.RFC3339))
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Region:     aws.String("us-east-1"),
 | |
| 		HTTPClient: cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	svc := iam.New(sess)
 | |
| 	createUserInput := &iam.CreateUserInput{
 | |
| 		UserName: aws.String(userName),
 | |
| 	}
 | |
| 	log.Printf("[INFO] AWS CreateUser: %s", userName)
 | |
| 	if _, err := svc.CreateUser(createUserInput); err != nil {
 | |
| 		t.Fatalf("AWS CreateUser failed: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	putPolicyInput := &iam.PutUserPolicyInput{
 | |
| 		PolicyDocument: aws.String(timebombPolicy),
 | |
| 		PolicyName:     aws.String("SelfDestructionTimebomb"),
 | |
| 		UserName:       aws.String(userName),
 | |
| 	}
 | |
| 	_, err = svc.PutUserPolicy(putPolicyInput)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("AWS PutUserPolicy failed: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	attachUserPolicyInput := &iam.AttachUserPolicyInput{
 | |
| 		PolicyArn: aws.String("arn:aws:iam::aws:policy/AdministratorAccess"),
 | |
| 		UserName:  aws.String(userName),
 | |
| 	}
 | |
| 	_, err = svc.AttachUserPolicy(attachUserPolicyInput)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("AWS AttachUserPolicy failed, %v", err)
 | |
| 	}
 | |
| 
 | |
| 	createAccessKeyInput := &iam.CreateAccessKeyInput{
 | |
| 		UserName: aws.String(userName),
 | |
| 	}
 | |
| 	createAccessKeyOutput, err := svc.CreateAccessKey(createAccessKeyInput)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("AWS CreateAccessKey failed: %v", err)
 | |
| 	}
 | |
| 	if createAccessKeyOutput == nil {
 | |
| 		t.Fatalf("AWS CreateAccessKey returned nil")
 | |
| 	}
 | |
| 	genAccessKey := createAccessKeyOutput.AccessKey
 | |
| 
 | |
| 	accessKey.AccessKeyID = *genAccessKey.AccessKeyId
 | |
| 	accessKey.SecretAccessKey = *genAccessKey.SecretAccessKey
 | |
| }
 | |
| 
 | |
| // Create an IAM Group and add an inline policy and managed policies if specified
 | |
| func createGroup(t *testing.T, groupName string, inlinePolicy string, managedPolicies []string) {
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Region:     aws.String("us-east-1"),
 | |
| 		HTTPClient: cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	svc := iam.New(sess)
 | |
| 	createGroupInput := &iam.CreateGroupInput{
 | |
| 		GroupName: aws.String(groupName),
 | |
| 	}
 | |
| 	log.Printf("[INFO] AWS CreateGroup: %s", groupName)
 | |
| 	if _, err := svc.CreateGroup(createGroupInput); err != nil {
 | |
| 		t.Fatalf("AWS CreateGroup failed: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	if len(inlinePolicy) > 0 {
 | |
| 		putPolicyInput := &iam.PutGroupPolicyInput{
 | |
| 			PolicyDocument: aws.String(inlinePolicy),
 | |
| 			PolicyName:     aws.String("InlinePolicy"),
 | |
| 			GroupName:      aws.String(groupName),
 | |
| 		}
 | |
| 		_, err = svc.PutGroupPolicy(putPolicyInput)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("AWS PutGroupPolicy failed: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, mp := range managedPolicies {
 | |
| 		attachGroupPolicyInput := &iam.AttachGroupPolicyInput{
 | |
| 			PolicyArn: aws.String(mp),
 | |
| 			GroupName: aws.String(groupName),
 | |
| 		}
 | |
| 		_, err = svc.AttachGroupPolicy(attachGroupPolicyInput)
 | |
| 		if err != nil {
 | |
| 			t.Fatalf("AWS AttachGroupPolicy failed, %v", err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func deleteTestRole(roleName string) error {
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Region:     aws.String("us-east-1"),
 | |
| 		HTTPClient: cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	svc := iam.New(sess)
 | |
| 	listAttachmentsInput := &iam.ListAttachedRolePoliciesInput{
 | |
| 		RoleName: aws.String(roleName),
 | |
| 	}
 | |
| 	detacher := func(result *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool {
 | |
| 		for _, policy := range result.AttachedPolicies {
 | |
| 			detachInput := &iam.DetachRolePolicyInput{
 | |
| 				PolicyArn: policy.PolicyArn,
 | |
| 				RoleName:  aws.String(roleName), // Required
 | |
| 			}
 | |
| 			_, err := svc.DetachRolePolicy(detachInput)
 | |
| 			if err != nil {
 | |
| 				log.Printf("[WARN] AWS DetachRolePolicy failed for policy %s: %v", *policy.PolicyArn, err)
 | |
| 			}
 | |
| 		}
 | |
| 		return true
 | |
| 	}
 | |
| 	if err := svc.ListAttachedRolePoliciesPages(listAttachmentsInput, detacher); err != nil {
 | |
| 		log.Printf("[WARN] AWS DetachRolePolicy failed: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	params := &iam.DeleteRoleInput{
 | |
| 		RoleName: aws.String(roleName),
 | |
| 	}
 | |
| 
 | |
| 	log.Printf("[INFO] AWS DeleteRole: %s", roleName)
 | |
| 	_, err = svc.DeleteRole(params)
 | |
| 	if err != nil {
 | |
| 		log.Printf("[WARN] AWS DeleteRole failed: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func deleteTestUser(accessKey *awsAccessKey, userName string) error {
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Region:     aws.String("us-east-1"),
 | |
| 		HTTPClient: cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	svc := iam.New(sess)
 | |
| 	userDetachment := &iam.DetachUserPolicyInput{
 | |
| 		PolicyArn: aws.String("arn:aws:iam::aws:policy/AdministratorAccess"),
 | |
| 		UserName:  aws.String(userName),
 | |
| 	}
 | |
| 	if _, err := svc.DetachUserPolicy(userDetachment); err != nil {
 | |
| 		log.Printf("[WARN] AWS DetachUserPolicy failed: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	deleteAccessKeyInput := &iam.DeleteAccessKeyInput{
 | |
| 		AccessKeyId: aws.String(accessKey.AccessKeyID),
 | |
| 		UserName:    aws.String(userName),
 | |
| 	}
 | |
| 	_, err = svc.DeleteAccessKey(deleteAccessKeyInput)
 | |
| 	if err != nil {
 | |
| 		log.Printf("[WARN] AWS DeleteAccessKey failed: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	deleteTestUserPolicyInput := &iam.DeleteUserPolicyInput{
 | |
| 		PolicyName: aws.String("SelfDestructionTimebomb"),
 | |
| 		UserName:   aws.String(userName),
 | |
| 	}
 | |
| 	_, err = svc.DeleteUserPolicy(deleteTestUserPolicyInput)
 | |
| 	if err != nil {
 | |
| 		log.Printf("[WARN] AWS DeleteUserPolicy failed: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 	deleteTestUserInput := &iam.DeleteUserInput{
 | |
| 		UserName: aws.String(userName),
 | |
| 	}
 | |
| 	log.Printf("[INFO] AWS DeleteUser: %s", userName)
 | |
| 	_, err = svc.DeleteUser(deleteTestUserInput)
 | |
| 	if err != nil {
 | |
| 		log.Printf("[WARN] AWS DeleteUser failed: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func deleteTestGroup(groupName string) error {
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Region:     aws.String("us-east-1"),
 | |
| 		HTTPClient: cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	svc := iam.New(sess)
 | |
| 
 | |
| 	// Detach any managed group policies
 | |
| 	getGroupsInput := &iam.ListAttachedGroupPoliciesInput{
 | |
| 		GroupName: aws.String(groupName),
 | |
| 	}
 | |
| 	getGroupsOutput, err := svc.ListAttachedGroupPolicies(getGroupsInput)
 | |
| 	if err != nil {
 | |
| 		log.Printf("[WARN] AWS ListAttachedGroupPolicies failed: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 	for _, g := range getGroupsOutput.AttachedPolicies {
 | |
| 		detachGroupInput := &iam.DetachGroupPolicyInput{
 | |
| 			GroupName: aws.String(groupName),
 | |
| 			PolicyArn: g.PolicyArn,
 | |
| 		}
 | |
| 		if _, err := svc.DetachGroupPolicy(detachGroupInput); err != nil {
 | |
| 			log.Printf("[WARN] AWS DetachGroupPolicy failed: %v", err)
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Remove any inline policies
 | |
| 	listGroupPoliciesInput := &iam.ListGroupPoliciesInput{
 | |
| 		GroupName: aws.String(groupName),
 | |
| 	}
 | |
| 	listGroupPoliciesOutput, err := svc.ListGroupPolicies(listGroupPoliciesInput)
 | |
| 	if err != nil {
 | |
| 		log.Printf("[WARN] AWS ListGroupPolicies failed: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 	for _, g := range listGroupPoliciesOutput.PolicyNames {
 | |
| 		deleteGroupPolicyInput := &iam.DeleteGroupPolicyInput{
 | |
| 			GroupName:  aws.String(groupName),
 | |
| 			PolicyName: g,
 | |
| 		}
 | |
| 		if _, err := svc.DeleteGroupPolicy(deleteGroupPolicyInput); err != nil {
 | |
| 			log.Printf("[WARN] AWS DeleteGroupPolicy failed: %v", err)
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Delete the group
 | |
| 	deleteTestGroupInput := &iam.DeleteGroupInput{
 | |
| 		GroupName: aws.String(groupName),
 | |
| 	}
 | |
| 	log.Printf("[INFO] AWS DeleteGroup: %s", groupName)
 | |
| 	_, err = svc.DeleteGroup(deleteTestGroupInput)
 | |
| 	if err != nil {
 | |
| 		log.Printf("[WARN] AWS DeleteGroup failed: %v", err)
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func testAccStepConfig(t *testing.T) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "config/root",
 | |
| 		Data: map[string]interface{}{
 | |
| 			"region": os.Getenv("AWS_DEFAULT_REGION"),
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepConfigWithCreds(t *testing.T, accessKey *awsAccessKey) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "config/root",
 | |
| 		Data: map[string]interface{}{
 | |
| 			"region": os.Getenv("AWS_DEFAULT_REGION"),
 | |
| 		},
 | |
| 		PreFlight: func(req *logical.Request) error {
 | |
| 			// Values in Data above get eagerly evaluated due to the testing framework.
 | |
| 			// In particular, they get evaluated before accessKey gets set by CreateUser
 | |
| 			// and thus would fail. By moving to a closure in a PreFlight, we ensure that
 | |
| 			// the creds get evaluated lazily after they've been properly set
 | |
| 			req.Data["access_key"] = accessKey.AccessKeyID
 | |
| 			req.Data["secret_key"] = accessKey.SecretAccessKey
 | |
| 			return nil
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepRotateRoot(oldAccessKey *awsAccessKey) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "config/rotate-root",
 | |
| 		Check: func(resp *logical.Response) error {
 | |
| 			if resp == nil {
 | |
| 				return fmt.Errorf("received nil response from config/rotate-root")
 | |
| 			}
 | |
| 			newAccessKeyID := resp.Data["access_key"].(string)
 | |
| 			if newAccessKeyID == oldAccessKey.AccessKeyID {
 | |
| 				return fmt.Errorf("rotate-root didn't rotate access key")
 | |
| 			}
 | |
| 			awsConfig := &aws.Config{
 | |
| 				Region:      aws.String("us-east-1"),
 | |
| 				HTTPClient:  cleanhttp.DefaultClient(),
 | |
| 				Credentials: credentials.NewStaticCredentials(oldAccessKey.AccessKeyID, oldAccessKey.SecretAccessKey, ""),
 | |
| 			}
 | |
| 			// sigh....
 | |
| 			oldAccessKey.AccessKeyID = newAccessKeyID
 | |
| 			log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 			sess, err := session.NewSession(awsConfig)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			svc := sts.New(sess)
 | |
| 			params := &sts.GetCallerIdentityInput{}
 | |
| 			if _, err := svc.GetCallerIdentity(params); err == nil {
 | |
| 				return fmt.Errorf("bad: old credentials succeeded after rotate")
 | |
| 			}
 | |
| 			if aerr, ok := err.(awserr.Error); ok {
 | |
| 				if aerr.Code() != "InvalidClientTokenId" {
 | |
| 					return fmt.Errorf("Unknown error returned from AWS: %#v", aerr)
 | |
| 				}
 | |
| 				return nil
 | |
| 			}
 | |
| 			return err
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepRead(_ *testing.T, path, name string, credentialTests []credentialTestFunc) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.ReadOperation,
 | |
| 		Path:      path + "/" + name,
 | |
| 		Check: func(resp *logical.Response) error {
 | |
| 			var d struct {
 | |
| 				AccessKey string `mapstructure:"access_key"`
 | |
| 				SecretKey string `mapstructure:"secret_key"`
 | |
| 				STSToken  string `mapstructure:"session_token"`
 | |
| 			}
 | |
| 			if err := mapstructure.Decode(resp.Data, &d); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			log.Printf("[WARN] Generated credentials: %v", d)
 | |
| 			for _, test := range credentialTests {
 | |
| 				err := test(d.AccessKey, d.SecretKey, d.STSToken)
 | |
| 				if err != nil {
 | |
| 					return err
 | |
| 				}
 | |
| 			}
 | |
| 			return nil
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepReadWithMFA(t *testing.T, path, name, mfaCode string, credentialTests []credentialTestFunc) logicaltest.TestStep {
 | |
| 	step := testAccStepRead(t, path, name, credentialTests)
 | |
| 	step.Data = map[string]interface{}{
 | |
| 		"mfa_code": mfaCode,
 | |
| 	}
 | |
| 
 | |
| 	return step
 | |
| }
 | |
| 
 | |
| func testAccStepReadSTSResponse(name string, maximumTTL time.Duration) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.ReadOperation,
 | |
| 		Path:      "creds/" + name,
 | |
| 		Check: func(resp *logical.Response) error {
 | |
| 			if resp.Secret == nil {
 | |
| 				return fmt.Errorf("bad: nil Secret returned")
 | |
| 			}
 | |
| 			ttl := resp.Secret.TTL
 | |
| 			if ttl > maximumTTL {
 | |
| 				return fmt.Errorf("bad: ttl of %d greater than maximum of %d", ttl/time.Second, maximumTTL/time.Second)
 | |
| 			}
 | |
| 			return nil
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func describeInstancesTest(accessKey, secretKey, token string) error {
 | |
| 	creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Credentials: creds,
 | |
| 		Region:      aws.String("us-east-1"),
 | |
| 		HTTPClient:  cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client := ec2.New(sess)
 | |
| 	log.Printf("[WARN] Verifying that the generated credentials work with ec2:DescribeInstances...")
 | |
| 	return retryUntilSuccess(func() error {
 | |
| 		_, err := client.DescribeInstances(&ec2.DescribeInstancesInput{})
 | |
| 		return err
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func describeAzsTestUnauthorized(accessKey, secretKey, token string) error {
 | |
| 	creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Credentials: creds,
 | |
| 		Region:      aws.String("us-east-1"),
 | |
| 		HTTPClient:  cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client := ec2.New(sess)
 | |
| 	log.Printf("[WARN] Verifying that the generated credentials don't work with ec2:DescribeAvailabilityZones...")
 | |
| 	return retryUntilSuccess(func() error {
 | |
| 		_, err := client.DescribeAvailabilityZones(&ec2.DescribeAvailabilityZonesInput{})
 | |
| 		// Need to make sure AWS authenticates the generated credentials but does not authorize the operation
 | |
| 		if err == nil {
 | |
| 			return fmt.Errorf("operation succeeded when expected failure")
 | |
| 		}
 | |
| 		if aerr, ok := err.(awserr.Error); ok {
 | |
| 			if aerr.Code() == "UnauthorizedOperation" {
 | |
| 				return nil
 | |
| 			}
 | |
| 		}
 | |
| 		return err
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func assertCreatedIAMUser(accessKey, secretKey, token string) error {
 | |
| 	creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Credentials: creds,
 | |
| 		Region:      aws.String("us-east-1"),
 | |
| 		HTTPClient:  cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client := iam.New(sess)
 | |
| 	log.Printf("[WARN] Checking if IAM User is created properly...")
 | |
| 	userOutput, err := client.GetUser(&iam.GetUserInput{})
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if *userOutput.User.Path != "/path/" {
 | |
| 		return fmt.Errorf("bad: got: %#v\nexpected: %#v", userOutput.User.Path, "/path/")
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func listIamUsersTest(accessKey, secretKey, token string) error {
 | |
| 	creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Credentials: creds,
 | |
| 		Region:      aws.String("us-east-1"),
 | |
| 		HTTPClient:  cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client := iam.New(sess)
 | |
| 	log.Printf("[WARN] Verifying that the generated credentials work with iam:ListUsers...")
 | |
| 	return retryUntilSuccess(func() error {
 | |
| 		_, err := client.ListUsers(&iam.ListUsersInput{})
 | |
| 		return err
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func listDynamoTablesTest(accessKey, secretKey, token string) error {
 | |
| 	creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Credentials: creds,
 | |
| 		Region:      aws.String("us-east-1"),
 | |
| 		HTTPClient:  cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client := dynamodb.New(sess)
 | |
| 	log.Printf("[WARN] Verifying that the generated credentials work with dynamodb:ListTables...")
 | |
| 	return retryUntilSuccess(func() error {
 | |
| 		_, err := client.ListTables(&dynamodb.ListTablesInput{})
 | |
| 		return err
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func listS3BucketsTest(accessKey, secretKey, token string) error {
 | |
| 	creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
 | |
| 	awsConfig := &aws.Config{
 | |
| 		Credentials: creds,
 | |
| 		Region:      aws.String("us-east-1"),
 | |
| 		HTTPClient:  cleanhttp.DefaultClient(),
 | |
| 	}
 | |
| 	sess, err := session.NewSession(awsConfig)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	client := s3.New(sess)
 | |
| 	log.Printf("[WARN] Verifying that the generated credentials work with s3:ListBuckets...")
 | |
| 	return retryUntilSuccess(func() error {
 | |
| 		_, err := client.ListBuckets(&s3.ListBucketsInput{})
 | |
| 		return err
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func retryUntilSuccess(op func() error) error {
 | |
| 	retryCount := 0
 | |
| 	success := false
 | |
| 	var err error
 | |
| 	for !success && retryCount < 10 {
 | |
| 		err = op()
 | |
| 		if err == nil {
 | |
| 			return nil
 | |
| 		}
 | |
| 		time.Sleep(time.Second)
 | |
| 		retryCount++
 | |
| 	}
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func testAccStepReadSTSWithArnPolicy(t *testing.T, name string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.ReadOperation,
 | |
| 		Path:      "sts/" + name,
 | |
| 		ErrorOk:   true,
 | |
| 		Check: func(resp *logical.Response) error {
 | |
| 			if resp.Data["error"] !=
 | |
| 				"attempted to retrieve iam_user credentials through the sts path; this is not allowed for legacy roles" {
 | |
| 				t.Fatalf("bad: %v", resp)
 | |
| 			}
 | |
| 			return nil
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepWritePolicy(t *testing.T, name string, policy string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Data: map[string]interface{}{
 | |
| 			"policy": policy,
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepDeletePolicy(t *testing.T, n string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.DeleteOperation,
 | |
| 		Path:      "roles/" + n,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepReadPolicy(t *testing.T, name string, value string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.ReadOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Check: func(resp *logical.Response) error {
 | |
| 			if resp == nil {
 | |
| 				if value == "" {
 | |
| 					return nil
 | |
| 				}
 | |
| 
 | |
| 				return fmt.Errorf("bad: %#v", resp)
 | |
| 			}
 | |
| 
 | |
| 			expected := map[string]interface{}{
 | |
| 				"policy_arns":              []string(nil),
 | |
| 				"role_arns":                []string(nil),
 | |
| 				"policy_document":          value,
 | |
| 				"credential_type":          strings.Join([]string{iamUserCred, federationTokenCred}, ","),
 | |
| 				"default_sts_ttl":          int64(0),
 | |
| 				"max_sts_ttl":              int64(0),
 | |
| 				"user_path":                "",
 | |
| 				"permissions_boundary_arn": "",
 | |
| 				"iam_groups":               []string(nil),
 | |
| 				"iam_tags":                 map[string]string(nil),
 | |
| 				"mfa_serial_number":        "",
 | |
| 				"session_tags":             map[string]string(nil),
 | |
| 				"external_id":              "",
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(resp.Data, expected) {
 | |
| 				return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
 | |
| 			}
 | |
| 			return nil
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const testDynamoPolicy = `{
 | |
|     "Version": "2012-10-17",
 | |
|     "Statement": [
 | |
|         {
 | |
|             "Sid": "Stmt1426528957000",
 | |
|             "Effect": "Allow",
 | |
|             "Action": [
 | |
|                 "dynamodb:List*"
 | |
|             ],
 | |
|             "Resource": [
 | |
|                 "*"
 | |
|             ]
 | |
|         }
 | |
|     ]
 | |
| }
 | |
| `
 | |
| 
 | |
| const testS3Policy = `{
 | |
|     "Version": "2012-10-17",
 | |
|     "Statement": [
 | |
|         {
 | |
|             "Effect": "Allow",
 | |
|             "Action": [
 | |
|                 "s3:Get*",
 | |
|                 "s3:List*"
 | |
|             ],
 | |
|             "Resource": "*"
 | |
|         }
 | |
|     ]
 | |
| }`
 | |
| 
 | |
| const (
 | |
| 	adminAccessPolicyArn = "arn:aws:iam::aws:policy/AdministratorAccess"
 | |
| 	ec2PolicyArn         = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
 | |
| 	iamPolicyArn         = "arn:aws:iam::aws:policy/IAMReadOnlyAccess"
 | |
| 	dynamoPolicyArn      = "arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess"
 | |
| )
 | |
| 
 | |
| func testAccStepWriteRole(t *testing.T, name string, data map[string]interface{}) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Data:      data,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepReadRole(t *testing.T, name string, expected map[string]interface{}) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.ReadOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Check: func(resp *logical.Response) error {
 | |
| 			if resp == nil {
 | |
| 				if expected == nil {
 | |
| 					return nil
 | |
| 				}
 | |
| 				return fmt.Errorf("bad: nil response")
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(resp.Data, expected) {
 | |
| 				return fmt.Errorf("bad: got %#v\nexpected: %#v", resp.Data, expected)
 | |
| 			}
 | |
| 			return nil
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepWriteArnPolicyRef(t *testing.T, name string, arn string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Data: map[string]interface{}{
 | |
| 			"arn": ec2PolicyArn,
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_basicPolicyArnRef(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck:       func() { testAccPreCheck(t) },
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteArnPolicyRef(t, "test", ec2PolicyArn),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{describeInstancesTest}),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_iamUserManagedInlinePoliciesGroups(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	compacted, err := compactJSON(testDynamoPolicy)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("bad: %#v", err)
 | |
| 	}
 | |
| 	groupName := generateUniqueGroupName(t.Name())
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"policy_document": testDynamoPolicy,
 | |
| 		"policy_arns":     []string{ec2PolicyArn, iamPolicyArn},
 | |
| 		"iam_groups":      []string{groupName},
 | |
| 		"credential_type": iamUserCred,
 | |
| 		"user_path":       "/path/",
 | |
| 	}
 | |
| 	expectedRoleData := map[string]interface{}{
 | |
| 		"policy_document":          compacted,
 | |
| 		"policy_arns":              []string{ec2PolicyArn, iamPolicyArn},
 | |
| 		"credential_type":          iamUserCred,
 | |
| 		"role_arns":                []string(nil),
 | |
| 		"default_sts_ttl":          int64(0),
 | |
| 		"max_sts_ttl":              int64(0),
 | |
| 		"user_path":                "/path/",
 | |
| 		"permissions_boundary_arn": "",
 | |
| 		"iam_groups":               []string{groupName},
 | |
| 		"iam_tags":                 map[string]string(nil),
 | |
| 		"mfa_serial_number":        "",
 | |
| 	}
 | |
| 
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createGroup(t, groupName, testS3Policy, []string{})
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepReadRole(t, "test", expectedRoleData),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{describeInstancesTest, listIamUsersTest, listDynamoTablesTest, assertCreatedIAMUser, listS3BucketsTest}),
 | |
| 			testAccStepRead(t, "sts", "test", []credentialTestFunc{describeInstancesTest, listIamUsersTest, listDynamoTablesTest, listS3BucketsTest}),
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			return deleteTestGroup(groupName)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Similar to TestBackend_iamUserManagedInlinePoliciesGroups() but managing
 | |
| // policies only with groups
 | |
| func TestAcceptanceBackend_iamUserGroups(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	group1Name := generateUniqueGroupName(t.Name())
 | |
| 	group2Name := generateUniqueGroupName(t.Name())
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"iam_groups":      []string{group1Name, group2Name},
 | |
| 		"credential_type": iamUserCred,
 | |
| 		"user_path":       "/path/",
 | |
| 	}
 | |
| 	expectedRoleData := map[string]interface{}{
 | |
| 		"policy_document":          "",
 | |
| 		"policy_arns":              []string(nil),
 | |
| 		"credential_type":          iamUserCred,
 | |
| 		"role_arns":                []string(nil),
 | |
| 		"default_sts_ttl":          int64(0),
 | |
| 		"max_sts_ttl":              int64(0),
 | |
| 		"user_path":                "/path/",
 | |
| 		"permissions_boundary_arn": "",
 | |
| 		"iam_groups":               []string{group1Name, group2Name},
 | |
| 		"iam_tags":                 map[string]string(nil),
 | |
| 		"mfa_serial_number":        "",
 | |
| 	}
 | |
| 
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createGroup(t, group1Name, testS3Policy, []string{ec2PolicyArn, iamPolicyArn})
 | |
| 			createGroup(t, group2Name, testDynamoPolicy, []string{})
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepReadRole(t, "test", expectedRoleData),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{describeInstancesTest, listIamUsersTest, listDynamoTablesTest, assertCreatedIAMUser, listS3BucketsTest}),
 | |
| 			testAccStepRead(t, "sts", "test", []credentialTestFunc{describeInstancesTest, listIamUsersTest, listDynamoTablesTest, listS3BucketsTest}),
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			if err := deleteTestGroup(group1Name); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			return deleteTestGroup(group2Name)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_AssumedRoleWithPolicyDoc(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	roleName := generateUniqueRoleName(t.Name())
 | |
| 
 | |
| 	awsAccountID, err := getAccountID()
 | |
| 	if err != nil {
 | |
| 		t.Logf("Unable to retrive user via sts:GetCallerIdentity: %#v", err)
 | |
| 		t.Skip("Could not determine AWS account ID from sts:GetCallerIdentity for acceptance tests, skipping")
 | |
| 	}
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"policy_document": allowAllButDescribeAzs,
 | |
| 		"role_arns":       []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", awsAccountID, roleName)},
 | |
| 		"credential_type": assumedRoleCred,
 | |
| 	}
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createRole(t, roleName, awsAccountID, []string{ec2PolicyArn}, nil)
 | |
| 			// Sleep sometime because AWS is eventually consistent
 | |
| 			log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepRead(t, "sts", "test", []credentialTestFunc{describeInstancesTest, describeAzsTestUnauthorized}),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{describeInstancesTest, describeAzsTestUnauthorized}),
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			return deleteTestRole(roleName)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_AssumedRoleWithPolicyARN(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	roleName := generateUniqueRoleName(t.Name())
 | |
| 
 | |
| 	awsAccountID, err := getAccountID()
 | |
| 	if err != nil {
 | |
| 		t.Logf("Unable to retrive user via sts:GetCallerIdentity: %#v", err)
 | |
| 		t.Skip("Could not determine AWS account ID from sts:GetCallerIdentity for acceptance tests, skipping")
 | |
| 	}
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"policy_arns":     iamPolicyArn,
 | |
| 		"role_arns":       []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", awsAccountID, roleName)},
 | |
| 		"credential_type": assumedRoleCred,
 | |
| 	}
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createRole(t, roleName, awsAccountID, []string{ec2PolicyArn, iamPolicyArn}, nil)
 | |
| 			log.Printf("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepRead(t, "sts", "test", []credentialTestFunc{listIamUsersTest, describeAzsTestUnauthorized}),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{listIamUsersTest, describeAzsTestUnauthorized}),
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			return deleteTestRole(roleName)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_AssumedRoleWithGroups(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	roleName := generateUniqueRoleName(t.Name())
 | |
| 	groupName := generateUniqueGroupName(t.Name())
 | |
| 
 | |
| 	awsAccountID, err := getAccountID()
 | |
| 	if err != nil {
 | |
| 		t.Logf("Unable to retrive user via sts:GetCallerIdentity: %#v", err)
 | |
| 		t.Skip("Could not determine AWS account ID from sts:GetCallerIdentity for acceptance tests, skipping")
 | |
| 	}
 | |
| 
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"iam_groups":      []string{groupName},
 | |
| 		"role_arns":       []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", awsAccountID, roleName)},
 | |
| 		"credential_type": assumedRoleCred,
 | |
| 	}
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createRole(t, roleName, awsAccountID, []string{ec2PolicyArn}, nil)
 | |
| 			createGroup(t, groupName, allowAllButDescribeAzs, []string{})
 | |
| 			// Sleep sometime because AWS is eventually consistent
 | |
| 			log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepRead(t, "sts", "test", []credentialTestFunc{describeInstancesTest, describeAzsTestUnauthorized}),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{describeInstancesTest, describeAzsTestUnauthorized}),
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			if err := deleteTestGroup(groupName); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			return deleteTestRole(roleName)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestAcceptanceBackend_AssumedRoleWithSessionTags tests that session tags are
 | |
| // passed to the assumed role.
 | |
| func TestAcceptanceBackend_AssumedRoleWithSessionTags(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	roleName := generateUniqueRoleName(t.Name())
 | |
| 	awsAccountID, err := getAccountID()
 | |
| 	if err != nil {
 | |
| 		t.Logf("Unable to retrive user via sts:GetCallerIdentity: %#v", err)
 | |
| 		t.Skip("Could not determine AWS account ID from sts:GetCallerIdentity for acceptance tests, skipping")
 | |
| 	}
 | |
| 
 | |
| 	roleARN := fmt.Sprintf("arn:aws:iam::%s:role/%s", awsAccountID, roleName)
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"policy_document": allowAllButDescribeAzs,
 | |
| 		"role_arns":       []string{roleARN},
 | |
| 		"credential_type": assumedRoleCred,
 | |
| 		"session_tags": map[string]string{
 | |
| 			"foo": "bar",
 | |
| 			"baz": "qux",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	// allowSessionTagsPolicy allows the role to tag the session, it needs to be
 | |
| 	// included in the trust policy.
 | |
| 	allowSessionTagsPolicy := fmt.Sprintf(`
 | |
| 		{
 | |
| 			"Sid": "AllowPassSessionTagsAndTransitive",
 | |
| 			"Effect": "Allow",
 | |
| 			"Action": "sts:TagSession",
 | |
| 			"Principal": {
 | |
| 				  "AWS": "arn:aws:iam::%s:root"
 | |
| 			}
 | |
| 		}
 | |
| `, awsAccountID)
 | |
| 
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createRole(t, roleName, awsAccountID, []string{ec2PolicyArn}, []string{allowSessionTagsPolicy})
 | |
| 			log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepRead(t, "sts", "test", []credentialTestFunc{describeInstancesTest, describeAzsTestUnauthorized}),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{describeInstancesTest, describeAzsTestUnauthorized}),
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			return deleteTestRole(roleName)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_FederationTokenWithPolicyARN(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	userName := generateUniqueUserName(t.Name())
 | |
| 	accessKey := &awsAccessKey{}
 | |
| 
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"policy_arns":     dynamoPolicyArn,
 | |
| 		"credential_type": federationTokenCred,
 | |
| 	}
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createUser(t, userName, accessKey)
 | |
| 			// Sleep sometime because AWS is eventually consistent
 | |
| 			log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfigWithCreds(t, accessKey),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepRead(t, "sts", "test", []credentialTestFunc{listDynamoTablesTest, describeAzsTestUnauthorized}),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{listDynamoTablesTest, describeAzsTestUnauthorized}),
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			return deleteTestUser(accessKey, userName)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_FederationTokenWithGroups(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	userName := generateUniqueUserName(t.Name())
 | |
| 	groupName := generateUniqueGroupName(t.Name())
 | |
| 	accessKey := &awsAccessKey{}
 | |
| 
 | |
| 	// IAM policy where Statement is a single element, not a list
 | |
| 	iamSingleStatementPolicy := `{
 | |
| 		"Version": "2012-10-17",
 | |
| 		"Statement": {
 | |
| 			"Effect": "Allow",
 | |
| 			"Action": [
 | |
| 				"s3:Get*",
 | |
| 				"s3:List*"
 | |
| 			],
 | |
| 			"Resource": "*"
 | |
| 		}
 | |
| 	}`
 | |
| 
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"iam_groups":      []string{groupName},
 | |
| 		"policy_document": iamSingleStatementPolicy,
 | |
| 		"credential_type": federationTokenCred,
 | |
| 	}
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createUser(t, userName, accessKey)
 | |
| 			createGroup(t, groupName, "", []string{dynamoPolicyArn})
 | |
| 			// Sleep sometime because AWS is eventually consistent
 | |
| 			log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfigWithCreds(t, accessKey),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepRead(t, "sts", "test", []credentialTestFunc{listDynamoTablesTest, describeAzsTestUnauthorized, listS3BucketsTest}),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{listDynamoTablesTest, describeAzsTestUnauthorized, listS3BucketsTest}),
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			if err := deleteTestGroup(groupName); err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			return deleteTestUser(accessKey, userName)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestAcceptanceBackend_SessionToken
 | |
| func TestAcceptanceBackend_SessionToken(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	userName := generateUniqueUserName(t.Name())
 | |
| 	accessKey := &awsAccessKey{}
 | |
| 
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"credential_type": sessionTokenCred,
 | |
| 	}
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createUser(t, userName, accessKey)
 | |
| 			// Sleep sometime because AWS is eventually consistent
 | |
| 			log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfigWithCreds(t, accessKey),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepRead(t, "sts", "test", []credentialTestFunc{listDynamoTablesTest}),
 | |
| 			testAccStepRead(t, "creds", "test", []credentialTestFunc{listDynamoTablesTest}),
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			return deleteTestUser(accessKey, userName)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Running this test requires a pre-made IAM user that has the necessary access permissions set
 | |
| // and a set MFA device. This device serial number along with the other associated values must
 | |
| // be set to the environment variables in the function below.
 | |
| // For this reason, the test is currently a manually run-only acceptance test.
 | |
| func TestAcceptanceBackend_SessionTokenWithMFA(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	serial, found := os.LookupEnv("AWS_TEST_MFA_SERIAL_NUMBER")
 | |
| 	if !found {
 | |
| 		t.Skipf("AWS_TEST_MFA_SERIAL_NUMBER not set, skipping")
 | |
| 	}
 | |
| 	code, found := os.LookupEnv("AWS_TEST_MFA_CODE")
 | |
| 	if !found {
 | |
| 		t.Skipf("AWS_TEST_MFA_CODE not set, skipping")
 | |
| 	}
 | |
| 	accessKeyID, found := os.LookupEnv("AWS_TEST_MFA_USER_ACCESS_KEY")
 | |
| 	if !found {
 | |
| 		t.Skipf("AWS_TEST_MFA_USER_ACCESS_KEY not set, skipping")
 | |
| 	}
 | |
| 	secretKey, found := os.LookupEnv("AWS_TEST_MFA_USER_SECRET_KEY")
 | |
| 	if !found {
 | |
| 		t.Skipf("AWS_TEST_MFA_USER_SECRET_KEY not set, skipping")
 | |
| 	}
 | |
| 
 | |
| 	accessKey := &awsAccessKey{}
 | |
| 	accessKey.AccessKeyID = accessKeyID
 | |
| 	accessKey.SecretAccessKey = secretKey
 | |
| 
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"credential_type":   sessionTokenCred,
 | |
| 		"mfa_serial_number": serial,
 | |
| 	}
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			// Sleep sometime because AWS is eventually consistent
 | |
| 			log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfigWithCreds(t, accessKey),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepReadWithMFA(t, "sts", "test", code, []credentialTestFunc{listDynamoTablesTest}),
 | |
| 			testAccStepReadWithMFA(t, "creds", "test", code, []credentialTestFunc{listDynamoTablesTest}),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestAcceptanceBackend_RoleDefaultSTSTTL(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	roleName := generateUniqueRoleName(t.Name())
 | |
| 	minAwsAssumeRoleDuration := 900
 | |
| 	awsAccountID, err := getAccountID()
 | |
| 	if err != nil {
 | |
| 		t.Logf("Unable to retrive user via sts:GetCallerIdentity: %#v", err)
 | |
| 		t.Skip("Could not determine AWS account ID from sts:GetCallerIdentity for acceptance tests, skipping")
 | |
| 	}
 | |
| 	roleData := map[string]interface{}{
 | |
| 		"role_arns":       []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", awsAccountID, roleName)},
 | |
| 		"credential_type": assumedRoleCred,
 | |
| 		"default_sts_ttl": minAwsAssumeRoleDuration,
 | |
| 		"max_sts_ttl":     minAwsAssumeRoleDuration,
 | |
| 	}
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: true,
 | |
| 		PreCheck: func() {
 | |
| 			testAccPreCheck(t)
 | |
| 			createRole(t, roleName, awsAccountID, []string{ec2PolicyArn}, nil)
 | |
| 			log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
 | |
| 			time.Sleep(10 * time.Second)
 | |
| 		},
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteRole(t, "test", roleData),
 | |
| 			testAccStepReadSTSResponse("test", time.Duration(minAwsAssumeRoleDuration)*time.Second), // allow a little slack
 | |
| 		},
 | |
| 		Teardown: func() error {
 | |
| 			return deleteTestRole(roleName)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestBackend_policyArnCRUD test the CRUD operations for policy ARNs.
 | |
| func TestBackend_policyArnCRUD(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: false,
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteArnPolicyRef(t, "test", ec2PolicyArn),
 | |
| 			testAccStepReadArnPolicy(t, "test", ec2PolicyArn),
 | |
| 			testAccStepDeletePolicy(t, "test"),
 | |
| 			testAccStepReadArnPolicy(t, "test", ""),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func testAccStepReadArnPolicy(t *testing.T, name string, value string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.ReadOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Check: func(resp *logical.Response) error {
 | |
| 			if resp == nil {
 | |
| 				if value == "" {
 | |
| 					return nil
 | |
| 				}
 | |
| 
 | |
| 				return fmt.Errorf("bad: %#v", resp)
 | |
| 			}
 | |
| 
 | |
| 			expected := map[string]interface{}{
 | |
| 				"policy_arns":              []string{value},
 | |
| 				"role_arns":                []string(nil),
 | |
| 				"policy_document":          "",
 | |
| 				"credential_type":          iamUserCred,
 | |
| 				"default_sts_ttl":          int64(0),
 | |
| 				"max_sts_ttl":              int64(0),
 | |
| 				"user_path":                "",
 | |
| 				"permissions_boundary_arn": "",
 | |
| 				"iam_groups":               []string(nil),
 | |
| 				"iam_tags":                 map[string]string(nil),
 | |
| 				"mfa_serial_number":        "",
 | |
| 				"session_tags":             map[string]string(nil),
 | |
| 				"external_id":              "",
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(resp.Data, expected) {
 | |
| 				return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
 | |
| 			}
 | |
| 
 | |
| 			return nil
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepWriteArnRoleRef(t *testing.T, vaultRoleName, awsRoleName, awsAccountID string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "roles/" + vaultRoleName,
 | |
| 		Data: map[string]interface{}{
 | |
| 			"arn": fmt.Sprintf("arn:aws:iam::%s:role/%s", awsAccountID, awsRoleName),
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestBackend_iamGroupsCRUD tests CRUD operations for IAM groups.
 | |
| func TestBackend_iamGroupsCRUD(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: false,
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteIamGroups(t, "test", []string{"group1", "group2"}),
 | |
| 			testAccStepReadIamGroups(t, "test", []string{"group1", "group2"}),
 | |
| 			testAccStepDeletePolicy(t, "test"),
 | |
| 			testAccStepReadIamGroups(t, "test", []string{}),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func testAccStepWriteIamGroups(t *testing.T, name string, groups []string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Data: map[string]interface{}{
 | |
| 			"credential_type": iamUserCred,
 | |
| 			"iam_groups":      groups,
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepReadIamGroups(t *testing.T, name string, groups []string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.ReadOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Check: func(resp *logical.Response) error {
 | |
| 			if resp == nil {
 | |
| 				if len(groups) == 0 {
 | |
| 					return nil
 | |
| 				}
 | |
| 
 | |
| 				return fmt.Errorf("bad: %#v", resp)
 | |
| 			}
 | |
| 
 | |
| 			expected := map[string]interface{}{
 | |
| 				"policy_arns":              []string(nil),
 | |
| 				"role_arns":                []string(nil),
 | |
| 				"policy_document":          "",
 | |
| 				"credential_type":          iamUserCred,
 | |
| 				"default_sts_ttl":          int64(0),
 | |
| 				"max_sts_ttl":              int64(0),
 | |
| 				"user_path":                "",
 | |
| 				"permissions_boundary_arn": "",
 | |
| 				"iam_groups":               groups,
 | |
| 				"iam_tags":                 map[string]string(nil),
 | |
| 				"mfa_serial_number":        "",
 | |
| 				"session_tags":             map[string]string(nil),
 | |
| 				"external_id":              "",
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(resp.Data, expected) {
 | |
| 				return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
 | |
| 			}
 | |
| 
 | |
| 			return nil
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestBackend_iamTagsCRUD tests the CRUD operations for IAM tags.
 | |
| func TestBackend_iamTagsCRUD(t *testing.T) {
 | |
| 	logicaltest.Test(t, logicaltest.TestCase{
 | |
| 		AcceptanceTest: false,
 | |
| 		LogicalBackend: getBackend(t),
 | |
| 		Steps: []logicaltest.TestStep{
 | |
| 			testAccStepConfig(t),
 | |
| 			testAccStepWriteIamTags(t, "test", map[string]string{"key1": "value1", "key2": "value2"}),
 | |
| 			testAccStepReadIamTags(t, "test", map[string]string{"key1": "value1", "key2": "value2"}),
 | |
| 			testAccStepDeletePolicy(t, "test"),
 | |
| 			testAccStepReadIamTags(t, "test", map[string]string{}),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func testAccStepWriteIamTags(t *testing.T, name string, tags map[string]string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Data: map[string]interface{}{
 | |
| 			"credential_type": iamUserCred,
 | |
| 			"iam_tags":        tags,
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepReadIamTags(t *testing.T, name string, tags map[string]string) logicaltest.TestStep {
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.ReadOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Check: func(resp *logical.Response) error {
 | |
| 			if resp == nil {
 | |
| 				if len(tags) == 0 {
 | |
| 					return nil
 | |
| 				}
 | |
| 
 | |
| 				return fmt.Errorf("vault response not received")
 | |
| 			}
 | |
| 
 | |
| 			expected := map[string]interface{}{
 | |
| 				"policy_arns":              []string(nil),
 | |
| 				"role_arns":                []string(nil),
 | |
| 				"policy_document":          "",
 | |
| 				"credential_type":          iamUserCred,
 | |
| 				"default_sts_ttl":          int64(0),
 | |
| 				"max_sts_ttl":              int64(0),
 | |
| 				"user_path":                "",
 | |
| 				"permissions_boundary_arn": "",
 | |
| 				"iam_groups":               []string(nil),
 | |
| 				"iam_tags":                 tags,
 | |
| 				"mfa_serial_number":        "",
 | |
| 				"session_tags":             map[string]string(nil),
 | |
| 				"external_id":              "",
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(resp.Data, expected) {
 | |
| 				return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
 | |
| 			}
 | |
| 
 | |
| 			return nil
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestBackend_stsSessionTagsCRUD tests the CRUD operations for STS session tags.
 | |
| func TestBackend_stsSessionTagsCRUD(t *testing.T) {
 | |
| 	t.Parallel()
 | |
| 
 | |
| 	tagParams0 := map[string]string{"tag1": "value1", "tag2": "value2"}
 | |
| 	tagParams1 := map[string]string{"tag1": "value1", "tag2": "value4", "tag3": "value3"}
 | |
| 
 | |
| 	// list of tags in the form of "key=value"
 | |
| 	tagParamsList0 := []string{"key1=value1", "key2=value2"}
 | |
| 	tagParamsList0Expect := map[string]string{"key1": "value1", "key2": "value2"}
 | |
| 	tagParamsList1 := []string{"key1=value2", "key3=value4"}
 | |
| 	tagParamsList1Expect := map[string]string{"key1": "value2", "key3": "value4"}
 | |
| 
 | |
| 	type testCase struct {
 | |
| 		name        string
 | |
| 		expectTags  []map[string]string
 | |
| 		tagsParams  []any
 | |
| 		externalIDs []string
 | |
| 	}
 | |
| 
 | |
| 	for _, tt := range []testCase{
 | |
| 		{
 | |
| 			name: "mapped-only",
 | |
| 			tagsParams: []any{
 | |
| 				tagParams0,
 | |
| 				map[string]string{},
 | |
| 				tagParams1,
 | |
| 			},
 | |
| 			expectTags: []map[string]string{
 | |
| 				tagParams0,
 | |
| 				{},
 | |
| 				tagParams1,
 | |
| 			},
 | |
| 			externalIDs: []string{"foo", "", "bar"},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "string-list-only",
 | |
| 			tagsParams: []any{
 | |
| 				tagParamsList0,
 | |
| 				tagParamsList1,
 | |
| 			},
 | |
| 			expectTags: []map[string]string{
 | |
| 				tagParamsList0Expect,
 | |
| 				tagParamsList1Expect,
 | |
| 			},
 | |
| 			externalIDs: []string{"foo"},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "mixed-param-types",
 | |
| 			tagsParams: []any{
 | |
| 				tagParams0,
 | |
| 				tagParamsList0,
 | |
| 				tagParams1,
 | |
| 				tagParamsList1,
 | |
| 			},
 | |
| 			expectTags: []map[string]string{
 | |
| 				tagParams0,
 | |
| 				tagParamsList0Expect,
 | |
| 				tagParams1,
 | |
| 				tagParamsList1Expect,
 | |
| 			},
 | |
| 			externalIDs: []string{"foo", "bar"},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "unset-tags",
 | |
| 			tagsParams: []any{
 | |
| 				tagParams0,
 | |
| 				map[string]string{},
 | |
| 			},
 | |
| 			expectTags: []map[string]string{
 | |
| 				tagParams0,
 | |
| 				{},
 | |
| 			},
 | |
| 		},
 | |
| 	} {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			steps := []logicaltest.TestStep{
 | |
| 				testAccStepConfig(t),
 | |
| 			}
 | |
| 
 | |
| 			if len(tt.tagsParams) != len(tt.expectTags) {
 | |
| 				t.Fatalf("invalid test case: test case params and expect must have the same length")
 | |
| 			}
 | |
| 
 | |
| 			// lastNonEmptyExternalID is used to store the last non-empty external ID for the
 | |
| 			// test case. The value will is expected to be set on the role. Setting the value
 | |
| 			// to an empty string has no effect on update operations.
 | |
| 			var lastNonEmptyExternalID string
 | |
| 			for idx, params := range tt.tagsParams {
 | |
| 				var externalID string
 | |
| 				if len(tt.externalIDs) > idx {
 | |
| 					externalID = tt.externalIDs[idx]
 | |
| 				}
 | |
| 				if externalID != "" {
 | |
| 					lastNonEmptyExternalID = externalID
 | |
| 				}
 | |
| 				steps = append(steps, testAccStepWriteSTSSessionTags(t, tt.name, params, externalID))
 | |
| 				steps = append(steps, testAccStepReadSTSSessionTags(t, tt.name, tt.expectTags[idx], lastNonEmptyExternalID, false))
 | |
| 			}
 | |
| 			steps = append(
 | |
| 				steps,
 | |
| 				testAccStepDeletePolicy(t, tt.name),
 | |
| 				testAccStepReadSTSSessionTags(t, tt.name, nil, "", true),
 | |
| 			)
 | |
| 			logicaltest.Test(t, logicaltest.TestCase{
 | |
| 				AcceptanceTest: false,
 | |
| 				LogicalBackend: getBackend(t),
 | |
| 				Steps:          steps,
 | |
| 			})
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepWriteSTSSessionTags(t *testing.T, name string, tags any, externalID string) logicaltest.TestStep {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	data := map[string]interface{}{
 | |
| 		"credential_type": assumedRoleCred,
 | |
| 		"session_tags":    tags,
 | |
| 	}
 | |
| 	if externalID != "" {
 | |
| 		data["external_id"] = externalID
 | |
| 	}
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.UpdateOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Data:      data,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func testAccStepReadSTSSessionTags(t *testing.T, name string, tags any, externalID string, expectNilResp bool) logicaltest.TestStep {
 | |
| 	t.Helper()
 | |
| 
 | |
| 	return logicaltest.TestStep{
 | |
| 		Operation: logical.ReadOperation,
 | |
| 		Path:      "roles/" + name,
 | |
| 		Check: func(resp *logical.Response) error {
 | |
| 			if resp == nil {
 | |
| 				if expectNilResp {
 | |
| 					return nil
 | |
| 				}
 | |
| 				return fmt.Errorf("vault response not received")
 | |
| 			}
 | |
| 
 | |
| 			expected := map[string]interface{}{
 | |
| 				"policy_arns":              []string(nil),
 | |
| 				"role_arns":                []string(nil),
 | |
| 				"policy_document":          "",
 | |
| 				"credential_type":          assumedRoleCred,
 | |
| 				"default_sts_ttl":          int64(0),
 | |
| 				"max_sts_ttl":              int64(0),
 | |
| 				"user_path":                "",
 | |
| 				"permissions_boundary_arn": "",
 | |
| 				"iam_groups":               []string(nil),
 | |
| 				"iam_tags":                 map[string]string(nil),
 | |
| 				"mfa_serial_number":        "",
 | |
| 				"session_tags":             tags,
 | |
| 				"external_id":              externalID,
 | |
| 			}
 | |
| 			if !reflect.DeepEqual(resp.Data, expected) {
 | |
| 				return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
 | |
| 			}
 | |
| 
 | |
| 			return nil
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func generateUniqueRoleName(prefix string) string {
 | |
| 	return generateUniqueName(prefix, 64)
 | |
| }
 | |
| 
 | |
| func generateUniqueUserName(prefix string) string {
 | |
| 	return generateUniqueName(prefix, 64)
 | |
| }
 | |
| 
 | |
| func generateUniqueGroupName(prefix string) string {
 | |
| 	return generateUniqueName(prefix, 128)
 | |
| }
 | |
| 
 | |
| func generateUniqueName(prefix string, maxLength int) string {
 | |
| 	name := testhelpers.RandomWithPrefix(prefix)
 | |
| 	if len(name) > maxLength {
 | |
| 		return name[:maxLength]
 | |
| 	}
 | |
| 	return name
 | |
| }
 | |
| 
 | |
| type awsAccessKey struct {
 | |
| 	AccessKeyID     string
 | |
| 	SecretAccessKey string
 | |
| }
 | |
| 
 | |
| type credentialTestFunc func(string, string, string) error
 | 
