Skip to content

Commit 5ee8d10

Browse files
committed
Add utility method for parsing dual-stack and fips URIs
1 parent fcb7bc4 commit 5ee8d10

File tree

10 files changed

+375
-39
lines changed

10 files changed

+375
-39
lines changed

agent/dockerclient/dockerapi/docker_client.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -655,13 +655,13 @@ func (dg *dockerGoClient) InspectImage(image string) (*types.ImageInspect, error
655655
func (dg *dockerGoClient) getAuthdata(image string, authData *apicontainer.RegistryAuthenticationData) (registry.AuthConfig, error) {
656656

657657
if authData == nil {
658-
return dg.auth.GetAuthconfig(image, nil)
658+
return dg.auth.GetAuthconfig(image, nil, nil)
659659
}
660660

661661
switch authData.Type {
662662
case apicontainer.AuthTypeECR:
663-
provider := dockerauth.NewECRAuthProvider(dg.ecrClientFactory, dg.ecrTokenCache)
664-
authConfig, err := provider.GetAuthconfig(image, authData)
663+
provider := dockerauth.NewECRAuthProvider(dg.ecrClientFactory, dg.ecrTokenCache, dg.config.InstanceIPCompatibility)
664+
authConfig, err := provider.GetAuthconfig(image, authData, &dg.config.InstanceIPCompatibility)
665665
if err != nil {
666666
err = redactEcrUrls(image, err)
667667
return authConfig, CannotPullECRContainerError{err}
@@ -672,7 +672,7 @@ func (dg *dockerGoClient) getAuthdata(image string, authData *apicontainer.Regis
672672
return authData.ASMAuthData.GetDockerAuthConfig(), nil
673673

674674
default:
675-
return dg.auth.GetAuthconfig(image, nil)
675+
return dg.auth.GetAuthconfig(image, nil, nil)
676676
}
677677
}
678678

agent/dockerclient/dockerapi/docker_client_test.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ func TestPullImageECRSuccess(t *testing.T) {
280280
RegistryAuth: "eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwicGFzc3dvcmQiOiJwYXNzd29yZCIsInNlcnZlcmFkZHJlc3MiOiJodHRwczovL3JlZ2lzdHJ5LmVuZHBvaW50In0K",
281281
}
282282

283-
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData).Return(ecrClient, nil)
283+
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData, &client.config.InstanceIPCompatibility).Return(ecrClient, nil)
284284
ecrClient.EXPECT().GetAuthorizationToken(registryID).Return(
285285
&ecr_types.AuthorizationData{
286286
ProxyEndpoint: aws.String("https://" + imageEndpoint),
@@ -448,19 +448,20 @@ func TestPullImageManifest(t *testing.T) {
448448

449449
client, err := NewDockerGoClient(sdkFactory, defaultTestConfig(), context.Background())
450450
require.NoError(t, err)
451+
goClient, _ := client.(*dockerGoClient)
451452

452453
if tc.setSDKFactoryExpectations != nil {
453454
tc.setSDKFactoryExpectations(sdkFactory, ctrl)
454455
}
455456

456457
ecrClientFactory := mock_ecr.NewMockECRFactory(ctrl)
457458
ecrClient := mock_ecr.NewMockECRClient(ctrl)
458-
client.(*dockerGoClient).ecrClientFactory = ecrClientFactory
459-
client.(*dockerGoClient).manifestPullBackoff = retry.NewExponentialBackoff(
459+
goClient.ecrClientFactory = ecrClientFactory
460+
goClient.manifestPullBackoff = retry.NewExponentialBackoff(
460461
1*time.Nanosecond, 1*time.Nanosecond, 1, 1)
461462

462463
if tc.setECRClientExpectations != nil {
463-
ecrClientFactory.EXPECT().GetClient(tc.authData.ECRAuthData).Return(ecrClient, nil)
464+
ecrClientFactory.EXPECT().GetClient(tc.authData.ECRAuthData, &goClient.config.InstanceIPCompatibility).Return(ecrClient, nil)
464465
tc.setECRClientExpectations(ecrClient)
465466
}
466467

@@ -513,7 +514,7 @@ func TestPullImageECRAuthFail(t *testing.T) {
513514
image := imageEndpoint + "/myimage:tag"
514515

515516
// no retries for this error
516-
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData).Return(ecrClient, nil)
517+
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData, &goClient.config.InstanceIPCompatibility).Return(ecrClient, nil)
517518
ecrClient.EXPECT().GetAuthorizationToken(gomock.Any()).Return(nil, errors.New("test error"))
518519

519520
metadata := client.PullImage(ctx, image, authData, defaultTestConfig().ImagePullTimeout)
@@ -1769,7 +1770,7 @@ func TestECRAuthCacheWithoutExecutionRole(t *testing.T) {
17691770
username := "username"
17701771
password := "password"
17711772

1772-
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData).Return(ecrClient, nil).Times(1)
1773+
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData, &client.config.InstanceIPCompatibility).Return(ecrClient, nil).Times(1)
17731774
ecrClient.EXPECT().GetAuthorizationToken(registryID).Return(
17741775
&ecr_types.AuthorizationData{
17751776
ProxyEndpoint: aws.String("https://" + imageEndpoint),
@@ -1825,7 +1826,7 @@ func TestECRAuthCacheForDifferentRegistry(t *testing.T) {
18251826
username := "username"
18261827
password := "password"
18271828

1828-
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData).Return(ecrClient, nil).Times(1)
1829+
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData, &client.config.InstanceIPCompatibility).Return(ecrClient, nil).Times(1)
18291830
ecrClient.EXPECT().GetAuthorizationToken(registryID).Return(
18301831
&ecr_types.AuthorizationData{
18311832
ProxyEndpoint: aws.String("https://" + imageEndpoint),
@@ -1844,7 +1845,7 @@ func TestECRAuthCacheForDifferentRegistry(t *testing.T) {
18441845

18451846
// Pull from the different registry should expect ECR client call
18461847
authData.ECRAuthData.RegistryID = "another"
1847-
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData).Return(ecrClient, nil).Times(1)
1848+
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData, &client.config.InstanceIPCompatibility).Return(ecrClient, nil).Times(1)
18481849
ecrClient.EXPECT().GetAuthorizationToken("another").Return(
18491850
&ecr_types.AuthorizationData{
18501851
ProxyEndpoint: aws.String("https://" + imageEndpoint),
@@ -1884,7 +1885,7 @@ func TestECRAuthCacheWithSameExecutionRole(t *testing.T) {
18841885
username := "username"
18851886
password := "password"
18861887

1887-
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData).Return(ecrClient, nil).Times(1)
1888+
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData, &client.config.InstanceIPCompatibility).Return(ecrClient, nil).Times(1)
18881889
ecrClient.EXPECT().GetAuthorizationToken(registryID).Return(
18891890
&ecr_types.AuthorizationData{
18901891
ProxyEndpoint: aws.String("https://" + imageEndpoint),
@@ -1939,7 +1940,7 @@ func TestECRAuthCacheWithDifferentExecutionRole(t *testing.T) {
19391940
username := "username"
19401941
password := "password"
19411942

1942-
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData).Return(ecrClient, nil).Times(1)
1943+
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData, &client.config.InstanceIPCompatibility).Return(ecrClient, nil).Times(1)
19431944
ecrClient.EXPECT().GetAuthorizationToken(registryID).Return(
19441945
&ecr_types.AuthorizationData{
19451946
ProxyEndpoint: aws.String("https://" + imageEndpoint),
@@ -1960,7 +1961,7 @@ func TestECRAuthCacheWithDifferentExecutionRole(t *testing.T) {
19601961
authData.ECRAuthData.SetPullCredentials(credentials.IAMRoleCredentials{
19611962
RoleArn: "executionRole2",
19621963
})
1963-
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData).Return(ecrClient, nil).Times(1)
1964+
ecrClientFactory.EXPECT().GetClient(authData.ECRAuthData, &client.config.InstanceIPCompatibility).Return(ecrClient, nil).Times(1)
19641965
ecrClient.EXPECT().GetAuthorizationToken(registryID).Return(
19651966
&ecr_types.AuthorizationData{
19661967
ProxyEndpoint: aws.String("https://" + imageEndpoint),

agent/dockerclient/dockerauth/dockerauth.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import (
2121
"strings"
2222

2323
apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container"
24+
"github.com/aws/amazon-ecs-agent/agent/config/ipcompatibility"
2425
"github.com/aws/amazon-ecs-agent/agent/utils"
25-
"github.com/docker/docker/api/types/registry"
2626

2727
"github.com/cihub/seelog"
28+
"github.com/docker/docker/api/types/registry"
2829
)
2930

3031
func NewDockerAuthProvider(authType string, authData json.RawMessage) DockerAuthProvider {
@@ -47,7 +48,7 @@ type dockercfgConfigEntry struct {
4748
type dockercfgData map[string]dockercfgConfigEntry
4849

4950
// GetAuthconfig retrieves the correct auth configuration for the given repository
50-
func (authProvider *dockerAuthProvider) GetAuthconfig(image string, registryAuthData *apicontainer.RegistryAuthenticationData) (registry.AuthConfig, error) {
51+
func (authProvider *dockerAuthProvider) GetAuthconfig(image string, registryAuthData *apicontainer.RegistryAuthenticationData, ipCompatibility *ipcompatibility.IPCompatibility) (registry.AuthConfig, error) {
5152
// Ignore 'tag', not used in auth determination
5253
repository, _ := utils.ParseRepositoryTag(image)
5354
authDataMap := authProvider.authMap

agent/dockerclient/dockerauth/ecr.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"time"
2121

2222
apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container"
23+
"github.com/aws/amazon-ecs-agent/agent/config/ipcompatibility"
2324
"github.com/aws/amazon-ecs-agent/agent/ecr"
2425
"github.com/aws/amazon-ecs-agent/ecs-agent/async"
2526
"github.com/aws/amazon-ecs-agent/ecs-agent/credentials"
@@ -62,7 +63,7 @@ func (key *cacheKey) String() string {
6263

6364
// NewECRAuthProvider returns a DockerAuthProvider that can handle retrieve
6465
// credentials for pulling from Amazon EC2 Container Registry
65-
func NewECRAuthProvider(ecrFactory ecr.ECRFactory, cache async.Cache) DockerAuthProvider {
66+
func NewECRAuthProvider(ecrFactory ecr.ECRFactory, cache async.Cache, ipCompatibility ipcompatibility.IPCompatibility) DockerAuthProvider {
6667
return &ecrAuthProvider{
6768
tokenCache: cache,
6869
factory: ecrFactory,
@@ -71,7 +72,7 @@ func NewECRAuthProvider(ecrFactory ecr.ECRFactory, cache async.Cache) DockerAuth
7172

7273
// GetAuthconfig retrieves the correct auth configuration for the given repository
7374
func (authProvider *ecrAuthProvider) GetAuthconfig(image string,
74-
registryAuthData *apicontainer.RegistryAuthenticationData) (registry.AuthConfig, error) {
75+
registryAuthData *apicontainer.RegistryAuthenticationData, ipCompatibility *ipcompatibility.IPCompatibility) (registry.AuthConfig, error) {
7576

7677
if registryAuthData == nil {
7778
return registry.AuthConfig{}, fmt.Errorf("dockerauth: missing container's registry auth data")
@@ -106,7 +107,7 @@ func (authProvider *ecrAuthProvider) GetAuthconfig(image string,
106107
}
107108

108109
// Get the auth config from ECR
109-
return authProvider.getAuthConfigFromECR(image, key, authData)
110+
return authProvider.getAuthConfigFromECR(image, key, authData, ipCompatibility)
110111
}
111112

112113
// getAuthconfigFromCache retrieves the token from cache
@@ -139,9 +140,9 @@ func (authProvider *ecrAuthProvider) getAuthConfigFromCache(key cacheKey) *regis
139140
}
140141

141142
// getAuthConfigFromECR calls the ECR API to get docker auth config
142-
func (authProvider *ecrAuthProvider) getAuthConfigFromECR(image string, key cacheKey, authData *apicontainer.ECRAuthData) (registry.AuthConfig, error) {
143+
func (authProvider *ecrAuthProvider) getAuthConfigFromECR(image string, key cacheKey, authData *apicontainer.ECRAuthData, ipCompatibility *ipcompatibility.IPCompatibility) (registry.AuthConfig, error) {
143144
// Create ECR client to get the token
144-
client, err := authProvider.factory.GetClient(authData)
145+
client, err := authProvider.factory.GetClient(authData, ipCompatibility)
145146
if err != nil {
146147
return registry.AuthConfig{}, err
147148
}
@@ -164,7 +165,6 @@ func (authProvider *ecrAuthProvider) getAuthConfigFromECR(image string, key cach
164165

165166
// Verify the auth data has the correct format for ECR
166167
if ecrAuthData.ProxyEndpoint != nil &&
167-
strings.HasPrefix(proxyEndpointScheme+image, aws.ToString(ecrAuthData.ProxyEndpoint)) &&
168168
ecrAuthData.AuthorizationToken != nil {
169169

170170
// Cache the new token

agent/dockerclient/dockerauth/interface.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ package dockerauth
1717

1818
import (
1919
apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container"
20+
"github.com/aws/amazon-ecs-agent/agent/config/ipcompatibility"
21+
2022
"github.com/docker/docker/api/types/registry"
2123
)
2224

2325
// DockerAuthProvider is something that can give the auth information for a given docker image
2426
type DockerAuthProvider interface {
25-
GetAuthconfig(image string, registryAuthData *apicontainer.RegistryAuthenticationData) (registry.AuthConfig, error)
27+
GetAuthconfig(image string, registryAuthData *apicontainer.RegistryAuthenticationData, ipCompatibility *ipcompatibility.IPCompatibility) (registry.AuthConfig, error)
2628
}

agent/ecr/factory.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container"
2424
"github.com/aws/amazon-ecs-agent/agent/config"
25+
"github.com/aws/amazon-ecs-agent/agent/config/ipcompatibility"
2526
agentversion "github.com/aws/amazon-ecs-agent/agent/version"
2627
"github.com/aws/amazon-ecs-agent/ecs-agent/credentials"
2728
"github.com/aws/amazon-ecs-agent/ecs-agent/credentials/providers"
@@ -36,7 +37,7 @@ import (
3637

3738
// ECRFactory defines the interface to produce an ECR SDK client
3839
type ECRFactory interface {
39-
GetClient(*apicontainer.ECRAuthData) (ECRClient, error)
40+
GetClient(*apicontainer.ECRAuthData, *ipcompatibility.IPCompatibility) (ECRClient, error)
4041
}
4142

4243
type ecrFactory struct {
@@ -55,8 +56,8 @@ func NewECRFactory(acceptInsecureCert bool) ECRFactory {
5556
}
5657

5758
// GetClient creates the ECR SDK client based on the authdata
58-
func (factory *ecrFactory) GetClient(authData *apicontainer.ECRAuthData) (ECRClient, error) {
59-
clientConfig, err := getClientConfig(factory.httpClient, authData)
59+
func (factory *ecrFactory) GetClient(authData *apicontainer.ECRAuthData, ipCompatibility *ipcompatibility.IPCompatibility) (ECRClient, error) {
60+
clientConfig, err := getClientConfig(factory.httpClient, authData, ipCompatibility)
6061
if err != nil {
6162
return &ecrClient{}, err
6263
}
@@ -65,14 +66,16 @@ func (factory *ecrFactory) GetClient(authData *apicontainer.ECRAuthData) (ECRCli
6566
}
6667

6768
// getClientConfig returns the config for the ecr client based on authData
68-
func getClientConfig(httpClient *http.Client, authData *apicontainer.ECRAuthData) (*aws.Config, error) {
69+
func getClientConfig(httpClient *http.Client, authData *apicontainer.ECRAuthData, ipCompatibility *ipcompatibility.IPCompatibility) (*aws.Config, error) {
6970
opts := []func(*awsconfig.LoadOptions) error{
7071
awsconfig.WithRegion(authData.Region),
7172
awsconfig.WithHTTPClient(httpClient),
7273
}
7374

7475
if authData.EndpointOverride != "" {
7576
opts = append(opts, awsconfig.WithBaseEndpoint(utils.AddScheme(authData.EndpointOverride)))
77+
} else if ipCompatibility != nil && ipCompatibility.IsIPv6Only() {
78+
opts = append(opts, awsconfig.WithUseDualStackEndpoint(aws.DualStackEndpointStateEnabled))
7679
}
7780

7881
var credentialsOpt awsconfig.LoadOptionsFunc

agent/ecr/factory_test.go

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,70 @@
1717
package ecr
1818

1919
import (
20+
"fmt"
2021
"testing"
2122

2223
apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container"
24+
"github.com/aws/amazon-ecs-agent/agent/config/ipcompatibility"
2325

2426
"github.com/stretchr/testify/assert"
2527
)
2628

29+
const (
30+
endpointOverride string = "api.ecr.us-west-2.amazonaws.com"
31+
)
32+
2733
func TestGetClientConfigEndpointOverride(t *testing.T) {
28-
testAuthData := &apicontainer.ECRAuthData{
29-
EndpointOverride: "api.ecr.us-west-2.amazonaws.com",
30-
Region: "us-west-2",
31-
UseExecutionRole: false,
34+
cases := []struct {
35+
Name string
36+
EndpointOverride string
37+
IPCompatibility ipcompatibility.IPCompatibility
38+
}{
39+
{
40+
Name: "IPv4 with endpoint override",
41+
EndpointOverride: endpointOverride,
42+
IPCompatibility: ipcompatibility.NewIPv4OnlyCompatibility(),
43+
},
44+
{
45+
Name: "IPv4 without endpoint override",
46+
EndpointOverride: "",
47+
IPCompatibility: ipcompatibility.NewIPv4OnlyCompatibility(),
48+
},
49+
{
50+
Name: "IPv6 with endpoint override",
51+
EndpointOverride: endpointOverride,
52+
IPCompatibility: ipcompatibility.NewIPv6OnlyCompatibility(),
53+
},
54+
{
55+
Name: "IPv6 without endpoint override",
56+
EndpointOverride: "",
57+
IPCompatibility: ipcompatibility.NewIPv6OnlyCompatibility(),
58+
},
59+
{
60+
Name: "DualStack with endpoint override",
61+
EndpointOverride: endpointOverride,
62+
IPCompatibility: ipcompatibility.NewIPCompatibility(true, true),
63+
},
64+
{
65+
Name: "DualStack without endpoint override",
66+
EndpointOverride: "",
67+
IPCompatibility: ipcompatibility.NewIPCompatibility(true, true),
68+
},
3269
}
3370

34-
cfg, err := getClientConfig(nil, testAuthData)
71+
for _, test := range cases {
72+
t.Run(test.Name, func(t *testing.T) {
73+
testAuthData := &apicontainer.ECRAuthData{
74+
Region: "us-west-2",
75+
EndpointOverride: test.EndpointOverride,
76+
UseExecutionRole: false,
77+
}
78+
cfg, err := getClientConfig(nil, testAuthData, &test.IPCompatibility)
3579

36-
assert.Nil(t, err)
37-
assert.Equal(t, "https://"+testAuthData.EndpointOverride, *cfg.BaseEndpoint)
80+
assert.Nil(t, err)
81+
if test.EndpointOverride != "" {
82+
assert.Equal(t, fmt.Sprintf("https://%s", test.EndpointOverride), *cfg.BaseEndpoint)
83+
}
84+
})
85+
}
3886
}

agent/ecr/mocks/ecr_mocks.go

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)