Skip to content

Commit

Permalink
resource: use current user if in management account
Browse files Browse the repository at this point in the history
In the management account if we try to assume the
OrganizationAccountAccessRole and fail we should use the default
credentials and assume they are in the management account.

The OrganizationAccountAccessRole is created by default in child
accounts, but not in the management account.
  • Loading branch information
dschofie committed May 10, 2024
1 parent c192984 commit 61ef8b4
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 94 deletions.
30 changes: 17 additions & 13 deletions lib/awssts/awssts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ package awssts
import (
"strings"

"github.com/aws/aws-sdk-go/service/sts"
"github.com/santiago-labs/telophasecli/lib/localstack"
)

func SetEnviron(currEnv []string,
accessKeyID,
secretAccessKey,
sessionToken string,
func SetEnvironCreds(currEnv []string,
creds *sts.Credentials,
awsRegion *string) []string {
var newEnv []string

for _, e := range currEnv {
if strings.Contains(e, "AWS_ACCESS_KEY_ID=") ||
strings.Contains(e, "AWS_SECRET_ACCESS_KEY=") ||
strings.Contains(e, "AWS_SESSION_TOKEN=") {
continue
if creds != nil {
if strings.Contains(e, "AWS_ACCESS_KEY_ID=") ||
strings.Contains(e, "AWS_SECRET_ACCESS_KEY=") ||
strings.Contains(e, "AWS_SESSION_TOKEN=") {
continue
}
}

if awsRegion != nil && strings.Contains(e, "AWS_REGION=") {
Expand All @@ -26,11 +28,13 @@ func SetEnviron(currEnv []string,
newEnv = append(newEnv, e)
}

newEnv = append(newEnv,
"AWS_ACCESS_KEY_ID="+accessKeyID,
"AWS_SECRET_ACCESS_KEY="+secretAccessKey,
"AWS_SESSION_TOKEN="+sessionToken,
)
if creds != nil {
newEnv = append(newEnv,
"AWS_ACCESS_KEY_ID="+*creds.AccessKeyId,
"AWS_SECRET_ACCESS_KEY="+*creds.SecretAccessKey,
"AWS_SESSION_TOKEN="+*creds.SessionToken,
)
}

if awsRegion != nil {
newEnv = append(newEnv, *awsRegion)
Expand Down
65 changes: 34 additions & 31 deletions resourceoperation/cdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/samsarahq/go/oops"
"github.com/santiago-labs/telophasecli/cmd/runner"
"github.com/santiago-labs/telophasecli/lib/awssess"
"github.com/santiago-labs/telophasecli/lib/awssts"
Expand Down Expand Up @@ -45,18 +46,18 @@ func (co *cdkOperation) ListDependents() []ResourceOperation {
func (co *cdkOperation) Call(ctx context.Context) error {
co.OutputUI.Print(fmt.Sprintf("Executing CDK stack in %s", co.Stack.Path), *co.Account)

opRole, region, err := authAWS(*co.Account, *co.Stack.RoleARN(*co.Account), co.OutputUI)
creds, region, err := authAWS(*co.Account, *co.Stack.RoleARN(*co.Account), co.OutputUI)
if err != nil {
return err
}

// We must bootstrap cdk with the account role.
bootstrapCDK := bootstrapCDK(opRole, region, *co.Account, co.Stack)
bootstrapCDK := bootstrapCDK(creds, region, *co.Account, co.Stack)
if err := co.OutputUI.RunCmd(bootstrapCDK, *co.Account); err != nil {
return err
}

synthCDK := synthCDK(opRole, *co.Account, co.Stack)
synthCDK := synthCDK(creds, *co.Account, co.Stack)
if err := co.OutputUI.RunCmd(synthCDK, *co.Account); err != nil {
return err
}
Expand All @@ -76,14 +77,10 @@ func (co *cdkOperation) Call(ctx context.Context) error {

cmd := exec.Command(localstack.CdkCmd(), cdkArgs...)
cmd.Dir = co.Stack.Path
if opRole != nil {
cmd.Env = awssts.SetEnviron(os.Environ(),
*opRole.Credentials.AccessKeyId,
*opRole.Credentials.SecretAccessKey,
*opRole.Credentials.SessionToken,
co.Stack.AWSRegionEnv(),
)
}
cmd.Env = awssts.SetEnvironCreds(os.Environ(),
creds,
co.Stack.AWSRegionEnv(),
)
if err := co.OutputUI.RunCmd(cmd, *co.Account); err != nil {
return err
}
Expand All @@ -101,7 +98,7 @@ func (co *cdkOperation) ToString() string {
return ""
}

func bootstrapCDK(result *sts.AssumeRoleOutput, region string, acct resource.Account, stack resource.Stack) *exec.Cmd {
func bootstrapCDK(creds *sts.Credentials, region string, acct resource.Account, stack resource.Stack) *exec.Cmd {
cdkArgs := append([]string{
"bootstrap",
fmt.Sprintf("aws://%s/%s", acct.AccountID, region),
Expand All @@ -111,39 +108,31 @@ func bootstrapCDK(result *sts.AssumeRoleOutput, region string, acct resource.Acc

cmd := exec.Command(localstack.CdkCmd(), cdkArgs...)
cmd.Dir = stack.Path
if result != nil {
cmd.Env = awssts.SetEnviron(os.Environ(),
*result.Credentials.AccessKeyId,
*result.Credentials.SecretAccessKey,
*result.Credentials.SessionToken,
stack.AWSRegionEnv(),
)
}
cmd.Env = awssts.SetEnvironCreds(os.Environ(),
creds,
stack.AWSRegionEnv(),
)

return cmd
}

func synthCDK(result *sts.AssumeRoleOutput, acct resource.Account, stack resource.Stack) *exec.Cmd {
func synthCDK(creds *sts.Credentials, acct resource.Account, stack resource.Stack) *exec.Cmd {
cdkArgs := append(
[]string{"synth"},
cdkDefaultArgs(acct, stack)...,
)

cmd := exec.Command(localstack.CdkCmd(), cdkArgs...)
cmd.Dir = stack.Path
if result != nil {
cmd.Env = awssts.SetEnviron(os.Environ(),
*result.Credentials.AccessKeyId,
*result.Credentials.SecretAccessKey,
*result.Credentials.SessionToken,
stack.AWSRegionEnv(),
)
}
cmd.Env = awssts.SetEnvironCreds(os.Environ(),
creds,
stack.AWSRegionEnv(),
)

return cmd
}

func authAWS(acct resource.Account, arn string, consoleUI runner.ConsoleUI) (*sts.AssumeRoleOutput, string, error) {
func authAWS(acct resource.Account, arn string, consoleUI runner.ConsoleUI) (*sts.Credentials, string, error) {
var svc *sts.STS
sess := session.Must(awssess.DefaultSession())
svc = sts.New(sess)
Expand All @@ -155,7 +144,21 @@ func authAWS(acct resource.Account, arn string, consoleUI runner.ConsoleUI) (*st
}

role, err := awssess.AssumeRole(svc, input)
return role, *sess.Config.Region, err
if err != nil {
// If we are in the management account the OrganizationAccountAccessRole
// does not exist so fallback to the current credentials.
if acct.ManagementAccount {
// I tried to use GetSessionToken to satisfy a type and avoid
// passing around a nil. However, GetSessionToken's output keys are
// not allowed to make any IAM changes.
//
// Return nil because we don't need to assume a role.
return nil, "us-east-1", nil
}

return nil, "", oops.Wrapf(err, "AssumeRole")
}
return role.Credentials, *sess.Config.Region, nil
}

func cdkDefaultArgs(acct resource.Account, stack resource.Stack) []string {
Expand Down
24 changes: 14 additions & 10 deletions resourceoperation/cloudformation.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
Expand All @@ -28,16 +28,20 @@ type cloudformationOp struct {
}

func NewCloudformationOperation(consoleUI runner.ConsoleUI, acct *resource.Account, stack resource.Stack, op int) ResourceOperation {
cfg := aws.NewConfig()
if stack.Region != "" {
cfg.WithRegion(stack.Region)
creds, _, err := authAWS(*acct, *stack.RoleARN(*acct), consoleUI)
if err != nil {
panic(oops.Wrapf(err, "authAWS"))
}
sess := session.Must(awssess.DefaultSession(cfg))
creds := stscreds.NewCredentials(sess, *stack.RoleARN(*acct))
cloudformationClient := cloudformation.New(sess,
&aws.Config{
Credentials: creds,
})

var newCreds *credentials.Credentials
if creds != nil {
newCreds = credentials.NewStaticCredentials(*creds.AccessKeyId, *creds.SecretAccessKey, *creds.SessionToken)
}

cloudformationClient := cloudformation.New(session.Must(awssess.DefaultSession(&aws.Config{
Credentials: newCreds,
Region: &stack.Region,
})))

return &cloudformationOp{
Account: acct,
Expand Down
33 changes: 12 additions & 21 deletions resourceoperation/scp.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ func (so *scpOperation) ListDependents() []ResourceOperation {
func (so *scpOperation) Call(ctx context.Context) error {
so.OutputUI.Print(fmt.Sprintf("Executing SCP Terraform stack in %s", so.Stack.Path), *so.MgmtAcct)

var acctRole *sts.AssumeRoleOutput
var creds *sts.Credentials
if so.MgmtAcct.AssumeRoleName != "" {
var err error
acctRole, _, err = authAWS(*so.MgmtAcct, so.MgmtAcct.AssumeRoleARN(), so.OutputUI)
creds, _, err = authAWS(*so.MgmtAcct, so.MgmtAcct.AssumeRoleARN(), so.OutputUI)
if err != nil {
return err
}
Expand All @@ -121,15 +121,11 @@ func (so *scpOperation) Call(ctx context.Context) error {
}

if initTFCmd != nil {
if acctRole != nil {
initTFCmd.Env = awssts.SetEnviron(os.Environ(),
*acctRole.Credentials.AccessKeyId,
*acctRole.Credentials.SecretAccessKey,
*acctRole.Credentials.SessionToken,
// SCPs can't have regions
nil,
)
}
initTFCmd.Env = awssts.SetEnvironCreds(os.Environ(),
creds,
// SCPs can't have regions
nil,
)
if err := so.OutputUI.RunCmd(initTFCmd, *so.MgmtAcct); err != nil {
return err
}
Expand All @@ -149,16 +145,11 @@ func (so *scpOperation) Call(ctx context.Context) error {
workingPath := so.tmpPath()
cmd := exec.Command(localstack.TfCmd(), args...)
cmd.Dir = workingPath

if acctRole != nil {
cmd.Env = awssts.SetEnviron(os.Environ(),
*acctRole.Credentials.AccessKeyId,
*acctRole.Credentials.SecretAccessKey,
*acctRole.Credentials.SessionToken,
// SCPs don't have regions
nil,
)
}
cmd.Env = awssts.SetEnvironCreds(os.Environ(),
creds,
// SCPs don't have regions
nil,
)

if err := so.OutputUI.RunCmd(cmd, *so.MgmtAcct); err != nil {
return err
Expand Down
32 changes: 13 additions & 19 deletions resourceoperation/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,29 @@ func (to *tfOperation) ListDependents() []ResourceOperation {
func (to *tfOperation) Call(ctx context.Context) error {
to.OutputUI.Print(fmt.Sprintf("Executing Terraform stack in %s", to.Stack.Path), *to.Account)

var stackRole *sts.AssumeRoleOutput
var creds *sts.Credentials
var assumeRoleErr error
if to.Account.AccountID != "" {
if roleArn := to.Stack.RoleARN(*to.Account); roleArn != nil {
stackRole, _, assumeRoleErr = authAWS(*to.Account, *roleArn, to.OutputUI)
creds, _, assumeRoleErr = authAWS(*to.Account, *roleArn, to.OutputUI)
} else {
stackRole, _, assumeRoleErr = authAWS(*to.Account, to.Account.AssumeRoleARN(), to.OutputUI)
creds, _, assumeRoleErr = authAWS(*to.Account, to.Account.AssumeRoleARN(), to.OutputUI)
}

if assumeRoleErr != nil {
return assumeRoleErr
}
}

initTFCmd := to.initTf(stackRole)
initTFCmd := to.initTf(creds)
if initTFCmd != nil {
if err := to.OutputUI.RunCmd(initTFCmd, *to.Account); err != nil {
return err
}
}

// Set workspace if we are using it.
setWorkspace, err := to.setWorkspace(stackRole)
setWorkspace, err := to.setWorkspace(creds)
if err != nil {
return err
}
Expand All @@ -92,10 +92,8 @@ func (to *tfOperation) Call(ctx context.Context) error {
cmd := exec.Command(localstack.TfCmd(), args...)
cmd.Dir = workingPath

cmd.Env = awssts.SetEnviron(os.Environ(),
*stackRole.Credentials.AccessKeyId,
*stackRole.Credentials.SecretAccessKey,
*stackRole.Credentials.SessionToken,
cmd.Env = awssts.SetEnvironCreds(os.Environ(),
creds,
to.Stack.AWSRegionEnv(),
)

Expand All @@ -112,7 +110,7 @@ func (to *tfOperation) Call(ctx context.Context) error {
return nil
}

func (to *tfOperation) initTf(role *sts.AssumeRoleOutput) *exec.Cmd {
func (to *tfOperation) initTf(creds *sts.Credentials) *exec.Cmd {
workingPath := terraform.TmpPath(*to.Account, to.Stack.Path)
terraformDir := filepath.Join(workingPath, ".terraform")
if terraformDir == "" || !strings.Contains(terraformDir, "telophasedirs") {
Expand All @@ -139,10 +137,8 @@ func (to *tfOperation) initTf(role *sts.AssumeRoleOutput) *exec.Cmd {
cmd := exec.Command(localstack.TfCmd(), "init")
cmd.Dir = workingPath

cmd.Env = awssts.SetEnviron(os.Environ(),
*role.Credentials.AccessKeyId,
*role.Credentials.SecretAccessKey,
*role.Credentials.SessionToken,
cmd.Env = awssts.SetEnvironCreds(os.Environ(),
creds,
to.Stack.AWSRegionEnv(),
)

Expand All @@ -168,7 +164,7 @@ func replaceVals(workspace, AccountID, Region string) (string, error) {
return currentContent, nil
}

func (to *tfOperation) setWorkspace(role *sts.AssumeRoleOutput) (*exec.Cmd, error) {
func (to *tfOperation) setWorkspace(creds *sts.Credentials) (*exec.Cmd, error) {
if !to.Stack.WorkspaceEnabled() {
return nil, nil
}
Expand All @@ -183,10 +179,8 @@ func (to *tfOperation) setWorkspace(role *sts.AssumeRoleOutput) (*exec.Cmd, erro
cmd := exec.Command(localstack.TfCmd(), "workspace", "select", "-or-create", rewrittenWorkspace)
cmd.Dir = workingPath

cmd.Env = awssts.SetEnviron(os.Environ(),
*role.Credentials.AccessKeyId,
*role.Credentials.SecretAccessKey,
*role.Credentials.SessionToken,
cmd.Env = awssts.SetEnvironCreds(os.Environ(),
creds,
to.Stack.AWSRegionEnv(),
)

Expand Down

0 comments on commit 61ef8b4

Please sign in to comment.