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:
parent
c1f7e4a276
commit
e66795a095
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue