Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bugfix] Assume role works regardless of how AWS Profile is specified #375

Merged
merged 1 commit into from
Nov 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 53 additions & 22 deletions ecs-cli/modules/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func TestRegionOrderOfResolutionECSConfig(t *testing.T) {
ecsConfig.AWSProfile = customProfileName
os.Setenv("AWS_REGION", envAwsRegion)
os.Setenv("AWS_PROFILE", customProfileName)
os.Setenv("AWS_DEFAULT_PROFILE", assumeRoleName)
os.Setenv("AWS_CONFIG_FILE", "aws_config_example.ini")
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "aws_credentials_example.ini")
defer os.Clearenv()
Expand All @@ -93,6 +94,7 @@ func TestRegionOrderOfResolutionEnvVar(t *testing.T) {
ecsConfig.AWSProfile = customProfileName
os.Setenv("AWS_REGION", envAwsRegion) // takes precedence
os.Setenv("AWS_PROFILE", customProfileName)
os.Setenv("AWS_DEFAULT_PROFILE", assumeRoleName)
os.Setenv("AWS_CONFIG_FILE", "aws_config_example.ini")
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "aws_credentials_example.ini")
defer os.Clearenv()
Expand All @@ -111,6 +113,7 @@ func TestRegionOrderOfResolutionDefaultEnvVar(t *testing.T) {
ecsConfig.AWSProfile = customProfileName
os.Setenv("AWS_DEFAULT_REGION", envAwsRegion) // takes precedence
os.Setenv("AWS_PROFILE", customProfileName)
os.Setenv("AWS_DEFAULT_PROFILE", assumeRoleName)
os.Setenv("AWS_CONFIG_FILE", "aws_config_example.ini")
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "aws_credentials_example.ini")
defer os.Clearenv()
Expand Down Expand Up @@ -328,34 +331,62 @@ func TestCredentialsWhenUsingDefaultAWSProfileEnvVariable(t *testing.T) {
}

// 3d) Use credentials from assume role profile
func TestCredentialsWhenUsingAssumeRoleProfile(t *testing.T) {
func TestCredentialsWhenUsingAssumeRoleAWSProfileFlag(t *testing.T) {
// defaults
ecsConfig := NewCLIConfig(clusterName)

// set variables for test
flagSet := flag.NewFlagSet("ecs-cli-up", 0)
flagSet.String(flags.AWSProfileFlag, customProfileName, "")
context := cli.NewContext(nil, flagSet, nil)
ecsConfig.AWSProfile = assumeRoleName
os.Setenv("AWS_CONFIG_FILE", "aws_config_example.ini")
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "aws_credentials_example.ini")
defer os.Clearenv()

startingConfig := assumeRoleTestHelper()

// invoke test and verify
testCredentialsInSessionWithConfig(t, context, ecsConfig, startingConfig, assumeRoleAccessKey, assumeRoleSecretKey)
}

func TestCredentialsWhenUsingAssumeRoleEnvVar(t *testing.T) {
// defaults
ecsConfig := NewCLIConfig(clusterName)

// set variables for test
flagSet := flag.NewFlagSet("ecs-cli-up", 0)
context := cli.NewContext(nil, flagSet, nil)
os.Setenv("AWS_DEFAULT_PROFILE", assumeRoleName)
os.Setenv("AWS_CONFIG_FILE", "aws_config_example.ini")
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "aws_credentials_example.ini")
defer os.Clearenv()

startingConfig := assumeRoleTestHelper()

// invoke test and verify
testCredentialsInSessionWithConfig(t, context, ecsConfig, startingConfig, assumeRoleAccessKey, assumeRoleSecretKey)
}

func assumeRoleTestHelper() *aws.Config {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
const respMsg = `
<AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
<AssumeRoleResult>
<AssumedRoleUser>
<Arn>arn:aws:sts::account_id:assumed-role/role/session_name</Arn>
<AssumedRoleId>AKID:session_name</AssumedRoleId>
</AssumedRoleUser>
<Credentials>
<AccessKeyId>` + assumeRoleAccessKey + `</AccessKeyId>
<SecretAccessKey>` + assumeRoleSecretKey + `</SecretAccessKey>
<SessionToken>SESSION_TOKEN</SessionToken>
<Expiration>%s</Expiration>
</Credentials>
</AssumeRoleResult>
<ResponseMetadata>
<RequestId>request-id</RequestId>
</ResponseMetadata>
<AssumeRoleResult>
<AssumedRoleUser>
<Arn>arn:aws:sts::account_id:assumed-role/role/session_name</Arn>
<AssumedRoleId>AKID:session_name</AssumedRoleId>
</AssumedRoleUser>
<Credentials>
<AccessKeyId>` + assumeRoleAccessKey + `</AccessKeyId>
<SecretAccessKey>` + assumeRoleSecretKey + `</SecretAccessKey>
<SessionToken>SESSION_TOKEN</SessionToken>
<Expiration>%s</Expiration>
</Credentials>
</AssumeRoleResult>
<ResponseMetadata>
<RequestId>request-id</RequestId>
</ResponseMetadata>
</AssumeRoleResponse>
`
w.Write([]byte(fmt.Sprintf(respMsg, time.Now().Add(15*time.Minute).Format("2006-01-02T15:04:05Z"))))
Expand All @@ -365,8 +396,7 @@ func TestCredentialsWhenUsingAssumeRoleProfile(t *testing.T) {
startingConfig.Endpoint = aws.String(server.URL)
startingConfig.DisableSSL = aws.Bool(true)

// invoke test and verify
testCredentialsInSessionWithConfig(t, ecsConfig, &startingConfig, assumeRoleAccessKey, assumeRoleSecretKey)
return &startingConfig
}

//4) Use credentials from EC2 Instance Role
Expand All @@ -375,6 +405,8 @@ func TestCredentialsWhenUsingEC2InstanceRole(t *testing.T) {
ecsConfig := NewCLIConfig(clusterName)

// set variables for test
flagSet := flag.NewFlagSet("ecs-cli-up", 0)
context := cli.NewContext(nil, flagSet, nil)
os.Setenv("AWS_DEFAULT_PROFILE", ec2InstanceRoleName)
os.Setenv("AWS_CONFIG_FILE", "aws_config_example.ini")
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "aws_credentials_example.ini")
Expand Down Expand Up @@ -410,7 +442,7 @@ func TestCredentialsWhenUsingEC2InstanceRole(t *testing.T) {
startingConfig.EndpointResolver = endpoints.ResolverFunc(myCustomResolver)

// invoke test and verify
testCredentialsInSessionWithConfig(t, ecsConfig, &startingConfig, ec2InstanceRoleAccessKey, ec2InstanceRoleSecretKey)
testCredentialsInSessionWithConfig(t, context, ecsConfig, &startingConfig, ec2InstanceRoleAccessKey, ec2InstanceRoleSecretKey)
}

// Error if Session.Credentials are nil
Expand Down Expand Up @@ -527,10 +559,9 @@ func testCredentialsInSessionWithContext(t *testing.T, context *cli.Context, inp
verifyCredentialsInSession(t, awsSession, expectedAccessKey, expectedSecretKey)
}

func testCredentialsInSessionWithConfig(t *testing.T, inputConfig *CLIConfig, ecsConfig *aws.Config,
func testCredentialsInSessionWithConfig(t *testing.T, context *cli.Context, inputConfig *CLIConfig, ecsConfig *aws.Config,
expectedAccessKey, expectedSecretKey string) {
//awsSession, err := inputConfig.toAWSSessionWithConfig(*ecsConfig)
awsSession, err := defaultSessionFromProfile(inputConfig.Region, *ecsConfig)
awsSession, err := inputConfig.toAWSSessionWithConfig(context, ecsConfig)
assert.NoError(t, err, "Unexpected error generating a new session")

verifyCredentialsInSession(t, awsSession, expectedAccessKey, expectedSecretKey)
Expand Down
68 changes: 39 additions & 29 deletions ecs-cli/modules/config/config_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ func NewCLIConfig(cluster string) *CLIConfig {
// a) AWS_DEFAULT_PROFILE environment variable (defaults to 'default')
// 5) EC2 Instance role
func (cfg *CLIConfig) ToAWSSession(context *cli.Context) (*session.Session, error) {
return cfg.toAWSSessionWithConfig(context, &aws.Config{})
}

// ToAWSSessionWithConfig processes credential order of precedence
// The argument svcConfig is needed to allow important unit tests to work
// (for example: assume role)
func (cfg *CLIConfig) toAWSSessionWithConfig(context *cli.Context, svcConfig *aws.Config) (*session.Session, error) {

region, err := cfg.getRegion()

Expand All @@ -121,13 +128,18 @@ func (cfg *CLIConfig) ToAWSSession(context *cli.Context) (*session.Session, erro
}

if hasProfileFlags(context) {
return sessionFromECSConfig(cfg, region)
// The AWS SDK Go lets Env Vars override sourcing a profile from the shared credential file
// This means that if the Env Vars are present, it will ignore the profile.
// So we unset them and then reset them, to allow our order of precedence to be correct.
keyID, secretKey := unsetEnvVars()
defer resetEnvVars(keyID, secretKey)
return sessionFromECSConfig(cfg, region, svcConfig)
} else if hasEnvVars(context) {
return defaultSession(region)
return sessionFromProfile("", region, svcConfig)
} else if isDefaultECSProfileCase(cfg) {
return sessionFromECSConfig(cfg, region)
return sessionFromECSConfig(cfg, region, svcConfig)
} else {
return defaultSessionFromProfile(region, aws.Config{})
return sessionFromProfile("", region, svcConfig)
}

}
Expand All @@ -144,43 +156,41 @@ func isDefaultECSProfileCase(cfg *CLIConfig) bool {
return (cfg.AWSAccessKey != "" || cfg.AWSSecretKey != "" || cfg.AWSProfile != "")
}

func sessionFromECSConfig(cfg *CLIConfig, region string) (*session.Session, error) {
func sessionFromECSConfig(cfg *CLIConfig, region string, svcConfig *aws.Config) (*session.Session, error) {
if cfg.AWSSecretKey != "" {
return customSessionFromKeys(region, cfg.AWSAccessKey, cfg.AWSSecretKey)
return sessionFromKeys(region, cfg.AWSAccessKey, cfg.AWSSecretKey, svcConfig)
} else {
return customSessionFromProfile(region, cfg.AWSProfile)
return sessionFromProfile(cfg.AWSProfile, region, svcConfig)
}
}

// The argument svcConfig is needed to allow important unit tests to work
// (for example: assume role)
func defaultSessionFromProfile(region string, svcConfig aws.Config) (*session.Session, error) {
svcConfig.Region = aws.String(region)
return session.NewSessionWithOptions(session.Options{
Config: svcConfig,
Profile: os.Getenv(flags.AwsDefaultProfileEnvVar),
SharedConfigState: session.SharedConfigEnable,
})
func unsetEnvVars() (keyID string, secretKey string) {
keyID = os.Getenv(flags.AWSAccessKeyEnvVar)
secretKey = os.Getenv(flags.AWSSecretKeyEnvVar)
os.Unsetenv(flags.AWSAccessKeyEnvVar)
os.Unsetenv(flags.AWSSecretKeyEnvVar)

return keyID, secretKey
}

func defaultSession(region string) (*session.Session, error) {
return session.NewSession(&aws.Config{
Region: aws.String(region),
})
func resetEnvVars(keyID string, secretKey string) {
os.Setenv(flags.AWSAccessKeyEnvVar, keyID)
os.Setenv(flags.AWSSecretKeyEnvVar, secretKey)
}

func customSessionFromProfile(region string, awsProfile string) (*session.Session, error) {
return session.NewSession(&aws.Config{
Region: aws.String(region),
Credentials: credentials.NewSharedCredentials("", awsProfile),
func sessionFromProfile(profile string, region string, svcConfig *aws.Config) (*session.Session, error) {
svcConfig.Region = aws.String(region)
return session.NewSessionWithOptions(session.Options{
Config: *svcConfig,
Profile: profile,
SharedConfigState: session.SharedConfigEnable,
})
}

func customSessionFromKeys(region string, awsAccess string, awsSecret string) (*session.Session, error) {
return session.NewSession(&aws.Config{
Region: aws.String(region),
Credentials: credentials.NewStaticCredentials(awsAccess, awsSecret, ""),
})
func sessionFromKeys(region string, awsAccess string, awsSecret string, svcConfig *aws.Config) (*session.Session, error) {
svcConfig.Region = aws.String(region)
svcConfig.Credentials = credentials.NewStaticCredentials(awsAccess, awsSecret, "")
return session.NewSession(svcConfig)
}

// Region: Order of resolution
Expand Down