-
Notifications
You must be signed in to change notification settings - Fork 31
fix(aws): use default credential chain #403
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,13 +20,10 @@ import ( | |
| "context" | ||
| "fmt" | ||
| "net/http" | ||
| "os" | ||
| "time" | ||
|
|
||
| "github.com/aws/aws-sdk-go-v2/aws" | ||
| "github.com/aws/aws-sdk-go-v2/config" | ||
| "github.com/aws/aws-sdk-go-v2/credentials" | ||
| "github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds" | ||
| "github.com/aws/aws-sdk-go-v2/service/ec2" | ||
| "github.com/mitchellh/mapstructure" | ||
| "k8s.io/klog/v2" | ||
|
|
@@ -52,15 +49,12 @@ type EC2Client interface { | |
| DescribeInstanceTopology(ctx context.Context, params *ec2.DescribeInstanceTopologyInput, optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTopologyOutput, error) | ||
| } | ||
|
|
||
| type CredsClient interface { | ||
| Retrieve(ctx context.Context) (aws.Credentials, error) | ||
| } | ||
|
|
||
| type ClientFactory func(region string, pageSize *int) (*Client, error) | ||
|
|
||
| type Client struct { | ||
| ec2 EC2Client | ||
| pageSize int32 | ||
| ec2 EC2Client | ||
| credentials aws.CredentialsProvider | ||
| pageSize int32 | ||
| } | ||
|
|
||
| func (c *Client) PageSize() *int32 { | ||
|
|
@@ -78,7 +72,7 @@ func NamedLoader() (string, providers.Loader) { | |
| } | ||
|
|
||
| func Loader(ctx context.Context, cfg providers.Config) (providers.Provider, *httperr.Error) { | ||
| creds, httpErr := getCredentials(ctx, cfg.Creds) | ||
| credsProvider, httpErr := getCredentialsProvider(cfg.Creds) | ||
| if httpErr != nil { | ||
| return nil, httpErr | ||
| } | ||
|
|
@@ -89,61 +83,49 @@ func Loader(ctx context.Context, cfg providers.Config) (providers.Provider, *htt | |
| } | ||
|
|
||
| clientFactory := func(region string, pageSize *int) (*Client, error) { | ||
| opts := []func(*config.LoadOptions) error{ | ||
| config.WithRegion(region), | ||
| config.WithCredentialsProvider( | ||
| credentials.NewStaticCredentialsProvider(creds.AccessKeyId, creds.SecretAccessKey, creds.Token), | ||
| )} | ||
|
|
||
| awsCfg, err := config.LoadDefaultConfig(ctx, opts...) | ||
| awsCfg, err := loadAWSConfig(ctx, region, credsProvider) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to load SDK config: %v", err) | ||
| } | ||
|
|
||
| ec2Client := ec2.NewFromConfig(awsCfg) | ||
|
|
||
| return &Client{ | ||
| ec2: ec2Client, | ||
| pageSize: setPageSize(pageSize), | ||
| ec2: ec2Client, | ||
| credentials: awsCfg.Credentials, | ||
| pageSize: setPageSize(pageSize), | ||
| }, nil | ||
| } | ||
|
|
||
| return New(clientFactory, trimTiers), nil | ||
| } | ||
|
|
||
| func getCredentials(ctx context.Context, creds map[string]any) (*Credentials, *httperr.Error) { | ||
| var accessKeyID, secretAccessKey, sessionToken string | ||
| func getCredentialsProvider(creds map[string]any) (aws.CredentialsProvider, *httperr.Error) { | ||
| if len(creds) == 0 { | ||
| klog.Infof("Using AWS SDK default credential chain") | ||
| return nil, nil | ||
| } | ||
|
|
||
| if len(creds) != 0 { | ||
| klog.Infof("Using provided AWS credentials") | ||
| parsedCreds, err := decodeCredentials(creds) | ||
| if err != nil { | ||
| return nil, httperr.NewError(http.StatusBadRequest, "credentials error: "+err.Error()) | ||
| } | ||
| accessKeyID = parsedCreds.AccessKeyId | ||
| secretAccessKey = parsedCreds.SecretAccessKey | ||
| sessionToken = parsedCreds.Token | ||
| } else if len(os.Getenv("AWS_ACCESS_KEY_ID")) != 0 && len(os.Getenv("AWS_SECRET_ACCESS_KEY")) != 0 { | ||
| klog.Infof("Using shell AWS credentials") | ||
| accessKeyID = os.Getenv("AWS_ACCESS_KEY_ID") | ||
| secretAccessKey = os.Getenv("AWS_SECRET_ACCESS_KEY") | ||
| sessionToken = os.Getenv("AWS_SESSION_TOKEN") | ||
| } else { | ||
| klog.Infof("Using node AWS access credentials") | ||
| creds, err := getCredentialsFromProvider(ctx) | ||
| if err != nil { | ||
| return nil, httperr.NewError(http.StatusUnauthorized, err.Error()) | ||
| } | ||
| accessKeyID = creds.AccessKeyID | ||
| secretAccessKey = creds.SecretAccessKey | ||
| sessionToken = creds.SessionToken | ||
| klog.Infof("Using explicit Topograph AWS credentials") | ||
| parsedCreds, err := decodeCredentials(creds) | ||
| if err != nil { | ||
| return nil, httperr.NewError(http.StatusBadRequest, "credentials error: "+err.Error()) | ||
| } | ||
|
|
||
| return credentials.NewStaticCredentialsProvider( | ||
| parsedCreds.AccessKeyId, | ||
| parsedCreds.SecretAccessKey, | ||
| parsedCreds.Token, | ||
| ), nil | ||
| } | ||
|
|
||
| func loadAWSConfig(ctx context.Context, region string, credsProvider aws.CredentialsProvider) (aws.Config, error) { | ||
| opts := []func(*config.LoadOptions) error{config.WithRegion(region)} | ||
| if credsProvider != nil { | ||
| opts = append(opts, config.WithCredentialsProvider(credsProvider)) | ||
| } | ||
|
|
||
| return &Credentials{ | ||
| AccessKeyId: accessKeyID, | ||
| SecretAccessKey: secretAccessKey, | ||
| Token: sessionToken, | ||
| }, nil | ||
| return config.LoadDefaultConfig(ctx, opts...) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One precedence change worth confirming is intentional: the old resolver only consulted request creds, then AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, then the EC2 instance role — it never read ~/.aws/config, ~/.aws/credentials, or AWS_PROFILE. The full default chain places shared-config/profile (and IRSA) ahead of the instance role, so a host with stray ~/.aws state or an exported AWS_PROFILE that previously fell through to instance-role creds will now silently authenticate as that profile identity. The docs cover the env / Pod Identity case; worth extending the caveat to shared-config/profile too.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the expanded precedence is intentional because the goal is to use the standard SDK chain. I’ll make the warning explicit for AWS_PROFILE and shared config/credentials files. |
||
| } | ||
|
|
||
| func decodeCredentials(creds map[string]any) (*Credentials, error) { | ||
|
|
@@ -161,25 +143,6 @@ func decodeCredentials(creds map[string]any) (*Credentials, error) { | |
| return c, nil | ||
| } | ||
|
|
||
| func getCredentialsFromProvider(ctx context.Context) (creds aws.Credentials, err error) { | ||
| credsClient := ec2rolecreds.New() | ||
|
|
||
| for { | ||
| creds, err = credsClient.Retrieve(ctx) | ||
| if err != nil { | ||
| return creds, err | ||
| } | ||
|
|
||
| if time.Now().Add(tokenTimeDelay).After(creds.Expires) { | ||
| klog.V(4).Infof("Waiting %s for new token", tokenTimeDelay.String()) | ||
| time.Sleep(tokenTimeDelay) | ||
| continue | ||
| } | ||
|
|
||
| return creds, nil | ||
| } | ||
| } | ||
|
|
||
| func (p *baseProvider) GenerateTopologyConfig(ctx context.Context, pageSize *int, instances []topology.ComputeInstances) (*topology.Graph, *httperr.Error) { | ||
| topo, err := p.generateInstanceTopology(ctx, pageSize, instances) | ||
| if err != nil { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With no explicit creds, credential-resolution failure is now deferred to the API call: the old ec2rolecreds path returned 401 at load time when instance-role creds couldn't be retrieved, whereas an unresolvable default chain now surfaces as the 502 from DescribeInstanceTopology. #177 deliberately curated these status codes, so worth deciding whether a no-credentials failure should still present as 401 rather than 502.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. The status-code change was not intentional. I’ll preserve the previous 401 for credential-resolution failures while keeping EC2 API authorization errors as 502.