Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ go 1.24.1

require (
fortio.org/progressbar v1.1.0
github.com/aws/aws-sdk-go-v2 v1.21.0
github.com/aws/aws-sdk-go-v2/config v1.18.42
github.com/aws/aws-sdk-go-v2/credentials v1.13.40
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0
github.com/fsnotify/fsnotify v1.9.0
github.com/gin-gonic/gin v1.10.0
github.com/go-logr/logr v1.4.2
Expand Down Expand Up @@ -64,6 +68,14 @@ require (
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/antonmedv/expr v1.15.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect
github.com/aws/smithy-go v1.14.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/blendle/zapdriver v1.3.1 // indirect
Expand Down
27 changes: 27 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,30 @@ github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/antonmedv/expr v1.15.3 h1:q3hOJZNvLvhqE8OHBs1cFRdbXFNKuA+bHmRaI+AmRmI=
github.com/antonmedv/expr v1.15.3/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE=
github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc=
github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M=
github.com/aws/aws-sdk-go-v2/config v1.18.42 h1:28jHROB27xZwU0CB88giDSjz7M1Sba3olb5JBGwina8=
github.com/aws/aws-sdk-go-v2/config v1.18.42/go.mod h1:4AZM3nMMxwlG+eZlxvBKqwVbkDLlnN2a4UGTL6HjaZI=
github.com/aws/aws-sdk-go-v2/credentials v1.13.40 h1:s8yOkDh+5b1jUDhMBtngF6zKWLDs84chUk2Vk0c38Og=
github.com/aws/aws-sdk-go-v2/credentials v1.13.40/go.mod h1:VtEHVAAqDWASwdOqj/1huyT6uHbs5s8FUHfDQdky/Rs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 h1:g+qlObJH4Kn4n21g69DjspU0hKTjWtq7naZ9OLCv0ew=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o=
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 h1:YkNzx1RLS0F5qdf9v1Q8Cuv9NXCL2TkosOxhzlUPV64=
github.com/aws/aws-sdk-go-v2/service/sso v1.14.1/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 h1:8lKOidPkmSmfUtiTgtdXWgaKItCZ/g75/jEk6Ql6GsA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4=
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 h1:s4bioTgjSFRwOoyEFzAVCmFmoowBgjTR8gkrF/sQ4wk=
github.com/aws/aws-sdk-go-v2/service/sts v1.22.0/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU=
github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ=
github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down Expand Up @@ -289,6 +313,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bwwc=
github.com/jarcoal/httpmock v1.2.0/go.mod h1:oCoTsnAz4+UoOUIf5lJOWV2QQIW5UoeUI6aM2YnWAZk=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
Expand Down Expand Up @@ -895,6 +921,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
Expand Down
100 changes: 100 additions & 0 deletions pkg/auth/aws/config.go
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
}
175 changes: 175 additions & 0 deletions pkg/auth/aws/credentials.go
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"
Comment thread
slin1237 marked this conversation as resolved.
}
Loading
Loading