logical/aws: Run Acceptance Tests in Parallel (#5383)

* Parallelize a couple AWS acceptance tests

Starting an effort to paralleize AWS secret engine acceptance tests.
Currently they take over a minute to run, and this parallelizes the two
that explicitly call a 10-second sleep, reulting in a 10-second speedup
in test time.

* Parameterize IAM user name

Probably not needed, but future-proofing the code

* Make remainder of tests parallel

AWS_ACCOUNT_ID environment variable is no longer being used; global
mutable state is a recipe for disaster when trying to run things in
parallel, and parallelizing the tests exposed a race condition in which
they were depending on the AWS_ACCOUNT_ID environment variable to be set
before they were run.

AWS_DEFAULT_REGION is still left as an environment variable because it
is required by AWS SDKs, but its configuration is now protected by a
sync.Once to ensure it only ever gets called a single time.

* Replace generateUnique*Name with testhelpers method
This commit is contained in:
Joel Thompson 2018-09-25 20:10:53 -04:00 committed by Brian Kassouf
parent c1f7e4a276
commit e66795a095

View file

@ -7,6 +7,7 @@ import (
"net/http"
"os"
"reflect"
"sync"
"testing"
"time"
@ -20,11 +21,14 @@ import (
"github.com/aws/aws-sdk-go/service/iam/iamiface"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/vault/helper/testhelpers"
"github.com/hashicorp/vault/logical"
logicaltest "github.com/hashicorp/vault/logical/testing"
"github.com/mitchellh/mapstructure"
)
var initSetup sync.Once
type mockIAMClient struct {
iamiface.IAMAPI
}
@ -39,6 +43,7 @@ func getBackend(t *testing.T) logical.Backend {
}
func TestBackend_basic(t *testing.T) {
t.Parallel()
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
PreCheck: func() { testAccPreCheck(t) },
@ -52,13 +57,21 @@ func TestBackend_basic(t *testing.T) {
}
func TestBackend_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 := generateUniqueName(t.Name())
userName := generateUniqueName(t.Name())
accessKey := &awsAccessKey{}
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
PreCheck: func() {
testAccPreCheck(t)
createUser(t, accessKey)
createRole(t)
createUser(t, userName, accessKey)
createRole(t, roleName, awsAccountID)
// 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...")
@ -71,16 +84,17 @@ func TestBackend_basicSTS(t *testing.T) {
testAccStepRead(t, "sts", "test", []credentialTestFunc{listDynamoTablesTest}),
testAccStepWriteArnPolicyRef(t, "test", ec2PolicyArn),
testAccStepReadSTSWithArnPolicy(t, "test"),
testAccStepWriteArnRoleRef(t, testRoleName),
testAccStepRead(t, "sts", testRoleName, []credentialTestFunc{describeInstancesTest}),
testAccStepWriteArnRoleRef(t, "test2", roleName, awsAccountID),
testAccStepRead(t, "sts", "test2", []credentialTestFunc{describeInstancesTest}),
},
Teardown: func() error {
return teardown(accessKey)
return teardown(accessKey, roleName, userName)
},
})
}
func TestBackend_policyCrud(t *testing.T) {
t.Parallel()
compacted, err := compactJSON(testDynamoPolicy)
if err != nil {
t.Fatalf("bad: %s", err)
@ -100,6 +114,7 @@ func TestBackend_policyCrud(t *testing.T) {
}
func TestBackend_throttled(t *testing.T) {
t.Parallel()
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
@ -155,20 +170,12 @@ func TestBackend_throttled(t *testing.T) {
}
func testAccPreCheck(t *testing.T) {
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")
}
if v := os.Getenv("AWS_ACCOUNT_ID"); v == "" {
accountID, err := getAccountID()
if err != nil {
t.Logf("Unable to retrive user via iam:GetUser: %#v", err)
t.Skip("AWS_ACCOUNT_ID not explicitly set and could not be read from iam:GetUser for acceptance tests, skipping")
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")
}
log.Printf("[INFO] Test: Used %s as AWS_ACCOUNT_ID", accountID)
os.Setenv("AWS_ACCOUNT_ID", accountID)
}
})
}
func getAccountID() (string, error) {
@ -191,9 +198,7 @@ func getAccountID() (string, error) {
return *res.Account, nil
}
const testRoleName = "Vault-Acceptance-Test-AWS-Assume-Role"
func createRole(t *testing.T) {
func createRole(t *testing.T, roleName, awsAccountID string) {
const testRoleAssumePolicy = `{
"Version": "2012-10-17",
"Statement": [
@ -212,15 +217,15 @@ func createRole(t *testing.T) {
HTTPClient: cleanhttp.DefaultClient(),
}
svc := iam.New(session.New(awsConfig))
trustPolicy := fmt.Sprintf(testRoleAssumePolicy, os.Getenv("AWS_ACCOUNT_ID"))
trustPolicy := fmt.Sprintf(testRoleAssumePolicy, awsAccountID)
params := &iam.CreateRoleInput{
AssumeRolePolicyDocument: aws.String(trustPolicy),
RoleName: aws.String(testRoleName),
RoleName: aws.String(roleName),
Path: aws.String("/"),
}
log.Printf("[INFO] AWS CreateRole: %s", testRoleName)
log.Printf("[INFO] AWS CreateRole: %s", roleName)
_, err := svc.CreateRole(params)
if err != nil {
@ -229,7 +234,7 @@ func createRole(t *testing.T) {
attachment := &iam.AttachRolePolicyInput{
PolicyArn: aws.String(ec2PolicyArn),
RoleName: aws.String(testRoleName), // Required
RoleName: aws.String(roleName), // Required
}
_, err = svc.AttachRolePolicy(attachment)
@ -238,9 +243,7 @@ func createRole(t *testing.T) {
}
}
const testUserName = "Vault-Acceptance-Test-AWS-FederationToken"
func createUser(t *testing.T, accessKey *awsAccessKey) {
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,
@ -277,9 +280,9 @@ func createUser(t *testing.T, accessKey *awsAccessKey) {
svc := iam.New(session.New(awsConfig))
createUserInput := &iam.CreateUserInput{
UserName: aws.String(testUserName),
UserName: aws.String(userName),
}
log.Printf("[INFO] AWS CreateUser: %s", testUserName)
log.Printf("[INFO] AWS CreateUser: %s", userName)
_, err := svc.CreateUser(createUserInput)
if err != nil {
t.Fatalf("AWS CreateUser failed: %v", err)
@ -288,7 +291,7 @@ func createUser(t *testing.T, accessKey *awsAccessKey) {
putPolicyInput := &iam.PutUserPolicyInput{
PolicyDocument: aws.String(timebombPolicy),
PolicyName: aws.String("SelfDestructionTimebomb"),
UserName: aws.String(testUserName),
UserName: aws.String(userName),
}
_, err = svc.PutUserPolicy(putPolicyInput)
if err != nil {
@ -297,7 +300,7 @@ func createUser(t *testing.T, accessKey *awsAccessKey) {
attachUserPolicyInput := &iam.AttachUserPolicyInput{
PolicyArn: aws.String("arn:aws:iam::aws:policy/AdministratorAccess"),
UserName: aws.String(testUserName),
UserName: aws.String(userName),
}
_, err = svc.AttachUserPolicy(attachUserPolicyInput)
if err != nil {
@ -305,7 +308,7 @@ func createUser(t *testing.T, accessKey *awsAccessKey) {
}
createAccessKeyInput := &iam.CreateAccessKeyInput{
UserName: aws.String(testUserName),
UserName: aws.String(userName),
}
createAccessKeyOutput, err := svc.CreateAccessKey(createAccessKeyInput)
if err != nil {
@ -320,7 +323,7 @@ func createUser(t *testing.T, accessKey *awsAccessKey) {
accessKey.SecretAccessKey = *genAccessKey.SecretAccessKey
}
func deleteTestRole() error {
func deleteTestRole(roleName string) error {
awsConfig := &aws.Config{
Region: aws.String("us-east-1"),
HTTPClient: cleanhttp.DefaultClient(),
@ -329,7 +332,7 @@ func deleteTestRole() error {
attachment := &iam.DetachRolePolicyInput{
PolicyArn: aws.String(ec2PolicyArn),
RoleName: aws.String(testRoleName), // Required
RoleName: aws.String(roleName), // Required
}
_, err := svc.DetachRolePolicy(attachment)
if err != nil {
@ -338,10 +341,10 @@ func deleteTestRole() error {
}
params := &iam.DeleteRoleInput{
RoleName: aws.String(testRoleName),
RoleName: aws.String(roleName),
}
log.Printf("[INFO] AWS DeleteRole: %s", testRoleName)
log.Printf("[INFO] AWS DeleteRole: %s", roleName)
_, err = svc.DeleteRole(params)
if err != nil {
@ -351,9 +354,9 @@ func deleteTestRole() error {
return nil
}
func teardown(accessKey *awsAccessKey) error {
func teardown(accessKey *awsAccessKey, roleName, userName string) error {
if err := deleteTestRole(); err != nil {
if err := deleteTestRole(roleName); err != nil {
return err
}
awsConfig := &aws.Config{
@ -364,7 +367,7 @@ func teardown(accessKey *awsAccessKey) error {
userDetachment := &iam.DetachUserPolicyInput{
PolicyArn: aws.String("arn:aws:iam::aws:policy/AdministratorAccess"),
UserName: aws.String(testUserName),
UserName: aws.String(userName),
}
_, err := svc.DetachUserPolicy(userDetachment)
if err != nil {
@ -374,7 +377,7 @@ func teardown(accessKey *awsAccessKey) error {
deleteAccessKeyInput := &iam.DeleteAccessKeyInput{
AccessKeyId: aws.String(accessKey.AccessKeyID),
UserName: aws.String(testUserName),
UserName: aws.String(userName),
}
_, err = svc.DeleteAccessKey(deleteAccessKeyInput)
if err != nil {
@ -384,7 +387,7 @@ func teardown(accessKey *awsAccessKey) error {
deleteUserPolicyInput := &iam.DeleteUserPolicyInput{
PolicyName: aws.String("SelfDestructionTimebomb"),
UserName: aws.String(testUserName),
UserName: aws.String(userName),
}
_, err = svc.DeleteUserPolicy(deleteUserPolicyInput)
if err != nil {
@ -392,9 +395,9 @@ func teardown(accessKey *awsAccessKey) error {
return err
}
deleteUserInput := &iam.DeleteUserInput{
UserName: aws.String(testUserName),
UserName: aws.String(userName),
}
log.Printf("[INFO] AWS DeleteUser: %s", testUserName)
log.Printf("[INFO] AWS DeleteUser: %s", userName)
_, err = svc.DeleteUser(deleteUserInput)
if err != nil {
log.Printf("[WARN] AWS DeleteUser failed: %v", err)
@ -659,6 +662,7 @@ func testAccStepWriteArnPolicyRef(t *testing.T, name string, arn string) logical
}
func TestBackend_basicPolicyArnRef(t *testing.T) {
t.Parallel()
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
PreCheck: func() { testAccPreCheck(t) },
@ -672,6 +676,7 @@ func TestBackend_basicPolicyArnRef(t *testing.T) {
}
func TestBackend_iamUserManagedInlinePolicies(t *testing.T) {
t.Parallel()
compacted, err := compactJSON(testDynamoPolicy)
if err != nil {
t.Fatalf("bad: %#v", err)
@ -702,6 +707,8 @@ func TestBackend_iamUserManagedInlinePolicies(t *testing.T) {
}
func TestBackend_AssumedRoleWithPolicyDoc(t *testing.T) {
t.Parallel()
roleName := generateUniqueName(t.Name())
// 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
@ -718,16 +725,21 @@ func TestBackend_AssumedRoleWithPolicyDoc(t *testing.T) {
}]
}
`
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", os.Getenv("AWS_ACCOUNT_ID"), testRoleName)},
"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)
createRole(t, roleName, awsAccountID)
// Sleep sometime because AWS is eventually consistent
log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
time.Sleep(10 * time.Second)
@ -739,11 +751,14 @@ func TestBackend_AssumedRoleWithPolicyDoc(t *testing.T) {
testAccStepRead(t, "sts", "test", []credentialTestFunc{describeInstancesTest, describeAzsTestUnauthorized}),
testAccStepRead(t, "creds", "test", []credentialTestFunc{describeInstancesTest, describeAzsTestUnauthorized}),
},
Teardown: deleteTestRole,
Teardown: func() error {
return deleteTestRole(roleName)
},
})
}
func TestBackend_policyArnCrud(t *testing.T) {
t.Parallel()
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
Backend: getBackend(t),
@ -785,16 +800,20 @@ func testAccStepReadArnPolicy(t *testing.T, name string, value string) logicalte
}
}
func testAccStepWriteArnRoleRef(t *testing.T, roleName string) logicaltest.TestStep {
func testAccStepWriteArnRoleRef(t *testing.T, vaultRoleName, awsRoleName, awsAccountID string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "roles/" + roleName,
Path: "roles/" + vaultRoleName,
Data: map[string]interface{}{
"arn": fmt.Sprintf("arn:aws:iam::%s:role/%s", os.Getenv("AWS_ACCOUNT_ID"), roleName),
"arn": fmt.Sprintf("arn:aws:iam::%s:role/%s", awsAccountID, awsRoleName),
},
}
}
func generateUniqueName(prefix string) string {
return testhelpers.RandomWithPrefix(prefix)
}
type awsAccessKey struct {
AccessKeyID string
SecretAccessKey string