Skip to content

Commit 6bda906

Browse files
authored
fix(aws): Fix error when specifying AWS_CA_BUNDLE environment variable. (#5665)
1 parent 8ef5580 commit 6bda906

File tree

4 files changed

+216
-4
lines changed

4 files changed

+216
-4
lines changed

provider/aws/config.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,15 @@ package aws
1919
import (
2020
"context"
2121
"fmt"
22-
"net/http"
2322

2423
awsv2 "github.com/aws/aws-sdk-go-v2/aws"
24+
2525
"github.com/aws/aws-sdk-go-v2/aws/retry"
2626
"github.com/aws/aws-sdk-go-v2/config"
2727
stscredsv2 "github.com/aws/aws-sdk-go-v2/credentials/stscreds"
2828
"github.com/aws/aws-sdk-go-v2/service/sts"
2929
"github.com/sirupsen/logrus"
3030

31-
extdnshttp "sigs.k8s.io/external-dns/pkg/http"
32-
3331
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
3432
)
3533

@@ -84,8 +82,8 @@ func newV2Config(awsConfig AWSSessionConfig) (awsv2.Config, error) {
8482
config.WithRetryer(func() awsv2.Retryer {
8583
return retry.AddWithMaxAttempts(retry.NewStandard(), awsConfig.APIRetries)
8684
}),
87-
config.WithHTTPClient(extdnshttp.NewInstrumentedClient(&http.Client{})),
8885
config.WithSharedConfigProfile(awsConfig.Profile),
86+
config.WithAPIOptions(GetInstrumentationMiddlewares()),
8987
}
9088

9189
cfg, err := config.LoadDefaultConfig(context.Background(), defaultOpts...)

provider/aws/config_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ func Test_newV2Config(t *testing.T) {
6262
assert.Equal(t, "AKIAIOSFODNN7EXAMPLE", creds.AccessKeyID)
6363
assert.Equal(t, "topsecret", creds.SecretAccessKey)
6464
})
65+
66+
t.Run("should not error when AWS_CA_BUNDLE set", func(t *testing.T) {
67+
// setup
68+
os.Setenv("AWS_CA_BUNDLE", "../../internal/testresources/ca.pem")
69+
defer os.Unsetenv("AWS_CA_BUNDLE")
70+
71+
// when
72+
_, err := newV2Config(AWSSessionConfig{})
73+
require.NoError(t, err)
74+
75+
// then
76+
assert.NoError(t, err)
77+
})
6578
}
6679

6780
func prepareCredentialsFile(t *testing.T) (*os.File, error) {
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package aws
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
"github.com/aws/smithy-go/middleware"
25+
smithyhttp "github.com/aws/smithy-go/transport/http"
26+
27+
extdnshttp "sigs.k8s.io/external-dns/pkg/http"
28+
"sigs.k8s.io/external-dns/pkg/metrics"
29+
)
30+
31+
type requestMetrics struct {
32+
StartTime time.Time
33+
}
34+
35+
type requestMetricsKey struct{}
36+
37+
func getRequestMetric(ctx context.Context) requestMetrics {
38+
requestMetrics, _ := middleware.GetStackValue(ctx, requestMetricsKey{}).(requestMetrics)
39+
return requestMetrics
40+
}
41+
42+
func setRequestMetric(ctx context.Context, requestMetrics requestMetrics) context.Context {
43+
return middleware.WithStackValue(ctx, requestMetricsKey{}, requestMetrics)
44+
}
45+
46+
var initializeTimedOperationMiddleware = middleware.InitializeMiddlewareFunc("timedOperation", func(
47+
ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler,
48+
) (middleware.InitializeOutput, middleware.Metadata, error) {
49+
requestMetrics := requestMetrics{}
50+
requestMetrics.StartTime = time.Now()
51+
ctx = setRequestMetric(ctx, requestMetrics)
52+
53+
return next.HandleInitialize(ctx, in)
54+
})
55+
56+
var extractAWSRequestParameters = middleware.DeserializeMiddlewareFunc("extractAWSRequestParameters", func(
57+
ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler,
58+
) (middleware.DeserializeOutput, middleware.Metadata, error) {
59+
// Call the next middleware first to get the response
60+
out, metadata, err := next.HandleDeserialize(ctx, in)
61+
62+
requestMetrics := getRequestMetric(ctx)
63+
64+
if req, ok := in.Request.(*smithyhttp.Request); ok && req != nil {
65+
extdnshttp.RequestDurationLabels.WithOptions(
66+
metrics.WithLabel("scheme", req.URL.Scheme),
67+
metrics.WithLabel("host", req.URL.Host),
68+
metrics.WithLabel("path", metrics.PathProcessor(req.URL.Path)),
69+
metrics.WithLabel("method", req.Method),
70+
)
71+
}
72+
73+
// Try to access HTTP response and status code
74+
if resp, ok := out.RawResponse.(*smithyhttp.Response); ok && resp != nil {
75+
extdnshttp.RequestDurationLabels.WithOptions(
76+
metrics.WithLabel("status", fmt.Sprintf("%d", resp.StatusCode)),
77+
)
78+
}
79+
80+
extdnshttp.RequestDurationMetric.SetWithLabels(time.Since(requestMetrics.StartTime).Seconds(), extdnshttp.RequestDurationLabels)
81+
82+
return out, metadata, err
83+
})
84+
85+
func GetInstrumentationMiddlewares() []func(*middleware.Stack) error {
86+
return []func(s *middleware.Stack) error{
87+
func(s *middleware.Stack) error {
88+
if err := s.Initialize.Add(initializeTimedOperationMiddleware, middleware.Before); err != nil {
89+
return fmt.Errorf("error adding timedOperationMiddleware: %w", err)
90+
}
91+
92+
if err := s.Deserialize.Add(extractAWSRequestParameters, middleware.After); err != nil {
93+
return fmt.Errorf("error adding extractAWSRequestParameters: %w", err)
94+
}
95+
96+
return nil
97+
},
98+
}
99+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package aws
18+
19+
import (
20+
"context"
21+
"net/http"
22+
"net/url"
23+
"testing"
24+
"time"
25+
26+
"github.com/aws/smithy-go/middleware"
27+
"github.com/stretchr/testify/assert"
28+
"github.com/stretchr/testify/require"
29+
30+
smithyhttp "github.com/aws/smithy-go/transport/http"
31+
)
32+
33+
func Test_GetInstrumentationMiddlewares(t *testing.T) {
34+
t.Run("adds expected middlewares", func(t *testing.T) {
35+
stack := middleware.NewStack("test-stack", nil)
36+
37+
for _, mw := range GetInstrumentationMiddlewares() {
38+
err := mw(stack)
39+
require.NoError(t, err)
40+
}
41+
42+
// Check Initialize stage
43+
timedOperationMiddleware, found := stack.Initialize.Get("timedOperation")
44+
assert.True(t, found, "timedOperation middleware should be present in Initialize stage")
45+
assert.NotNil(t, timedOperationMiddleware)
46+
47+
// Check Deserialize stage
48+
extractAWSRequestParametersMiddleware, found := stack.Deserialize.Get("extractAWSRequestParameters")
49+
assert.True(t, found, "extractAWSRequestParameters middleware should be present in Deserialize stage")
50+
assert.NotNil(t, extractAWSRequestParametersMiddleware)
51+
})
52+
}
53+
54+
type MockInitializeHandler struct {
55+
CapturedContext context.Context
56+
}
57+
58+
func (mock *MockInitializeHandler) HandleInitialize(ctx context.Context, in middleware.InitializeInput) (middleware.InitializeOutput, middleware.Metadata, error) {
59+
mock.CapturedContext = ctx
60+
61+
return middleware.InitializeOutput{}, middleware.Metadata{}, nil
62+
}
63+
64+
func Test_InitializedTimedOperationMiddleware(t *testing.T) {
65+
testContext := context.Background()
66+
mockInitializeHandler := &MockInitializeHandler{}
67+
68+
_, _, err := initializeTimedOperationMiddleware.HandleInitialize(testContext, middleware.InitializeInput{}, mockInitializeHandler)
69+
require.NoError(t, err)
70+
71+
requestMetrics := middleware.GetStackValue(mockInitializeHandler.CapturedContext, requestMetricsKey{}).(requestMetrics)
72+
assert.NotNil(t, requestMetrics.StartTime)
73+
}
74+
75+
type MockDeserializeHandler struct {
76+
}
77+
78+
func (mock *MockDeserializeHandler) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput) (middleware.DeserializeOutput, middleware.Metadata, error) {
79+
return middleware.DeserializeOutput{}, middleware.Metadata{}, nil
80+
}
81+
82+
func Test_ExtractAWSRequestParameters(t *testing.T) {
83+
testContext := context.Background()
84+
middleware.WithStackValue(testContext, requestMetricsKey{}, requestMetrics{StartTime: time.Now()})
85+
86+
mockDeserializeHandler := &MockDeserializeHandler{}
87+
88+
deserializeInput := middleware.DeserializeInput{
89+
Request: &smithyhttp.Request{
90+
Request: &http.Request{
91+
Method: http.MethodGet,
92+
URL: &url.URL{
93+
Host: "example.com",
94+
Scheme: "HTTPS",
95+
Path: "/testPath",
96+
},
97+
},
98+
},
99+
}
100+
_, _, err := extractAWSRequestParameters.HandleDeserialize(testContext, deserializeInput, mockDeserializeHandler)
101+
require.NoError(t, err)
102+
}

0 commit comments

Comments
 (0)