in internal/pkg/cli/run_local.go [737:875]
func (o *runLocalOpts) taskRoleCredentials(ctx context.Context) (map[string]string, error) {
// assumeRoleMethod tries to directly call sts:AssumeRole for TaskRole using default session
// calls sts:AssumeRole through aws-sdk-go here https://github.com/aws/aws-sdk-go/blob/ac58203a9054cc9d901429bdd94edfc0a7a1de46/aws/credentials/stscreds/assume_role_provider.go#L352
assumeRoleMethod := func() (map[string]string, error) {
taskDef, err := o.ecsClient.TaskDefinition(o.appName, o.envName, o.wkldName)
if err != nil {
return nil, err
}
taskRoleSess, err := o.sessProvider.FromRole(aws.StringValue(taskDef.TaskRoleArn), o.targetEnv.Region)
if err != nil {
return nil, err
}
return sessionEnvVars(ctx, taskRoleSess)
}
// ecsExecMethod tries to use ECS Exec to retrive credentials from running container
ecsExecMethod := func() (map[string]string, error) {
svcDesc, err := o.ecsClient.DescribeService(o.appName, o.envName, o.wkldName)
if err != nil {
return nil, fmt.Errorf("describe ECS service for %s in environment %s: %w", o.wkldName, o.envName, err)
}
stdoutReader, err := o.captureStdout()
if err != nil {
return nil, err
}
defer o.releaseStdout()
// try exec on each container within the service
var wg sync.WaitGroup
containerErr := make(chan error)
for _, task := range svcDesc.Tasks {
taskID, err := awsecs.TaskID(aws.StringValue(task.TaskArn))
if err != nil {
return nil, err
}
for _, container := range task.Containers {
wg.Add(1)
containerName := aws.StringValue(container.Name)
go func() {
defer wg.Done()
err := o.ecsExecutor.ExecuteCommand(awsecs.ExecuteCommandInput{
Cluster: svcDesc.ClusterName,
Command: fmt.Sprintf("/bin/sh -c %q\n", curlContainerCredentialsCmd),
Task: taskID,
Container: containerName,
})
if err != nil {
containerErr <- fmt.Errorf("container %s in task %s: %w", containerName, taskID, err)
}
}()
}
}
// wait for containers to finish and reset stdout
containersFinished := make(chan struct{})
go func() {
wg.Wait()
o.releaseStdout()
close(containersFinished)
}()
type containerCredentialsOutput struct {
AccessKeyId string
SecretAccessKey string
Token string
}
// parse stdout to try and find credentials
credsResult := make(chan map[string]string)
parseErr := make(chan error)
go func() {
select {
case <-containersFinished:
buf, err := io.ReadAll(stdoutReader)
if err != nil {
parseErr <- err
return
}
lines := bytes.Split(buf, []byte("\n"))
var creds containerCredentialsOutput
for _, line := range lines {
err := json.Unmarshal(line, &creds)
if err != nil {
continue
}
credsResult <- map[string]string{
"AWS_ACCESS_KEY_ID": creds.AccessKeyId,
"AWS_SECRET_ACCESS_KEY": creds.SecretAccessKey,
"AWS_SESSION_TOKEN": creds.Token,
}
return
}
parseErr <- errors.New("all containers failed to retrieve credentials")
case <-ctx.Done():
return
}
}()
var containerErrs []error
for {
select {
case creds := <-credsResult:
return creds, nil
case <-ctx.Done():
return nil, ctx.Err()
case err := <-parseErr:
return nil, errors.Join(append([]error{err}, containerErrs...)...)
case err := <-containerErr:
containerErrs = append(containerErrs, err)
}
}
}
credentialsChain := []func() (map[string]string, error){
assumeRoleMethod,
ecsExecMethod,
}
credentialsChainWrappedErrs := []string{
"assume role",
"ecs exec",
}
// return TaskRole credentials from first successful method
var errs []error
for errIndex, method := range credentialsChain {
vars, err := method()
if err == nil {
return vars, nil
}
errs = append(errs, fmt.Errorf("%s: %w", credentialsChainWrappedErrs[errIndex], err))
}
return nil, &errTaskRoleRetrievalFailed{errs}
}