diff --git a/README.md b/README.md index 105b794..159212d 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,32 @@ The tool uses the AWS SDK's default credential provider chain, which looks for c 3. IAM roles for EC2 instances 4. IAM roles for tasks (ECS/Fargate) +### MFA Support + +The tool supports AWS profiles that require Multi-Factor Authentication (MFA). When using a profile with MFA enabled (configured with `mfa_serial` in `~/.aws/config`), the tool will: + +1. Automatically detect that MFA is required +2. Prompt you to enter your MFA token code +3. Use the token to assume the role and generate temporary credentials + +Example AWS config with MFA: + +```ini +[profile my-mfa-profile] +region = us-east-1 +role_arn = arn:aws:iam::123456789012:role/MyRole +source_profile = default +mfa_serial = arn:aws:iam::123456789012:mfa/my-user +``` + +Usage with MFA: + +```bash +export AWS_PROFILE=my-mfa-profile +./elasticache-token -user-id iam-test-user-01 -replication-group-id iam-test-rg-01 +Enter MFA token: 123456 +``` + ## License This project is released under the MIT License. diff --git a/go.mod b/go.mod index cf40ef3..89c59b8 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.24.5 require ( github.com/aws/aws-sdk-go-v2 v1.37.2 github.com/aws/aws-sdk-go-v2/config v1.30.3 + github.com/aws/aws-sdk-go-v2/credentials v1.18.3 ) require ( - github.com/aws/aws-sdk-go-v2/credentials v1.18.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.2 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 // indirect diff --git a/pkg/awsutils/client.go b/pkg/awsutils/client.go index a394311..fb13402 100644 --- a/pkg/awsutils/client.go +++ b/pkg/awsutils/client.go @@ -1,14 +1,34 @@ package awsutils import ( + "bufio" "context" + "fmt" + "os" + "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" ) +func InteractiveMFATokenProvider() (string, error) { + fmt.Fprint(os.Stderr, "Enter MFA token: ") + reader := bufio.NewReader(os.Stdin) + token, err := reader.ReadString('\n') + if err != nil { + return "", fmt.Errorf("failed to read MFA token: %w", err) + } + return strings.TrimSpace(token), nil +} + func LoadAWSConfig(ctx context.Context, region string) (aws.Config, error) { - cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region)) + cfg, err := config.LoadDefaultConfig(ctx, + config.WithRegion(region), + config.WithAssumeRoleCredentialOptions(func(options *stscreds.AssumeRoleOptions) { + options.TokenProvider = InteractiveMFATokenProvider + }), + ) if err != nil { return aws.Config{}, err }