From ba43d77a1f2a4814da7d1b5bea36b78c44837dae Mon Sep 17 00:00:00 2001 From: Yaten Date: Sun, 7 Sep 2025 19:24:57 +0530 Subject: [PATCH 01/14] fix: role_arn support in AWSCloudWatch and AWSEmf exporters Signed-off-by: Yaten --- internal/aws/awsutil/conn.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/internal/aws/awsutil/conn.go b/internal/aws/awsutil/conn.go index 9b88f9c33839f..74e5c2536ee0a 100644 --- a/internal/aws/awsutil/conn.go +++ b/internal/aws/awsutil/conn.go @@ -15,6 +15,8 @@ import ( "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" + "github.com/aws/aws-sdk-go-v2/service/sts" "go.uber.org/zap" "golang.org/x/net/http2" ) @@ -103,6 +105,21 @@ func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionS return aws.Config{}, err } + if settings.RoleARN != "" { + stsClient := sts.NewFromConfig(cfg) + + assumeRoleOpts := func(o *stscreds.AssumeRoleOptions) { + if settings.ExternalID != "" { + o.ExternalID = &settings.ExternalID + } + } + + cfg.Credentials = aws.NewCredentialsCache( + stscreds.NewAssumeRoleProvider(stsClient, settings.RoleARN, assumeRoleOpts), + ) + logger.Debug("Using AssumeRole", zap.String("role_arn", settings.RoleARN)) + } + if cfg.Region == "" { logger.Error("cannot fetch region variable from config file, environment variables and ec2 metadata") return aws.Config{}, errors.New("cannot fetch region variable from config file, environment variables and ec2 metadata") From ccec41589b33fa21aeefc3c6f2c2ff2401ccd93e Mon Sep 17 00:00:00 2001 From: Yaten Date: Sun, 7 Sep 2025 21:24:18 +0530 Subject: [PATCH 02/14] add changelog entry & tidy go.mod Signed-off-by: Yaten --- ...x_support-for-role_arn-in-cloud-watch.yaml | 27 +++++++++++++++++++ internal/aws/awsutil/go.mod | 4 +-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 .chloggen/fix_support-for-role_arn-in-cloud-watch.yaml diff --git a/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml b/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml new file mode 100644 index 0000000000000..50f94c00be085 --- /dev/null +++ b/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: "breaking" + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: "awscloudwatchlogexporter" + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: "re-add support for `role_arn` in `awscloudwatchlogexporter` and `awsemfexporter`." + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [42115] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] diff --git a/internal/aws/awsutil/go.mod b/internal/aws/awsutil/go.mod index 13f378c627d65..f74581f1f85ca 100644 --- a/internal/aws/awsutil/go.mod +++ b/internal/aws/awsutil/go.mod @@ -5,6 +5,8 @@ go 1.24.0 require ( github.com/aws/aws-sdk-go-v2 v1.37.0 github.com/aws/aws-sdk-go-v2/config v1.30.1 + github.com/aws/aws-sdk-go-v2/credentials v1.18.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 github.com/stretchr/testify v1.10.0 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.0 @@ -12,7 +14,6 @@ require ( ) require ( - github.com/aws/aws-sdk-go-v2/credentials v1.18.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 // indirect @@ -21,7 +22,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 // indirect github.com/aws/smithy-go v1.22.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.3.1 // indirect From cf613533738e9456b6aa17957eb7f1128f00dbef Mon Sep 17 00:00:00 2001 From: Yaten Date: Sun, 14 Sep 2025 11:50:54 +0530 Subject: [PATCH 03/14] update changelog entry Signed-off-by: Yaten --- .chloggen/fix_support-for-role_arn-in-cloud-watch.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml b/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml index 50f94c00be085..79917c8bc891f 100644 --- a/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml +++ b/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml @@ -4,10 +4,10 @@ change_type: "breaking" # The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) -component: "awscloudwatchlogexporter" +component: ["awscloudwatchlogexporter", "awsemfexporter", "awsxrayexporter"] # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). -note: "re-add support for `role_arn` in `awscloudwatchlogexporter` and `awsemfexporter`." +note: "Fix support for role_arn (STS, short-lived token authentication)." # Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. issues: [42115] From 5d2d530842a2243b6f7da80f135bd782c0359bf5 Mon Sep 17 00:00:00 2001 From: Yaten Date: Sun, 14 Sep 2025 11:56:11 +0530 Subject: [PATCH 04/14] update testify pkg version Signed-off-by: Yaten --- internal/aws/awsutil/go.mod | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/aws/awsutil/go.mod b/internal/aws/awsutil/go.mod index 6b496adbe734a..b5a211dbf9526 100644 --- a/internal/aws/awsutil/go.mod +++ b/internal/aws/awsutil/go.mod @@ -7,8 +7,7 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.30.1 github.com/aws/aws-sdk-go-v2/credentials v1.18.1 github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 - github.com/stretchr/testify v1.10.0 - + github.com/stretchr/testify v1.11.1 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.0 golang.org/x/net v0.44.0 From 52b0d2238713d0d9925be5058c2be5c00581a473 Mon Sep 17 00:00:00 2001 From: Yaten Date: Sun, 14 Sep 2025 15:45:03 +0530 Subject: [PATCH 05/14] refactor GetAWSConfig + update changelog entry Signed-off-by: Yaten --- ...x_support-for-role_arn-in-cloud-watch.yaml | 2 +- .../awscloudwatchlogsexporter/exporter.go | 12 +++++- exporter/awsemfexporter/emf_exporter.go | 13 +++++- exporter/awsxrayexporter/awsxray.go | 13 +++++- internal/aws/awsutil/conn.go | 8 ++-- internal/aws/awsutil/conn_test.go | 43 +++++++++++++++++-- 6 files changed, 80 insertions(+), 11 deletions(-) diff --git a/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml b/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml index 79917c8bc891f..2f5831cd31a99 100644 --- a/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml +++ b/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml @@ -4,7 +4,7 @@ change_type: "breaking" # The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) -component: ["awscloudwatchlogexporter", "awsemfexporter", "awsxrayexporter"] +component: "awscloudwatchlogexporter, awsemfexporter, awsxrayexporter" # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). note: "Fix support for role_arn (STS, short-lived token authentication)." diff --git a/exporter/awscloudwatchlogsexporter/exporter.go b/exporter/awscloudwatchlogsexporter/exporter.go index 0a8820c8647a2..48d83538a7172 100644 --- a/exporter/awscloudwatchlogsexporter/exporter.go +++ b/exporter/awscloudwatchlogsexporter/exporter.go @@ -12,7 +12,9 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/google/uuid" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" @@ -47,13 +49,21 @@ type emfMetadata struct { } func newCwLogsPusher(ctx context.Context, expConfig *Config, params exp.Settings) (*cwlExporter, error) { + var stsClient *sts.Client if expConfig == nil { return nil, errors.New("awscloudwatchlogs exporter config is nil") } + if expConfig.AWSSessionSettings.RoleARN != "" { + baseCfg, err := config.LoadDefaultConfig(ctx) + if err != nil { + return nil, fmt.Errorf("failed to load AWS base config: %w", err) + } + stsClient = sts.NewFromConfig(baseCfg) + } expConfig.logger = params.Logger - awsConfig, err := awsutil.GetAWSConfig(ctx, params.Logger, &expConfig.AWSSessionSettings) + awsConfig, err := awsutil.GetAWSConfig(ctx, params.Logger, &expConfig.AWSSessionSettings, stsClient) if err != nil { return nil, err } diff --git a/exporter/awsemfexporter/emf_exporter.go b/exporter/awsemfexporter/emf_exporter.go index a2c86a8c88e85..209cc49755c15 100644 --- a/exporter/awsemfexporter/emf_exporter.go +++ b/exporter/awsemfexporter/emf_exporter.go @@ -10,6 +10,8 @@ import ( "strings" "sync" + AWSConfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/smithy-go" "github.com/google/uuid" "go.opentelemetry.io/collector/consumer/consumererror" @@ -46,13 +48,22 @@ type emfExporter struct { // newEmfExporter creates a new exporter using exporterhelper func newEmfExporter(ctx context.Context, config *Config, set exporter.Settings) (*emfExporter, error) { + var stsClient *sts.Client if config == nil { return nil, errors.New("emf exporter config is nil") } + if config.AWSSessionSettings.RoleARN != "" { + baseCfg, err := AWSConfig.LoadDefaultConfig(ctx) + if err != nil { + return nil, fmt.Errorf("failed to load AWS base config: %w", err) + } + + stsClient = sts.NewFromConfig(baseCfg) + } config.logger = set.Logger - awsConfig, err := awsutil.GetAWSConfig(ctx, set.Logger, &config.AWSSessionSettings) + awsConfig, err := awsutil.GetAWSConfig(ctx, set.Logger, &config.AWSSessionSettings, stsClient) if err != nil { return nil, err } diff --git a/exporter/awsxrayexporter/awsxray.go b/exporter/awsxrayexporter/awsxray.go index 5b11a9b289ff4..0f17896e769ff 100644 --- a/exporter/awsxrayexporter/awsxray.go +++ b/exporter/awsxrayexporter/awsxray.go @@ -8,6 +8,8 @@ import ( "errors" "fmt" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/aws-sdk-go-v2/service/xray" "github.com/aws/smithy-go" "go.opentelemetry.io/collector/component" @@ -30,10 +32,19 @@ const ( // newTracesExporter creates an exporter.Traces that converts to an X-Ray PutTraceSegments // request and then posts the request to the configured region's X-Ray endpoint. func newTracesExporter(ctx context.Context, cfg *Config, set exporter.Settings, registry telemetry.Registry) (exporter.Traces, error) { + var stsClient *sts.Client typeLog := zap.String("type", set.ID.Type().String()) nameLog := zap.String("name", set.ID.String()) logger := set.Logger - awsConfig, err := awsutil.GetAWSConfig(ctx, logger, &cfg.AWSSessionSettings) + if cfg.RoleARN != "" { + baseCfg, err := config.LoadDefaultConfig(ctx) + if err != nil { + return nil, fmt.Errorf("failed to load AWS base config: %w", err) + } + stsClient = sts.NewFromConfig(baseCfg) + } + + awsConfig, err := awsutil.GetAWSConfig(ctx, logger, &cfg.AWSSessionSettings, stsClient) if err != nil { return nil, err } diff --git a/internal/aws/awsutil/conn.go b/internal/aws/awsutil/conn.go index 74e5c2536ee0a..29cb8eb1efd45 100644 --- a/internal/aws/awsutil/conn.go +++ b/internal/aws/awsutil/conn.go @@ -21,6 +21,10 @@ import ( "golang.org/x/net/http2" ) +type STSClient interface { + AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) +} + // newHTTPClient returns new HTTP client instance with provided configuration. func newHTTPClient(logger *zap.Logger, maxIdle, requestTimeout int, noVerify bool, proxyAddress string, @@ -85,7 +89,7 @@ func getProxyURL(finalProxyAddress string) (*url.URL, error) { } // GetAWSConfig returns AWS config instance. -func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionSettings) (aws.Config, error) { +func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionSettings, stsClient STSClient) (aws.Config, error) { http, err := newHTTPClient(logger, settings.NumberOfWorkers, settings.RequestTimeoutSeconds, settings.NoVerifySSL, settings.ProxyAddress) if err != nil { logger.Error("unable to obtain proxy URL", zap.Error(err)) @@ -106,8 +110,6 @@ func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionS } if settings.RoleARN != "" { - stsClient := sts.NewFromConfig(cfg) - assumeRoleOpts := func(o *stscreds.AssumeRoleOptions) { if settings.ExternalID != "" { o.ExternalID = &settings.ExternalID diff --git a/internal/aws/awsutil/conn_test.go b/internal/aws/awsutil/conn_test.go index 3b609fadbec6f..e63f643038ec6 100644 --- a/internal/aws/awsutil/conn_test.go +++ b/internal/aws/awsutil/conn_test.go @@ -4,14 +4,49 @@ package awsutil import ( + "context" "net/http" "testing" "time" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/aws/aws-sdk-go-v2/service/sts/types" "github.com/stretchr/testify/assert" "go.uber.org/zap" ) +type mockSTS struct{} + +func (m *mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { + return &sts.AssumeRoleOutput{ + Credentials: &types.Credentials{ + AccessKeyId: aws.String("mockAccessKey"), + SecretAccessKey: aws.String("mockSecret"), + SessionToken: aws.String("mockToken"), + }, + }, nil +} + +func TestGetAWSConfig_WithRoleARN(t *testing.T) { + logger := zap.NewNop() + sessionCfg := CreateDefaultSessionConfig() + + cfg, err := GetAWSConfig(t.Context(), logger, &sessionCfg, &mockSTS{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + creds, err := cfg.Credentials.Retrieve(t.Context()) + if err != nil { + t.Fatalf("unexpected creds error: %v", err) + } + + if creds.AccessKeyID != "test" { + t.Errorf("expected mockAccessKey, got %s", creds.AccessKeyID) + } +} + // Test fetching region value from environment variable func TestRegionEnv(t *testing.T) { logger := zap.NewNop() @@ -20,7 +55,7 @@ func TestRegionEnv(t *testing.T) { t.Setenv("AWS_REGION", region) ctx := t.Context() - cfg, err := GetAWSConfig(ctx, logger, &sessionCfg) + cfg, err := GetAWSConfig(ctx, logger, &sessionCfg, nil) assert.NoError(t, err) assert.Equal(t, region, cfg.Region, "Region value should be fetched from environment") } @@ -32,7 +67,7 @@ func TestGetAWSConfigWithExplicitRegion(t *testing.T) { sessionCfg.Region = "eu-west-1" ctx := t.Context() - cfg, err := GetAWSConfig(ctx, logger, &sessionCfg) + cfg, err := GetAWSConfig(ctx, logger, &sessionCfg, nil) assert.NoError(t, err) assert.Equal(t, "eu-west-1", cfg.Region, "Region value should match the explicitly set region") } @@ -45,7 +80,7 @@ func TestGetAWSConfigWithCustomEndpoint(t *testing.T) { sessionCfg.Endpoint = "https://custom.endpoint.com" ctx := t.Context() - cfg, err := GetAWSConfig(ctx, logger, &sessionCfg) + cfg, err := GetAWSConfig(ctx, logger, &sessionCfg, nil) assert.NoError(t, err) assert.Equal(t, "us-west-2", cfg.Region) assert.NotNil(t, cfg.BaseEndpoint) @@ -60,7 +95,7 @@ func TestGetAWSConfigWithRetries(t *testing.T) { sessionCfg.MaxRetries = 5 ctx := t.Context() - cfg, err := GetAWSConfig(ctx, logger, &sessionCfg) + cfg, err := GetAWSConfig(ctx, logger, &sessionCfg, nil) assert.NoError(t, err) assert.Equal(t, 5, cfg.RetryMaxAttempts) } From 0e4283c217b3ecdf90927154d5632cf2888a7d10 Mon Sep 17 00:00:00 2001 From: Yaten Date: Sun, 14 Sep 2025 20:42:35 +0530 Subject: [PATCH 06/14] tidy go.mod files Signed-off-by: Yaten --- exporter/awscloudwatchlogsexporter/go.mod | 4 ++-- exporter/awsemfexporter/go.mod | 4 ++-- exporter/awsxrayexporter/go.mod | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/exporter/awscloudwatchlogsexporter/go.mod b/exporter/awscloudwatchlogsexporter/go.mod index 3d20685cb8414..102e6405f74d5 100644 --- a/exporter/awscloudwatchlogsexporter/go.mod +++ b/exporter/awscloudwatchlogsexporter/go.mod @@ -4,7 +4,9 @@ go 1.24.0 require ( github.com/aws/aws-sdk-go-v2 v1.37.0 + github.com/aws/aws-sdk-go-v2/config v1.30.1 github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.54.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 github.com/cenkalti/backoff/v5 v5.0.3 github.com/google/uuid v1.6.0 github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/awsutil v0.135.0 @@ -27,7 +29,6 @@ require ( require ( github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect @@ -37,7 +38,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 // indirect github.com/aws/smithy-go v1.22.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect diff --git a/exporter/awsemfexporter/go.mod b/exporter/awsemfexporter/go.mod index 5f081c354e418..78993167d5164 100644 --- a/exporter/awsemfexporter/go.mod +++ b/exporter/awsemfexporter/go.mod @@ -3,6 +3,8 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsemf go 1.24.0 require ( + github.com/aws/aws-sdk-go-v2/config v1.30.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 github.com/aws/smithy-go v1.22.5 github.com/google/uuid v1.6.0 github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/awsutil v0.135.0 @@ -30,7 +32,6 @@ require ( require ( github.com/aws/aws-sdk-go-v2 v1.37.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect @@ -41,7 +42,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect diff --git a/exporter/awsxrayexporter/go.mod b/exporter/awsxrayexporter/go.mod index 82a78cbe74ecb..e72bb8c08dded 100644 --- a/exporter/awsxrayexporter/go.mod +++ b/exporter/awsxrayexporter/go.mod @@ -4,6 +4,8 @@ go 1.24.0 require ( github.com/aws/aws-sdk-go-v2 v1.37.0 + github.com/aws/aws-sdk-go-v2/config v1.30.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 github.com/aws/aws-sdk-go-v2/service/xray v1.32.0 github.com/aws/smithy-go v1.22.5 github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/awsutil v0.135.0 @@ -26,7 +28,6 @@ require ( ) require ( - github.com/aws/aws-sdk-go-v2/config v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect @@ -36,7 +37,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect From 8178510f40a8a14fc1860951226d6f413e6cca6b Mon Sep 17 00:00:00 2001 From: Yaten Date: Mon, 15 Sep 2025 14:40:25 +0530 Subject: [PATCH 07/14] remove stsClient param from GetAWSConfig + update changelog entry Signed-off-by: Yaten --- ...x_support-for-role_arn-in-cloud-watch.yaml | 2 +- .../awscloudwatchlogsexporter/exporter.go | 13 +--------- exporter/awsemfexporter/emf_exporter.go | 13 +--------- exporter/awsemfexporter/go.mod | 4 +-- internal/aws/awsutil/conn.go | 8 +++--- internal/aws/awsutil/conn_test.go | 26 ++++--------------- 6 files changed, 13 insertions(+), 53 deletions(-) diff --git a/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml b/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml index 2f5831cd31a99..692a1b1387731 100644 --- a/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml +++ b/.chloggen/fix_support-for-role_arn-in-cloud-watch.yaml @@ -1,7 +1,7 @@ # Use this changelog template to create an entry for release notes. # One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' -change_type: "breaking" +change_type: "bug_fix" # The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) component: "awscloudwatchlogexporter, awsemfexporter, awsxrayexporter" diff --git a/exporter/awscloudwatchlogsexporter/exporter.go b/exporter/awscloudwatchlogsexporter/exporter.go index 48d83538a7172..418e9b946987f 100644 --- a/exporter/awscloudwatchlogsexporter/exporter.go +++ b/exporter/awscloudwatchlogsexporter/exporter.go @@ -12,9 +12,7 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" - "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/google/uuid" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" @@ -49,21 +47,12 @@ type emfMetadata struct { } func newCwLogsPusher(ctx context.Context, expConfig *Config, params exp.Settings) (*cwlExporter, error) { - var stsClient *sts.Client if expConfig == nil { return nil, errors.New("awscloudwatchlogs exporter config is nil") } - if expConfig.AWSSessionSettings.RoleARN != "" { - baseCfg, err := config.LoadDefaultConfig(ctx) - if err != nil { - return nil, fmt.Errorf("failed to load AWS base config: %w", err) - } - stsClient = sts.NewFromConfig(baseCfg) - } - expConfig.logger = params.Logger - awsConfig, err := awsutil.GetAWSConfig(ctx, params.Logger, &expConfig.AWSSessionSettings, stsClient) + awsConfig, err := awsutil.GetAWSConfig(ctx, params.Logger, &expConfig.AWSSessionSettings) if err != nil { return nil, err } diff --git a/exporter/awsemfexporter/emf_exporter.go b/exporter/awsemfexporter/emf_exporter.go index 209cc49755c15..a2c86a8c88e85 100644 --- a/exporter/awsemfexporter/emf_exporter.go +++ b/exporter/awsemfexporter/emf_exporter.go @@ -10,8 +10,6 @@ import ( "strings" "sync" - AWSConfig "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/smithy-go" "github.com/google/uuid" "go.opentelemetry.io/collector/consumer/consumererror" @@ -48,22 +46,13 @@ type emfExporter struct { // newEmfExporter creates a new exporter using exporterhelper func newEmfExporter(ctx context.Context, config *Config, set exporter.Settings) (*emfExporter, error) { - var stsClient *sts.Client if config == nil { return nil, errors.New("emf exporter config is nil") } - if config.AWSSessionSettings.RoleARN != "" { - baseCfg, err := AWSConfig.LoadDefaultConfig(ctx) - if err != nil { - return nil, fmt.Errorf("failed to load AWS base config: %w", err) - } - - stsClient = sts.NewFromConfig(baseCfg) - } config.logger = set.Logger - awsConfig, err := awsutil.GetAWSConfig(ctx, set.Logger, &config.AWSSessionSettings, stsClient) + awsConfig, err := awsutil.GetAWSConfig(ctx, set.Logger, &config.AWSSessionSettings) if err != nil { return nil, err } diff --git a/exporter/awsemfexporter/go.mod b/exporter/awsemfexporter/go.mod index 78993167d5164..5f081c354e418 100644 --- a/exporter/awsemfexporter/go.mod +++ b/exporter/awsemfexporter/go.mod @@ -3,8 +3,6 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsemf go 1.24.0 require ( - github.com/aws/aws-sdk-go-v2/config v1.30.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 github.com/aws/smithy-go v1.22.5 github.com/google/uuid v1.6.0 github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/awsutil v0.135.0 @@ -32,6 +30,7 @@ require ( require ( github.com/aws/aws-sdk-go-v2 v1.37.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect @@ -42,6 +41,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect diff --git a/internal/aws/awsutil/conn.go b/internal/aws/awsutil/conn.go index 29cb8eb1efd45..74e5c2536ee0a 100644 --- a/internal/aws/awsutil/conn.go +++ b/internal/aws/awsutil/conn.go @@ -21,10 +21,6 @@ import ( "golang.org/x/net/http2" ) -type STSClient interface { - AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) -} - // newHTTPClient returns new HTTP client instance with provided configuration. func newHTTPClient(logger *zap.Logger, maxIdle, requestTimeout int, noVerify bool, proxyAddress string, @@ -89,7 +85,7 @@ func getProxyURL(finalProxyAddress string) (*url.URL, error) { } // GetAWSConfig returns AWS config instance. -func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionSettings, stsClient STSClient) (aws.Config, error) { +func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionSettings) (aws.Config, error) { http, err := newHTTPClient(logger, settings.NumberOfWorkers, settings.RequestTimeoutSeconds, settings.NoVerifySSL, settings.ProxyAddress) if err != nil { logger.Error("unable to obtain proxy URL", zap.Error(err)) @@ -110,6 +106,8 @@ func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionS } if settings.RoleARN != "" { + stsClient := sts.NewFromConfig(cfg) + assumeRoleOpts := func(o *stscreds.AssumeRoleOptions) { if settings.ExternalID != "" { o.ExternalID = &settings.ExternalID diff --git a/internal/aws/awsutil/conn_test.go b/internal/aws/awsutil/conn_test.go index e63f643038ec6..3fdc5c9088012 100644 --- a/internal/aws/awsutil/conn_test.go +++ b/internal/aws/awsutil/conn_test.go @@ -4,35 +4,19 @@ package awsutil import ( - "context" "net/http" "testing" "time" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/sts" - "github.com/aws/aws-sdk-go-v2/service/sts/types" "github.com/stretchr/testify/assert" "go.uber.org/zap" ) -type mockSTS struct{} - -func (m *mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { - return &sts.AssumeRoleOutput{ - Credentials: &types.Credentials{ - AccessKeyId: aws.String("mockAccessKey"), - SecretAccessKey: aws.String("mockSecret"), - SessionToken: aws.String("mockToken"), - }, - }, nil -} - func TestGetAWSConfig_WithRoleARN(t *testing.T) { logger := zap.NewNop() sessionCfg := CreateDefaultSessionConfig() - cfg, err := GetAWSConfig(t.Context(), logger, &sessionCfg, &mockSTS{}) + cfg, err := GetAWSConfig(t.Context(), logger, &sessionCfg) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -55,7 +39,7 @@ func TestRegionEnv(t *testing.T) { t.Setenv("AWS_REGION", region) ctx := t.Context() - cfg, err := GetAWSConfig(ctx, logger, &sessionCfg, nil) + cfg, err := GetAWSConfig(ctx, logger, &sessionCfg) assert.NoError(t, err) assert.Equal(t, region, cfg.Region, "Region value should be fetched from environment") } @@ -67,7 +51,7 @@ func TestGetAWSConfigWithExplicitRegion(t *testing.T) { sessionCfg.Region = "eu-west-1" ctx := t.Context() - cfg, err := GetAWSConfig(ctx, logger, &sessionCfg, nil) + cfg, err := GetAWSConfig(ctx, logger, &sessionCfg) assert.NoError(t, err) assert.Equal(t, "eu-west-1", cfg.Region, "Region value should match the explicitly set region") } @@ -80,7 +64,7 @@ func TestGetAWSConfigWithCustomEndpoint(t *testing.T) { sessionCfg.Endpoint = "https://custom.endpoint.com" ctx := t.Context() - cfg, err := GetAWSConfig(ctx, logger, &sessionCfg, nil) + cfg, err := GetAWSConfig(ctx, logger, &sessionCfg) assert.NoError(t, err) assert.Equal(t, "us-west-2", cfg.Region) assert.NotNil(t, cfg.BaseEndpoint) @@ -95,7 +79,7 @@ func TestGetAWSConfigWithRetries(t *testing.T) { sessionCfg.MaxRetries = 5 ctx := t.Context() - cfg, err := GetAWSConfig(ctx, logger, &sessionCfg, nil) + cfg, err := GetAWSConfig(ctx, logger, &sessionCfg) assert.NoError(t, err) assert.Equal(t, 5, cfg.RetryMaxAttempts) } From 3c5e8c53ddeb4b821efb042b7dd6f8fb8b8e722c Mon Sep 17 00:00:00 2001 From: Yaten Date: Mon, 15 Sep 2025 14:42:02 +0530 Subject: [PATCH 08/14] update awsxray component acc to GetAWSConfig Signed-off-by: Yaten --- exporter/awsxrayexporter/awsxray.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/exporter/awsxrayexporter/awsxray.go b/exporter/awsxrayexporter/awsxray.go index 0f17896e769ff..03c81c1f80563 100644 --- a/exporter/awsxrayexporter/awsxray.go +++ b/exporter/awsxrayexporter/awsxray.go @@ -8,8 +8,6 @@ import ( "errors" "fmt" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/aws-sdk-go-v2/service/xray" "github.com/aws/smithy-go" "go.opentelemetry.io/collector/component" @@ -32,19 +30,11 @@ const ( // newTracesExporter creates an exporter.Traces that converts to an X-Ray PutTraceSegments // request and then posts the request to the configured region's X-Ray endpoint. func newTracesExporter(ctx context.Context, cfg *Config, set exporter.Settings, registry telemetry.Registry) (exporter.Traces, error) { - var stsClient *sts.Client typeLog := zap.String("type", set.ID.Type().String()) nameLog := zap.String("name", set.ID.String()) logger := set.Logger - if cfg.RoleARN != "" { - baseCfg, err := config.LoadDefaultConfig(ctx) - if err != nil { - return nil, fmt.Errorf("failed to load AWS base config: %w", err) - } - stsClient = sts.NewFromConfig(baseCfg) - } - awsConfig, err := awsutil.GetAWSConfig(ctx, logger, &cfg.AWSSessionSettings, stsClient) + awsConfig, err := awsutil.GetAWSConfig(ctx, logger, &cfg.AWSSessionSettings) if err != nil { return nil, err } From 92474f5a70f53f9c45ad3381958ba6b3868f934d Mon Sep 17 00:00:00 2001 From: Yaten Date: Mon, 15 Sep 2025 15:12:52 +0530 Subject: [PATCH 09/14] update role_arn tests Signed-off-by: Yaten --- internal/aws/awsutil/conn_test.go | 103 +++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 9 deletions(-) diff --git a/internal/aws/awsutil/conn_test.go b/internal/aws/awsutil/conn_test.go index 3fdc5c9088012..55815d82f775f 100644 --- a/internal/aws/awsutil/conn_test.go +++ b/internal/aws/awsutil/conn_test.go @@ -4,31 +4,116 @@ package awsutil import ( + "context" + "errors" "net/http" "testing" "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/stscreds" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/aws/aws-sdk-go-v2/service/sts/types" "github.com/stretchr/testify/assert" "go.uber.org/zap" ) -func TestGetAWSConfig_WithRoleARN(t *testing.T) { - logger := zap.NewNop() - sessionCfg := CreateDefaultSessionConfig() +type STSAPI interface { + AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) +} + +type mockSTS struct{} - cfg, err := GetAWSConfig(t.Context(), logger, &sessionCfg) +type ConfigLoader func(ctx context.Context, optFns ...func(*config.LoadOptions) error) (aws.Config, error) + +func GetAWSConfigHelper( + t *testing.T, + ctx context.Context, + logger *zap.Logger, + settings *AWSSessionSettings, + stsClient STSAPI, + loadCfg ConfigLoader, +) (aws.Config, error) { + t.Helper() + + http, err := newHTTPClient(logger, settings.NumberOfWorkers, settings.RequestTimeoutSeconds, settings.NoVerifySSL, settings.ProxyAddress) if err != nil { - t.Fatalf("unexpected error: %v", err) + logger.Error("unable to obtain proxy URL", zap.Error(err)) + return aws.Config{}, err + } + + var options []func(*config.LoadOptions) error + options = append(options, config.WithEC2IMDSRegion()) + + if settings.Region != "" { + options = append(options, config.WithRegion(settings.Region)) + logger.Debug("Fetch region from commandline/config file", zap.String("region", settings.Region)) } - creds, err := cfg.Credentials.Retrieve(t.Context()) + cfg, err := loadCfg(ctx, options...) if err != nil { - t.Fatalf("unexpected creds error: %v", err) + return aws.Config{}, err + } + + if settings.RoleARN != "" { + assumeRoleOpts := func(o *stscreds.AssumeRoleOptions) { + if settings.ExternalID != "" { + o.ExternalID = &settings.ExternalID + } + } + + cfg.Credentials = aws.NewCredentialsCache( + stscreds.NewAssumeRoleProvider(stsClient, settings.RoleARN, assumeRoleOpts), + ) + logger.Debug("Using AssumeRole", zap.String("role_arn", settings.RoleARN)) + } + + if cfg.Region == "" { + logger.Error("cannot fetch region variable from config file, environment variables and ec2 metadata") + return aws.Config{}, errors.New("cannot fetch region variable from config file, environment variables and ec2 metadata") + } + + cfg.HTTPClient = http + cfg.RetryMaxAttempts = settings.MaxRetries + if settings.Endpoint != "" { + cfg.BaseEndpoint = aws.String(settings.Endpoint) } - if creds.AccessKeyID != "test" { - t.Errorf("expected mockAccessKey, got %s", creds.AccessKeyID) + return cfg, nil +} + +func (m *mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { + return &sts.AssumeRoleOutput{ + Credentials: &types.Credentials{ + AccessKeyId: aws.String("mockAccessKey"), + SecretAccessKey: aws.String("mockSecret"), + SessionToken: aws.String("mockToken"), + }, + }, nil +} + +func fakeConfigLoader(ctx context.Context, optFns ...func(*config.LoadOptions) error) (aws.Config, error) { + return aws.Config{ + Region: "us-mock-1", + Credentials: aws.NewCredentialsCache( + aws.AnonymousCredentials{}), + }, nil +} + +func TestGetAWSConfigHelper_WithRoleARN(t *testing.T) { + logger := zap.NewNop() + settings := &AWSSessionSettings{ + Region: "us-mock-1", + RoleARN: "arn:aws:iam::123456789012:role/TestRole", + Endpoint: "http://localhost:4566", } + + mockSTS := &mockSTS{} + + cfg, err := GetAWSConfigHelper(t, t.Context(), logger, settings, mockSTS, fakeConfigLoader) + assert.NoError(t, err) + assert.Equal(t, "us-mock-1", cfg.Region) } // Test fetching region value from environment variable From 86430092853b71e03fb0c8ccc8b6fe26cf5b43c5 Mon Sep 17 00:00:00 2001 From: Yaten Date: Mon, 15 Sep 2025 16:48:12 +0530 Subject: [PATCH 10/14] tidy go.mod files Signed-off-by: Yaten --- exporter/awscloudwatchlogsexporter/go.mod | 4 ++-- exporter/awsxrayexporter/go.mod | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exporter/awscloudwatchlogsexporter/go.mod b/exporter/awscloudwatchlogsexporter/go.mod index 102e6405f74d5..3d20685cb8414 100644 --- a/exporter/awscloudwatchlogsexporter/go.mod +++ b/exporter/awscloudwatchlogsexporter/go.mod @@ -4,9 +4,7 @@ go 1.24.0 require ( github.com/aws/aws-sdk-go-v2 v1.37.0 - github.com/aws/aws-sdk-go-v2/config v1.30.1 github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.54.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 github.com/cenkalti/backoff/v5 v5.0.3 github.com/google/uuid v1.6.0 github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/awsutil v0.135.0 @@ -29,6 +27,7 @@ require ( require ( github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect @@ -38,6 +37,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 // indirect github.com/aws/smithy-go v1.22.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect diff --git a/exporter/awsxrayexporter/go.mod b/exporter/awsxrayexporter/go.mod index e72bb8c08dded..82a78cbe74ecb 100644 --- a/exporter/awsxrayexporter/go.mod +++ b/exporter/awsxrayexporter/go.mod @@ -4,8 +4,6 @@ go 1.24.0 require ( github.com/aws/aws-sdk-go-v2 v1.37.0 - github.com/aws/aws-sdk-go-v2/config v1.30.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 github.com/aws/aws-sdk-go-v2/service/xray v1.32.0 github.com/aws/smithy-go v1.22.5 github.com/open-telemetry/opentelemetry-collector-contrib/internal/aws/awsutil v0.135.0 @@ -28,6 +26,7 @@ require ( ) require ( + github.com/aws/aws-sdk-go-v2/config v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect @@ -37,6 +36,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect From 573ec70198ad6f1df30bd9931aff2273be060703 Mon Sep 17 00:00:00 2001 From: Yaten Date: Mon, 15 Sep 2025 17:24:17 +0530 Subject: [PATCH 11/14] attempt to fix linter errors Signed-off-by: Yaten --- internal/aws/awsutil/conn_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/aws/awsutil/conn_test.go b/internal/aws/awsutil/conn_test.go index 55815d82f775f..6d251cb11efa9 100644 --- a/internal/aws/awsutil/conn_test.go +++ b/internal/aws/awsutil/conn_test.go @@ -29,13 +29,13 @@ type ConfigLoader func(ctx context.Context, optFns ...func(*config.LoadOptions) func GetAWSConfigHelper( t *testing.T, - ctx context.Context, logger *zap.Logger, settings *AWSSessionSettings, stsClient STSAPI, loadCfg ConfigLoader, ) (aws.Config, error) { t.Helper() + ctx := t.Context() http, err := newHTTPClient(logger, settings.NumberOfWorkers, settings.RequestTimeoutSeconds, settings.NoVerifySSL, settings.ProxyAddress) if err != nil { @@ -83,7 +83,7 @@ func GetAWSConfigHelper( return cfg, nil } -func (m *mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { +func (*mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { return &sts.AssumeRoleOutput{ Credentials: &types.Credentials{ AccessKeyId: aws.String("mockAccessKey"), @@ -93,7 +93,7 @@ func (m *mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, o }, nil } -func fakeConfigLoader(ctx context.Context, optFns ...func(*config.LoadOptions) error) (aws.Config, error) { +func fakeConfigLoader(_ context.Context, optFns ...func(*config.LoadOptions) error) (aws.Config, error) { return aws.Config{ Region: "us-mock-1", Credentials: aws.NewCredentialsCache( @@ -111,7 +111,7 @@ func TestGetAWSConfigHelper_WithRoleARN(t *testing.T) { mockSTS := &mockSTS{} - cfg, err := GetAWSConfigHelper(t, t.Context(), logger, settings, mockSTS, fakeConfigLoader) + cfg, err := GetAWSConfigHelper(t, logger, settings, mockSTS, fakeConfigLoader) assert.NoError(t, err) assert.Equal(t, "us-mock-1", cfg.Region) } From bffe43b332caed88cabef3a10a45cc5942c3c41e Mon Sep 17 00:00:00 2001 From: Yaten Date: Tue, 16 Sep 2025 15:01:00 +0530 Subject: [PATCH 12/14] fix linter issues Signed-off-by: Yaten --- internal/aws/awsutil/conn_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/aws/awsutil/conn_test.go b/internal/aws/awsutil/conn_test.go index 6d251cb11efa9..44d2cf1fbc443 100644 --- a/internal/aws/awsutil/conn_test.go +++ b/internal/aws/awsutil/conn_test.go @@ -83,7 +83,7 @@ func GetAWSConfigHelper( return cfg, nil } -func (*mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { +func (*mockSTS) AssumeRole(_ context.Context, _ *sts.AssumeRoleInput, _ ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { return &sts.AssumeRoleOutput{ Credentials: &types.Credentials{ AccessKeyId: aws.String("mockAccessKey"), @@ -93,7 +93,7 @@ func (*mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, opt }, nil } -func fakeConfigLoader(_ context.Context, optFns ...func(*config.LoadOptions) error) (aws.Config, error) { +func fakeConfigLoader(_ context.Context, _ ...func(*config.LoadOptions) error) (aws.Config, error) { return aws.Config{ Region: "us-mock-1", Credentials: aws.NewCredentialsCache( From 7e720097a2acfa9ca785bf0cbbabe601d67aaa9c Mon Sep 17 00:00:00 2001 From: Yaten Date: Mon, 22 Sep 2025 21:05:14 +0530 Subject: [PATCH 13/14] refactor GetAWSConfig to add a helper func for better unit testing Signed-off-by: Yaten --- internal/aws/awsutil/conn.go | 15 ++++- internal/aws/awsutil/conn_test.go | 95 +++++-------------------------- 2 files changed, 26 insertions(+), 84 deletions(-) diff --git a/internal/aws/awsutil/conn.go b/internal/aws/awsutil/conn.go index 74e5c2536ee0a..17fe1bcb3b81a 100644 --- a/internal/aws/awsutil/conn.go +++ b/internal/aws/awsutil/conn.go @@ -84,9 +84,19 @@ func getProxyURL(finalProxyAddress string) (*url.URL, error) { return proxyURL, err } -// GetAWSConfig returns AWS config instance. func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionSettings) (aws.Config, error) { + getSTSClient := func(cfg aws.Config) stscreds.AssumeRoleAPIClient { + return sts.NewFromConfig(cfg) + } + + return getAWSConfig(ctx, logger, settings, getSTSClient) +} + +// getAWSConfig returns AWS config instance. This is separated from GetAWSConfig to allow +// easier testing with mocked AssumeRoleAPIClient. +func getAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionSettings, GetAssumeRoleAPIClient func(getAssumeRoleAPIClient aws.Config) stscreds.AssumeRoleAPIClient) (aws.Config, error) { http, err := newHTTPClient(logger, settings.NumberOfWorkers, settings.RequestTimeoutSeconds, settings.NoVerifySSL, settings.ProxyAddress) + if err != nil { logger.Error("unable to obtain proxy URL", zap.Error(err)) return aws.Config{}, err @@ -106,7 +116,7 @@ func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionS } if settings.RoleARN != "" { - stsClient := sts.NewFromConfig(cfg) + stsClient := GetAssumeRoleAPIClient(cfg) assumeRoleOpts := func(o *stscreds.AssumeRoleOptions) { if settings.ExternalID != "" { @@ -117,7 +127,6 @@ func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionS cfg.Credentials = aws.NewCredentialsCache( stscreds.NewAssumeRoleProvider(stsClient, settings.RoleARN, assumeRoleOpts), ) - logger.Debug("Using AssumeRole", zap.String("role_arn", settings.RoleARN)) } if cfg.Region == "" { diff --git a/internal/aws/awsutil/conn_test.go b/internal/aws/awsutil/conn_test.go index 44d2cf1fbc443..4d2fd392fe3b6 100644 --- a/internal/aws/awsutil/conn_test.go +++ b/internal/aws/awsutil/conn_test.go @@ -5,13 +5,11 @@ package awsutil import ( "context" - "errors" "net/http" "testing" "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/stscreds" "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/aws-sdk-go-v2/service/sts/types" @@ -19,68 +17,26 @@ import ( "go.uber.org/zap" ) -type STSAPI interface { - AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) -} - type mockSTS struct{} -type ConfigLoader func(ctx context.Context, optFns ...func(*config.LoadOptions) error) (aws.Config, error) - -func GetAWSConfigHelper( - t *testing.T, - logger *zap.Logger, - settings *AWSSessionSettings, - stsClient STSAPI, - loadCfg ConfigLoader, -) (aws.Config, error) { - t.Helper() - ctx := t.Context() - - http, err := newHTTPClient(logger, settings.NumberOfWorkers, settings.RequestTimeoutSeconds, settings.NoVerifySSL, settings.ProxyAddress) - if err != nil { - logger.Error("unable to obtain proxy URL", zap.Error(err)) - return aws.Config{}, err - } - - var options []func(*config.LoadOptions) error - options = append(options, config.WithEC2IMDSRegion()) - - if settings.Region != "" { - options = append(options, config.WithRegion(settings.Region)) - logger.Debug("Fetch region from commandline/config file", zap.String("region", settings.Region)) - } - - cfg, err := loadCfg(ctx, options...) - if err != nil { - return aws.Config{}, err - } - - if settings.RoleARN != "" { - assumeRoleOpts := func(o *stscreds.AssumeRoleOptions) { - if settings.ExternalID != "" { - o.ExternalID = &settings.ExternalID - } - } - - cfg.Credentials = aws.NewCredentialsCache( - stscreds.NewAssumeRoleProvider(stsClient, settings.RoleARN, assumeRoleOpts), - ) - logger.Debug("Using AssumeRole", zap.String("role_arn", settings.RoleARN)) +func TestGetAWSConfig(t *testing.T) { + logger := zap.NewNop() + settings := &AWSSessionSettings{ + Region: "us-mock-1", + RoleARN: "arn:aws:iam::123456789012:role/TestRole", + Endpoint: "http://localhost:4566", } - if cfg.Region == "" { - logger.Error("cannot fetch region variable from config file, environment variables and ec2 metadata") - return aws.Config{}, errors.New("cannot fetch region variable from config file, environment variables and ec2 metadata") - } + mockSTS := &mockSTS{} - cfg.HTTPClient = http - cfg.RetryMaxAttempts = settings.MaxRetries - if settings.Endpoint != "" { - cfg.BaseEndpoint = aws.String(settings.Endpoint) - } + cfg, err := getAWSConfig(t.Context(), logger, settings, func(cfg aws.Config) stscreds.AssumeRoleAPIClient { + return mockSTS + }) - return cfg, nil + assert.NoError(t, err) + assert.Equal(t, "us-mock-1", cfg.Region) + assert.Equal(t, "http://localhost:4566", *cfg.BaseEndpoint) + assert.NotNil(t, cfg.Credentials) } func (*mockSTS) AssumeRole(_ context.Context, _ *sts.AssumeRoleInput, _ ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { @@ -93,29 +49,6 @@ func (*mockSTS) AssumeRole(_ context.Context, _ *sts.AssumeRoleInput, _ ...func( }, nil } -func fakeConfigLoader(_ context.Context, _ ...func(*config.LoadOptions) error) (aws.Config, error) { - return aws.Config{ - Region: "us-mock-1", - Credentials: aws.NewCredentialsCache( - aws.AnonymousCredentials{}), - }, nil -} - -func TestGetAWSConfigHelper_WithRoleARN(t *testing.T) { - logger := zap.NewNop() - settings := &AWSSessionSettings{ - Region: "us-mock-1", - RoleARN: "arn:aws:iam::123456789012:role/TestRole", - Endpoint: "http://localhost:4566", - } - - mockSTS := &mockSTS{} - - cfg, err := GetAWSConfigHelper(t, logger, settings, mockSTS, fakeConfigLoader) - assert.NoError(t, err) - assert.Equal(t, "us-mock-1", cfg.Region) -} - // Test fetching region value from environment variable func TestRegionEnv(t *testing.T) { logger := zap.NewNop() From 591ff29069d2ac2e15d9e82054cb81a8851c8c77 Mon Sep 17 00:00:00 2001 From: Yaten Date: Mon, 22 Sep 2025 22:59:53 +0530 Subject: [PATCH 14/14] attempt to fix CI checks Signed-off-by: Yaten --- internal/aws/awsutil/conn.go | 5 ++--- internal/aws/awsutil/conn_test.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/aws/awsutil/conn.go b/internal/aws/awsutil/conn.go index 17fe1bcb3b81a..ce24949dfbc67 100644 --- a/internal/aws/awsutil/conn.go +++ b/internal/aws/awsutil/conn.go @@ -94,9 +94,8 @@ func GetAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionS // getAWSConfig returns AWS config instance. This is separated from GetAWSConfig to allow // easier testing with mocked AssumeRoleAPIClient. -func getAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionSettings, GetAssumeRoleAPIClient func(getAssumeRoleAPIClient aws.Config) stscreds.AssumeRoleAPIClient) (aws.Config, error) { +func getAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionSettings, getAssumeRoleAPIClient func(getAssumeRoleAPIClient aws.Config) stscreds.AssumeRoleAPIClient) (aws.Config, error) { http, err := newHTTPClient(logger, settings.NumberOfWorkers, settings.RequestTimeoutSeconds, settings.NoVerifySSL, settings.ProxyAddress) - if err != nil { logger.Error("unable to obtain proxy URL", zap.Error(err)) return aws.Config{}, err @@ -116,7 +115,7 @@ func getAWSConfig(ctx context.Context, logger *zap.Logger, settings *AWSSessionS } if settings.RoleARN != "" { - stsClient := GetAssumeRoleAPIClient(cfg) + stsClient := getAssumeRoleAPIClient(cfg) assumeRoleOpts := func(o *stscreds.AssumeRoleOptions) { if settings.ExternalID != "" { diff --git a/internal/aws/awsutil/conn_test.go b/internal/aws/awsutil/conn_test.go index 4d2fd392fe3b6..7a298a16b315d 100644 --- a/internal/aws/awsutil/conn_test.go +++ b/internal/aws/awsutil/conn_test.go @@ -29,7 +29,7 @@ func TestGetAWSConfig(t *testing.T) { mockSTS := &mockSTS{} - cfg, err := getAWSConfig(t.Context(), logger, settings, func(cfg aws.Config) stscreds.AssumeRoleAPIClient { + cfg, err := getAWSConfig(t.Context(), logger, settings, func(_ aws.Config) stscreds.AssumeRoleAPIClient { return mockSTS })