-
Notifications
You must be signed in to change notification settings - Fork 79
[misc] add aws auth for accesskey, ip, assumerole, wi, etc #112
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| package aws | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "time" | ||
| ) | ||
|
|
||
| // AccessKeyConfig represents AWS access key configuration | ||
| type AccessKeyConfig struct { | ||
| AccessKeyID string `mapstructure:"access_key_id" json:"access_key_id"` | ||
| SecretAccessKey string `mapstructure:"secret_access_key" json:"secret_access_key"` | ||
| SessionToken string `mapstructure:"session_token" json:"session_token,omitempty"` | ||
| } | ||
|
|
||
| // Validate validates the access key configuration | ||
| func (c *AccessKeyConfig) Validate() error { | ||
| if c.AccessKeyID == "" { | ||
| return fmt.Errorf("access_key_id is required") | ||
| } | ||
| if c.SecretAccessKey == "" { | ||
| return fmt.Errorf("secret_access_key is required") | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // AssumeRoleConfig represents AWS assume role configuration | ||
| type AssumeRoleConfig struct { | ||
| RoleARN string `mapstructure:"role_arn" json:"role_arn"` | ||
| RoleSessionName string `mapstructure:"role_session_name" json:"role_session_name,omitempty"` | ||
| ExternalID string `mapstructure:"external_id" json:"external_id,omitempty"` | ||
| Duration time.Duration `mapstructure:"duration" json:"duration,omitempty"` | ||
| Tags map[string]string `mapstructure:"tags" json:"tags,omitempty"` | ||
| } | ||
|
|
||
| // Validate validates the assume role configuration | ||
| func (c *AssumeRoleConfig) Validate() error { | ||
| if c.RoleARN == "" { | ||
| return fmt.Errorf("role_arn is required") | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // WebIdentityConfig represents AWS web identity configuration | ||
| type WebIdentityConfig struct { | ||
| RoleARN string `mapstructure:"role_arn" json:"role_arn"` | ||
| TokenFile string `mapstructure:"token_file" json:"token_file"` | ||
| RoleSessionName string `mapstructure:"role_session_name" json:"role_session_name,omitempty"` | ||
| } | ||
|
|
||
| // Validate validates the web identity configuration | ||
| func (c *WebIdentityConfig) Validate() error { | ||
| if c.RoleARN == "" { | ||
| return fmt.Errorf("role_arn is required for web identity") | ||
| } | ||
| if c.TokenFile == "" { | ||
| return fmt.Errorf("token_file is required for web identity") | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // ECSTaskRoleConfig represents ECS task role configuration | ||
| type ECSTaskRoleConfig struct { | ||
| // RelativeURI is the relative URI to the ECS credentials endpoint | ||
| // If not specified, it will be read from AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | ||
| RelativeURI string `mapstructure:"relative_uri" json:"relative_uri,omitempty"` | ||
|
|
||
| // FullURI is the full URI to the ECS credentials endpoint | ||
| // If not specified, it will be read from AWS_CONTAINER_CREDENTIALS_FULL_URI | ||
| FullURI string `mapstructure:"full_uri" json:"full_uri,omitempty"` | ||
|
|
||
| // AuthorizationToken is used for authentication with the ECS credentials endpoint | ||
| // If not specified, it will be read from AWS_CONTAINER_AUTHORIZATION_TOKEN | ||
| AuthorizationToken string `mapstructure:"authorization_token" json:"authorization_token,omitempty"` | ||
| } | ||
|
|
||
| // Validate validates the ECS task role configuration | ||
| func (c *ECSTaskRoleConfig) Validate() error { | ||
| // Either RelativeURI or FullURI must be specified | ||
| if c.RelativeURI == "" && c.FullURI == "" { | ||
| return fmt.Errorf("either relative_uri or full_uri must be specified for ECS task role") | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // ProcessConfig represents process credentials provider configuration | ||
| type ProcessConfig struct { | ||
| // Command is the command to execute to retrieve credentials | ||
| Command string `mapstructure:"command" json:"command"` | ||
|
|
||
| // Timeout is the maximum time to wait for the process to complete | ||
| Timeout time.Duration `mapstructure:"timeout" json:"timeout,omitempty"` | ||
| } | ||
|
|
||
| // Validate validates the process configuration | ||
| func (c *ProcessConfig) Validate() error { | ||
| if c.Command == "" { | ||
| return fmt.Errorf("command is required for process credentials provider") | ||
| } | ||
| return nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| package aws | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "net/http" | ||
| "strings" | ||
| "sync" | ||
| "time" | ||
|
|
||
| "github.com/aws/aws-sdk-go-v2/aws" | ||
| v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" | ||
| "github.com/sgl-project/ome/pkg/auth" | ||
| "github.com/sgl-project/ome/pkg/logging" | ||
| ) | ||
|
|
||
| // AWSCredentials implements auth.Credentials for AWS | ||
| type AWSCredentials struct { | ||
| credProvider aws.CredentialsProvider | ||
| authType auth.AuthType | ||
| region string | ||
| logger logging.Interface | ||
|
|
||
| // Mutex protects cached credentials | ||
| mu sync.RWMutex | ||
| cachedCreds *aws.Credentials | ||
| cacheExpiry time.Time | ||
| } | ||
|
|
||
| // Provider returns the provider type | ||
| func (c *AWSCredentials) Provider() auth.Provider { | ||
| return auth.ProviderAWS | ||
| } | ||
|
|
||
| // Type returns the authentication type | ||
| func (c *AWSCredentials) Type() auth.AuthType { | ||
| return c.authType | ||
| } | ||
|
|
||
| // Token retrieves the AWS credentials as a token string | ||
| func (c *AWSCredentials) Token(ctx context.Context) (string, error) { | ||
| creds, err := c.getCredentials(ctx) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| // Return formatted token (access key for identification) | ||
| return creds.AccessKeyID, nil | ||
| } | ||
|
|
||
| // SignRequest signs an HTTP request with AWS v4 signature | ||
| func (c *AWSCredentials) SignRequest(ctx context.Context, req *http.Request) error { | ||
| creds, err := c.getCredentials(ctx) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get credentials: %w", err) | ||
| } | ||
|
|
||
| // Create a signer | ||
| signer := v4.NewSigner() | ||
|
|
||
| // Determine service from host | ||
| service := extractServiceFromHost(req.Host) | ||
|
|
||
| // Calculate payload hash (empty for GET requests, unsigned for others) | ||
| payloadHash := "UNSIGNED-PAYLOAD" | ||
| if req.Method == http.MethodGet || req.Method == http.MethodHead { | ||
| payloadHash = "" | ||
| } | ||
|
|
||
| // Sign the request | ||
| err = signer.SignHTTP(ctx, *creds, req, payloadHash, service, c.region, time.Now()) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to sign request: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Refresh refreshes the credentials | ||
| func (c *AWSCredentials) Refresh(ctx context.Context) error { | ||
| // Clear cache to force refresh | ||
| c.mu.Lock() | ||
| c.cachedCreds = nil | ||
| c.cacheExpiry = time.Time{} | ||
| c.mu.Unlock() | ||
|
|
||
| // Try to get new credentials | ||
| _, err := c.getCredentials(ctx) | ||
| return err | ||
| } | ||
|
|
||
| // IsExpired checks if the credentials are expired | ||
| func (c *AWSCredentials) IsExpired() bool { | ||
| c.mu.RLock() | ||
| defer c.mu.RUnlock() | ||
|
|
||
| if c.cachedCreds == nil { | ||
| return true | ||
| } | ||
| return time.Now().After(c.cacheExpiry) | ||
| } | ||
|
|
||
| // GetRegion returns the AWS region | ||
| func (c *AWSCredentials) GetRegion() string { | ||
| return c.region | ||
| } | ||
|
|
||
| // GetCredentialsProvider returns the underlying AWS credentials provider | ||
| func (c *AWSCredentials) GetCredentialsProvider() aws.CredentialsProvider { | ||
| return c.credProvider | ||
| } | ||
|
|
||
| // getCredentials retrieves and caches AWS credentials | ||
| func (c *AWSCredentials) getCredentials(ctx context.Context) (*aws.Credentials, error) { | ||
| // Check cache with read lock | ||
| c.mu.RLock() | ||
| if c.cachedCreds != nil && time.Now().Before(c.cacheExpiry) { | ||
| creds := *c.cachedCreds | ||
| c.mu.RUnlock() | ||
| return &creds, nil | ||
| } | ||
| c.mu.RUnlock() | ||
|
|
||
| // Need to refresh - acquire write lock | ||
| c.mu.Lock() | ||
| defer c.mu.Unlock() | ||
|
|
||
| // Double-check after acquiring write lock | ||
| if c.cachedCreds != nil && time.Now().Before(c.cacheExpiry) { | ||
| return c.cachedCreds, nil | ||
| } | ||
|
|
||
| // Retrieve new credentials | ||
| creds, err := c.credProvider.Retrieve(ctx) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to retrieve credentials: %w", err) | ||
| } | ||
|
|
||
| // Cache credentials | ||
| c.cachedCreds = &creds | ||
| if creds.Expires.IsZero() { | ||
| // If no expiry, cache for 1 hour | ||
| c.cacheExpiry = time.Now().Add(1 * time.Hour) | ||
| } else { | ||
| // Cache until 5 minutes before expiry | ||
| c.cacheExpiry = creds.Expires.Add(-5 * time.Minute) | ||
| } | ||
|
|
||
| return &creds, nil | ||
| } | ||
|
|
||
| // extractServiceFromHost extracts the AWS service name from the host | ||
| func extractServiceFromHost(host string) string { | ||
| // Remove port if present | ||
| if idx := strings.LastIndex(host, ":"); idx != -1 { | ||
| host = host[:idx] | ||
| } | ||
|
|
||
| // Extract service from standard AWS domain pattern | ||
| // Examples: s3.amazonaws.com, dynamodb.us-east-1.amazonaws.com | ||
| parts := strings.Split(host, ".") | ||
| if len(parts) >= 2 { | ||
| // Check for service.region.amazonaws.com pattern | ||
| if len(parts) >= 3 && parts[len(parts)-2] == "amazonaws" { | ||
| return parts[0] | ||
| } | ||
| // Check for service.amazonaws.com pattern | ||
| if parts[1] == "amazonaws" { | ||
| return parts[0] | ||
| } | ||
| } | ||
|
|
||
| // Default to s3 for unknown patterns | ||
| return "s3" | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.