Skip to content

Commit f2d322c

Browse files
committed
Disable dualstack when it is not available
In some AWS regions S3 does not have a dual-stack endpoint. In these regions the operator should fallback to the standard endpoint.
1 parent 6facac2 commit f2d322c

File tree

2 files changed

+131
-2
lines changed

2 files changed

+131
-2
lines changed

pkg/storage/s3/s3.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ func (er *endpointsResolver) EndpointFor(service, region string, opts ...func(*e
8383
return endpoints.DefaultResolver().EndpointFor(service, region, opts...)
8484
}
8585

86+
func isUnknownEndpointError(err error) bool {
87+
_, ok := err.(endpoints.UnknownEndpointError)
88+
return ok
89+
}
90+
91+
func regionHasDualStackS3(region string) (bool, error) {
92+
_, err := endpoints.DefaultResolver().EndpointFor("s3", region, endpoints.UseDualStackEndpointOption)
93+
if isUnknownEndpointError(err) {
94+
return false, nil
95+
}
96+
if err != nil {
97+
return false, err
98+
}
99+
return true, nil
100+
}
101+
86102
type driver struct {
87103
Context context.Context
88104
Config *imageregistryv1.ImageRegistryConfigStorageS3
@@ -224,6 +240,18 @@ func (d *driver) getCABundle() (string, error) {
224240
}
225241
}
226242

243+
// useDualStack returns true if the driver should use dual-stack endpoints
244+
func (d *driver) useDualStack() (bool, error) {
245+
if d.Config.RegionEndpoint != "" {
246+
return true, nil
247+
}
248+
ok, err := regionHasDualStackS3(d.Config.Region)
249+
if err != nil {
250+
return false, fmt.Errorf("failed to determine if region %s has dual stack S3: %w", d.Config.Region, err)
251+
}
252+
return ok, nil
253+
}
254+
227255
// getS3Service returns a client that allows us to interact
228256
// with the aws S3 service
229257
func (d *driver) getS3Service() (*s3.S3, error) {
@@ -290,7 +318,14 @@ func (d *driver) getS3Service() (*s3.S3, error) {
290318
awsOptions.Config.HTTPClient.Transport = d.roundTripper
291319
}
292320

293-
awsOptions.Config.WithUseDualStack(true)
321+
useDualStack, err := d.useDualStack()
322+
if err != nil {
323+
return nil, err
324+
}
325+
if useDualStack {
326+
awsOptions.Config.WithUseDualStack(true)
327+
}
328+
294329
if d.Config.RegionEndpoint != "" {
295330
if !d.Config.VirtualHostedStyle {
296331
awsOptions.Config.WithS3ForcePathStyle(true)
@@ -358,10 +393,17 @@ func (d *driver) ConfigEnv() (envs envvar.List, err error) {
358393
envvar.EnvVar{Name: "REGISTRY_STORAGE_S3_REGION", Value: d.Config.Region},
359394
envvar.EnvVar{Name: "REGISTRY_STORAGE_S3_ENCRYPT", Value: d.Config.Encrypt},
360395
envvar.EnvVar{Name: "REGISTRY_STORAGE_S3_VIRTUALHOSTEDSTYLE", Value: d.Config.VirtualHostedStyle},
361-
envvar.EnvVar{Name: "REGISTRY_STORAGE_S3_USEDUALSTACK", Value: true},
362396
envvar.EnvVar{Name: "REGISTRY_STORAGE_S3_CREDENTIALSCONFIGPATH", Value: filepath.Join(imageRegistrySecretMountpoint, imageRegistrySecretDataKey)},
363397
)
364398

399+
useDualStack, err := d.useDualStack()
400+
if err != nil {
401+
return nil, err
402+
}
403+
if useDualStack {
404+
envs = append(envs, envvar.EnvVar{Name: "REGISTRY_STORAGE_S3_USEDUALSTACK", Value: true})
405+
}
406+
365407
if d.Config.CloudFront != nil {
366408
// Use structs to make ordering deterministic
367409
type cloudFrontOptions struct {

pkg/storage/s3/s3_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"testing"
1313

1414
"github.com/aws/aws-sdk-go/aws"
15+
"github.com/aws/aws-sdk-go/aws/endpoints"
1516
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
1617
"github.com/aws/aws-sdk-go/service/s3"
1718
"github.com/google/go-cmp/cmp"
@@ -27,6 +28,92 @@ import (
2728
"github.com/openshift/cluster-image-registry-operator/pkg/envvar"
2829
)
2930

31+
func TestEndpointsResolver(t *testing.T) {
32+
testCases := []struct {
33+
region string
34+
useDualStack bool
35+
endpoint string
36+
}{
37+
{
38+
region: "us-east-1",
39+
endpoint: "https://s3.amazonaws.com",
40+
},
41+
{
42+
region: "us-east-1",
43+
useDualStack: true,
44+
endpoint: "https://s3.dualstack.us-east-1.amazonaws.com",
45+
},
46+
{
47+
region: "us-gov-east-1",
48+
endpoint: "https://s3.us-gov-east-1.amazonaws.com",
49+
},
50+
{
51+
region: "us-gov-east-1",
52+
useDualStack: true,
53+
endpoint: "https://s3.dualstack.us-gov-east-1.amazonaws.com",
54+
},
55+
{
56+
region: "us-iso-east-1",
57+
endpoint: "https://s3.us-iso-east-1.c2s.ic.gov",
58+
},
59+
{
60+
region: "us-iso-east-1",
61+
useDualStack: true,
62+
endpoint: "",
63+
},
64+
}
65+
for _, tc := range testCases {
66+
t.Run(tc.region, func(t *testing.T) {
67+
er := newEndpointsResolver(tc.region, "", nil)
68+
var opts []func(*endpoints.Options)
69+
if tc.useDualStack {
70+
opts = append(opts, endpoints.UseDualStackEndpointOption)
71+
}
72+
ep, err := er.EndpointFor("s3", tc.region, opts...)
73+
if isUnknownEndpointError(err) && tc.endpoint == "" {
74+
return // the error is expected
75+
}
76+
if err != nil {
77+
t.Fatal(err)
78+
}
79+
if ep.URL != tc.endpoint {
80+
t.Errorf("got %s, want %s", ep.URL, tc.endpoint)
81+
}
82+
})
83+
}
84+
}
85+
86+
func TestRegionS3DualStack(t *testing.T) {
87+
testCases := []struct {
88+
region string
89+
want bool
90+
}{
91+
{
92+
region: "us-east-1",
93+
want: true,
94+
},
95+
{
96+
region: "us-gov-east-1",
97+
want: true,
98+
},
99+
{
100+
region: "us-iso-east-1",
101+
want: false,
102+
},
103+
}
104+
for _, tc := range testCases {
105+
t.Run(tc.region, func(t *testing.T) {
106+
got, err := regionHasDualStackS3(tc.region)
107+
if err != nil {
108+
t.Fatal(err)
109+
}
110+
if got != tc.want {
111+
t.Errorf("got %t, want %t", got, tc.want)
112+
}
113+
})
114+
}
115+
}
116+
30117
func TestGetConfig(t *testing.T) {
31118
testBuilder := cirofake.NewFixturesBuilder()
32119
testBuilder.AddInfraConfig(&configv1.Infrastructure{

0 commit comments

Comments
 (0)