10
"github.com/containers/image/v5/pkg/docker/config"
11
"github.com/containers/image/v5/types"
12
"github.com/stretchr/testify/assert"
13
"github.com/stretchr/testify/require"
16
const largeAuthFile = `{"auths":{
17
"docker.io/vendor": {"auth": "ZG9ja2VyOnZlbmRvcg=="},
18
"https://index.docker.io/v1": {"auth": "ZG9ja2VyOnRvcA=="},
19
"quay.io/libpod": {"auth": "cXVheTpsaWJwb2Q="},
20
"quay.io": {"auth": "cXVheTp0b3A="}
23
// Semantics of largeAuthFile
24
var largeAuthFileValues = map[string]types.DockerAuthConfig{
25
"docker.io/vendor": {Username: "docker", Password: "vendor"},
26
"docker.io": {Username: "docker", Password: "top"},
27
"quay.io/libpod": {Username: "quay", Password: "libpod"},
28
"quay.io": {Username: "quay", Password: "top"},
31
// systemContextForAuthFile returns a types.SystemContext with AuthFilePath pointing
32
// to a temporary file with fileContents, or nil if fileContents is empty; and a cleanup
33
// function the caller must arrange to call.
34
func systemContextForAuthFile(t *testing.T, fileContents string) (*types.SystemContext, func()) {
35
if fileContents == "" {
39
f, err := os.CreateTemp("", "auth.json")
40
require.NoError(t, err)
42
err = os.WriteFile(path, []byte(fileContents), 0700)
43
require.NoError(t, err)
44
return &types.SystemContext{AuthFilePath: path}, func() { os.Remove(path) }
47
// Test that GetCredentials() correctly parses what MakeXRegistryConfigHeader() produces
48
func TestMakeXRegistryConfigHeaderGetCredentialsRoundtrip(t *testing.T) {
49
for _, tc := range []struct {
52
username, password string
53
expectedOverride *types.DockerAuthConfig
54
expectedFileValues map[string]types.DockerAuthConfig
61
expectedOverride: nil,
62
expectedFileValues: nil,
66
fileContents: largeAuthFile,
69
expectedOverride: nil,
70
expectedFileValues: largeAuthFileValues,
73
name: "file data + override",
74
fileContents: largeAuthFile,
75
username: "override-user",
76
password: "override-pass",
77
expectedOverride: &types.DockerAuthConfig{Username: "override-user", Password: "override-pass"},
78
expectedFileValues: largeAuthFileValues,
81
sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
83
headers, err := MakeXRegistryConfigHeader(sys, tc.username, tc.password)
84
require.NoError(t, err)
85
req, err := http.NewRequest(http.MethodPost, "/", nil)
86
require.NoError(t, err, tc.name)
87
for _, v := range headers.Values(xRegistryConfigHeader) {
88
req.Header.Add(xRegistryConfigHeader, v)
91
override, resPath, err := GetCredentials(req)
92
require.NoError(t, err, tc.name)
93
defer RemoveAuthfile(resPath)
94
if tc.expectedOverride == nil {
95
assert.Nil(t, override, tc.name)
97
require.NotNil(t, override, tc.name)
98
assert.Equal(t, *tc.expectedOverride, *override, tc.name)
100
for key, expectedAuth := range tc.expectedFileValues {
101
auth, err := config.GetCredentials(&types.SystemContext{AuthFilePath: resPath}, key)
102
require.NoError(t, err, tc.name)
103
assert.Equal(t, expectedAuth, auth, "%s, key %s", tc.name, key)
108
// Test that GetCredentials() correctly parses what MakeXRegistryAuthHeader() produces
109
func TestMakeXRegistryAuthHeaderGetCredentialsRoundtrip(t *testing.T) {
110
for _, tc := range []struct {
113
username, password string
114
expectedOverride *types.DockerAuthConfig
115
expectedFileValues map[string]types.DockerAuthConfig
120
username: "override-user",
121
password: "override-pass",
122
expectedOverride: &types.DockerAuthConfig{Username: "override-user", Password: "override-pass"},
123
expectedFileValues: nil,
127
fileContents: largeAuthFile,
130
expectedFileValues: largeAuthFileValues,
133
sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
135
headers, err := MakeXRegistryAuthHeader(sys, tc.username, tc.password)
136
require.NoError(t, err)
137
req, err := http.NewRequest(http.MethodPost, "/", nil)
138
require.NoError(t, err, tc.name)
139
for _, v := range headers.Values(xRegistryAuthHeader) {
140
req.Header.Set(xRegistryAuthHeader, v)
143
override, resPath, err := GetCredentials(req)
144
require.NoError(t, err, tc.name)
145
defer RemoveAuthfile(resPath)
146
if tc.expectedOverride == nil {
147
assert.Nil(t, override, tc.name)
149
require.NotNil(t, override, tc.name)
150
assert.Equal(t, *tc.expectedOverride, *override, tc.name)
152
for key, expectedAuth := range tc.expectedFileValues {
153
auth, err := config.GetCredentials(&types.SystemContext{AuthFilePath: resPath}, key)
154
require.NoError(t, err, tc.name)
155
assert.Equal(t, expectedAuth, auth, "%s, key %s", tc.name, key)
160
func TestMakeXRegistryConfigHeader(t *testing.T) {
161
for _, tc := range []struct {
164
username, password string
166
expectedContents string
173
expectedContents: "",
176
name: "invalid JSON",
177
fileContents: "@invalid JSON",
184
fileContents: largeAuthFile,
188
"docker.io/vendor": {"username": "docker", "password": "vendor"},
189
"docker.io": {"username": "docker", "password": "top"},
190
"quay.io/libpod": {"username": "quay", "password": "libpod"},
191
"quay.io": {"username": "quay", "password": "top"}
195
name: "file data + override",
196
fileContents: largeAuthFile,
197
username: "override-user",
198
password: "override-pass",
200
"docker.io/vendor": {"username": "docker", "password": "vendor"},
201
"docker.io": {"username": "docker", "password": "top"},
202
"quay.io/libpod": {"username": "quay", "password": "libpod"},
203
"quay.io": {"username": "quay", "password": "top"},
204
"": {"username": "override-user", "password": "override-pass"}
208
sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
210
res, err := MakeXRegistryConfigHeader(sys, tc.username, tc.password)
212
assert.Error(t, err, tc.name)
214
require.NoError(t, err, tc.name)
215
if tc.expectedContents == "" {
216
assert.Empty(t, res, tc.name)
218
require.Len(t, res, 1, tc.name)
219
header, ok := res[xRegistryConfigHeader]
220
require.True(t, ok, tc.name)
221
decodedHeader, err := base64.URLEncoding.DecodeString(header[0])
222
require.NoError(t, err, tc.name)
223
// Don't test for a specific JSON representation, just for the expected contents.
224
expected := map[string]interface{}{}
225
actual := map[string]interface{}{}
226
err = json.Unmarshal([]byte(tc.expectedContents), &expected)
227
require.NoError(t, err, tc.name)
228
err = json.Unmarshal(decodedHeader, &actual)
229
require.NoError(t, err, tc.name)
230
assert.Equal(t, expected, actual, tc.name)
236
func TestMakeXRegistryAuthHeader(t *testing.T) {
237
for _, tc := range []struct {
240
username, password string
242
expectedContents string
247
username: "override-user",
248
password: "override-pass",
249
expectedContents: `{"username": "override-user", "password": "override-pass"}`,
252
name: "invalid JSON",
253
fileContents: "@invalid JSON",
260
fileContents: largeAuthFile,
264
"docker.io/vendor": {"username": "docker", "password": "vendor"},
265
"docker.io": {"username": "docker", "password": "top"},
266
"quay.io/libpod": {"username": "quay", "password": "libpod"},
267
"quay.io": {"username": "quay", "password": "top"}
271
sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
273
res, err := MakeXRegistryAuthHeader(sys, tc.username, tc.password)
275
assert.Error(t, err, tc.name)
277
require.NoError(t, err, tc.name)
278
if tc.expectedContents == "" {
279
assert.Empty(t, res, tc.name)
281
require.Len(t, res, 1, tc.name)
282
header, ok := res[xRegistryAuthHeader]
283
require.True(t, ok, tc.name)
284
decodedHeader, err := base64.URLEncoding.DecodeString(header[0])
285
require.NoError(t, err, tc.name)
286
// Don't test for a specific JSON representation, just for the expected contents.
287
expected := map[string]interface{}{}
288
actual := map[string]interface{}{}
289
err = json.Unmarshal([]byte(tc.expectedContents), &expected)
290
require.NoError(t, err, tc.name)
291
err = json.Unmarshal(decodedHeader, &actual)
292
require.NoError(t, err, tc.name)
293
assert.Equal(t, expected, actual, tc.name)
299
func TestAuthConfigsToAuthFile(t *testing.T) {
300
for _, tc := range []struct {
304
expectedContains string
307
name: "empty auth configs",
310
expectedContains: "{}",
313
name: "registry with a namespace prefix",
314
server: "my-registry.local/username",
316
expectedContains: `"my-registry.local/username":`,
319
name: "URLs are interpreted as full registries",
320
server: "http://my-registry.local/username",
322
expectedContains: `"my-registry.local":`,
325
name: "the old-style docker registry URL is normalized",
326
server: "http://index.docker.io/v1/",
328
expectedContains: `"docker.io":`,
331
name: "docker.io vendor namespace",
332
server: "docker.io/vendor",
334
expectedContains: `"docker.io/vendor":`,
337
configs := map[string]types.DockerAuthConfig{}
339
configs[tc.server] = types.DockerAuthConfig{}
342
filePath, err := authConfigsToAuthFile(configs)
346
assert.Empty(t, filePath)
348
assert.NoError(t, err)
349
content, err := os.ReadFile(filePath)
350
require.NoError(t, err)
351
assert.Contains(t, string(content), tc.expectedContains)
357
func TestParseSingleAuthHeader(t *testing.T) {
358
for _, tc := range []struct {
361
expected types.DockerAuthConfig
364
input: "", // An empty (or missing) header
365
expected: types.DockerAuthConfig{},
369
expected: types.DockerAuthConfig{},
372
{input: "@", shouldErr: true},
375
input: base64.URLEncoding.EncodeToString([]byte(`{"username":"u1","password":"p1"}`)),
376
expected: types.DockerAuthConfig{Username: "u1", Password: "p1"},
379
res, err := parseSingleAuthHeader(tc.input)
381
assert.Error(t, err, tc.input)
383
require.NoError(t, err, tc.input)
384
assert.Equal(t, tc.expected, res, tc.input)
389
func TestParseMultiAuthHeader(t *testing.T) {
390
for _, tc := range []struct {
393
expected map[string]types.DockerAuthConfig
396
{input: "", expected: nil},
398
{input: "null", expected: nil},
400
{input: "@", shouldErr: true},
403
input: base64.URLEncoding.EncodeToString([]byte(
404
`{"https://index.docker.io/v1/":{"username":"u1","password":"p1"},` +
405
`"quay.io/libpod":{"username":"u2","password":"p2"}}`)),
406
expected: map[string]types.DockerAuthConfig{
407
"https://index.docker.io/v1/": {Username: "u1", Password: "p1"},
408
"quay.io/libpod": {Username: "u2", Password: "p2"},
412
res, err := parseMultiAuthHeader(tc.input)
414
assert.Error(t, err, tc.input)
416
require.NoError(t, err, tc.input)
417
assert.Equal(t, tc.expected, res, tc.input)