Skip to content

Commit e8e4f84

Browse files
authored
fix: package credentials to support legacy auth keys in docker config (#617)
Fix: #616 --------- Signed-off-by: Lixia (Sylvia) Lei <[email protected]>
1 parent 86176e8 commit e8e4f84

File tree

3 files changed

+187
-1
lines changed

3 files changed

+187
-1
lines changed

registry/remote/credentials/internal/config/config.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,20 @@ func (cfg *Config) GetCredential(serverAddress string) (auth.Credential, error)
162162

163163
authCfgBytes, ok := cfg.authsCache[serverAddress]
164164
if !ok {
165-
return auth.EmptyCredential, nil
165+
// NOTE: the auth key for the server address may have been stored with
166+
// a http/https prefix in legacy config files, e.g. "registry.example.com"
167+
// can be stored as "https://registry.example.com/".
168+
var matched bool
169+
for addr, auth := range cfg.authsCache {
170+
if toHostname(addr) == serverAddress {
171+
matched = true
172+
authCfgBytes = auth
173+
break
174+
}
175+
}
176+
if !matched {
177+
return auth.EmptyCredential, nil
178+
}
166179
}
167180
var authCfg AuthConfig
168181
if err := json.Unmarshal(authCfgBytes, &authCfg); err != nil {
@@ -300,3 +313,15 @@ func decodeAuth(authStr string) (username string, password string, err error) {
300313
}
301314
return username, password, nil
302315
}
316+
317+
// toHostname normalizes a server address to just its hostname, removing
318+
// the scheme and the path parts.
319+
// It is used to match keys in the auths map, which may be either stored as
320+
// hostname or as hostname including scheme (in legacy docker config files).
321+
// Reference: https://github.com/docker/cli/blob/v24.0.6/cli/config/credentials/file_store.go#L71
322+
func toHostname(addr string) string {
323+
addr = strings.TrimPrefix(addr, "http://")
324+
addr = strings.TrimPrefix(addr, "https://")
325+
addr, _, _ = strings.Cut(addr, "/")
326+
return addr
327+
}

registry/remote/credentials/internal/config/config_test.go

+136
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,94 @@ func TestConfig_GetCredential_validConfig(t *testing.T) {
180180
}
181181
}
182182

183+
func TestConfig_GetCredential_legacyConfig(t *testing.T) {
184+
cfg, err := Load("../../testdata/legacy_auths_config.json")
185+
if err != nil {
186+
t.Fatal("Load() error =", err)
187+
}
188+
189+
tests := []struct {
190+
name string
191+
serverAddress string
192+
want auth.Credential
193+
wantErr bool
194+
}{
195+
{
196+
name: "Regular address matched",
197+
serverAddress: "registry1.example.com",
198+
want: auth.Credential{
199+
Username: "username1",
200+
Password: "password1",
201+
},
202+
},
203+
{
204+
name: "Another entry for the same address matched",
205+
serverAddress: "https://registry1.example.com/",
206+
want: auth.Credential{
207+
Username: "foo",
208+
Password: "bar",
209+
},
210+
},
211+
{
212+
name: "Address with different scheme unmached",
213+
serverAddress: "http://registry1.example.com/",
214+
want: auth.EmptyCredential,
215+
},
216+
{
217+
name: "Address with http prefix matched",
218+
serverAddress: "registry2.example.com",
219+
want: auth.Credential{
220+
Username: "username2",
221+
Password: "password2",
222+
},
223+
},
224+
{
225+
name: "Address with https prefix matched",
226+
serverAddress: "registry3.example.com",
227+
want: auth.Credential{
228+
Username: "username3",
229+
Password: "password3",
230+
},
231+
},
232+
{
233+
name: "Address with http prefix and / suffix matched",
234+
serverAddress: "registry4.example.com",
235+
want: auth.Credential{
236+
Username: "username4",
237+
Password: "password4",
238+
},
239+
},
240+
{
241+
name: "Address with https prefix and / suffix matched",
242+
serverAddress: "registry5.example.com",
243+
want: auth.Credential{
244+
Username: "username5",
245+
Password: "password5",
246+
},
247+
},
248+
{
249+
name: "Address with https prefix and path suffix matched",
250+
serverAddress: "registry6.example.com",
251+
want: auth.Credential{
252+
Username: "username6",
253+
Password: "password6",
254+
},
255+
},
256+
}
257+
for _, tt := range tests {
258+
t.Run(tt.name, func(t *testing.T) {
259+
got, err := cfg.GetCredential(tt.serverAddress)
260+
if (err != nil) != tt.wantErr {
261+
t.Errorf("Config.GetCredential() error = %v, wantErr %v", err, tt.wantErr)
262+
return
263+
}
264+
if !reflect.DeepEqual(got, tt.want) {
265+
t.Errorf("Config.GetCredential() = %v, want %v", got, tt.want)
266+
}
267+
})
268+
}
269+
}
270+
183271
func TestConfig_GetCredential_invalidConfig(t *testing.T) {
184272
cfg, err := Load("../../testdata/invalid_auths_entry_config.json")
185273
if err != nil {
@@ -1314,3 +1402,51 @@ func Test_decodeAuth(t *testing.T) {
13141402
})
13151403
}
13161404
}
1405+
1406+
func Test_toHostname(t *testing.T) {
1407+
tests := []struct {
1408+
name string
1409+
addr string
1410+
want string
1411+
}{
1412+
{
1413+
addr: "http://test.example.com",
1414+
want: "test.example.com",
1415+
},
1416+
{
1417+
addr: "http://test.example.com/",
1418+
want: "test.example.com",
1419+
},
1420+
{
1421+
addr: "http://test.example.com/foo/bar",
1422+
want: "test.example.com",
1423+
},
1424+
{
1425+
addr: "https://test.example.com",
1426+
want: "test.example.com",
1427+
},
1428+
{
1429+
addr: "https://test.example.com/",
1430+
want: "test.example.com",
1431+
},
1432+
{
1433+
addr: "http://test.example.com/foo/bar",
1434+
want: "test.example.com",
1435+
},
1436+
{
1437+
addr: "test.example.com",
1438+
want: "test.example.com",
1439+
},
1440+
{
1441+
addr: "test.example.com/foo/bar/",
1442+
want: "test.example.com",
1443+
},
1444+
}
1445+
for _, tt := range tests {
1446+
t.Run(tt.name, func(t *testing.T) {
1447+
if got := toHostname(tt.addr); got != tt.want {
1448+
t.Errorf("toHostname() = %v, want %v", got, tt.want)
1449+
}
1450+
})
1451+
}
1452+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"auths": {
3+
"registry1.example.com": {
4+
"auth": "dXNlcm5hbWUxOnBhc3N3b3JkMQ=="
5+
},
6+
"http://registry2.example.com": {
7+
"auth": "dXNlcm5hbWUyOnBhc3N3b3JkMg=="
8+
},
9+
"https://registry3.example.com": {
10+
"auth": "dXNlcm5hbWUzOnBhc3N3b3JkMw=="
11+
},
12+
"http://registry4.example.com/": {
13+
"auth": "dXNlcm5hbWU0OnBhc3N3b3JkNA=="
14+
},
15+
"https://registry5.example.com/": {
16+
"auth": "dXNlcm5hbWU1OnBhc3N3b3JkNQ=="
17+
},
18+
"https://registry6.example.com/path/": {
19+
"auth": "dXNlcm5hbWU2OnBhc3N3b3JkNg=="
20+
},
21+
"https://registry1.example.com/": {
22+
"auth": "Zm9vOmJhcg=="
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)