podman

Форк
0
/
auth_test.go 
420 строк · 12.5 Кб
1
package auth
2

3
import (
4
	"encoding/base64"
5
	"encoding/json"
6
	"net/http"
7
	"os"
8
	"testing"
9

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"
14
)
15

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="}
21
}}`
22

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"},
29
}
30

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 == "" {
36
		return nil, func() {}
37
	}
38

39
	f, err := os.CreateTemp("", "auth.json")
40
	require.NoError(t, err)
41
	path := f.Name()
42
	err = os.WriteFile(path, []byte(fileContents), 0700)
43
	require.NoError(t, err)
44
	return &types.SystemContext{AuthFilePath: path}, func() { os.Remove(path) }
45
}
46

47
// Test that GetCredentials() correctly parses what MakeXRegistryConfigHeader() produces
48
func TestMakeXRegistryConfigHeaderGetCredentialsRoundtrip(t *testing.T) {
49
	for _, tc := range []struct {
50
		name               string
51
		fileContents       string
52
		username, password string
53
		expectedOverride   *types.DockerAuthConfig
54
		expectedFileValues map[string]types.DockerAuthConfig
55
	}{
56
		{
57
			name:               "no data",
58
			fileContents:       "",
59
			username:           "",
60
			password:           "",
61
			expectedOverride:   nil,
62
			expectedFileValues: nil,
63
		},
64
		{
65
			name:               "file data",
66
			fileContents:       largeAuthFile,
67
			username:           "",
68
			password:           "",
69
			expectedOverride:   nil,
70
			expectedFileValues: largeAuthFileValues,
71
		},
72
		{
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,
79
		},
80
	} {
81
		sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
82
		defer cleanup()
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)
89
		}
90

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)
96
		} else {
97
			require.NotNil(t, override, tc.name)
98
			assert.Equal(t, *tc.expectedOverride, *override, tc.name)
99
		}
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)
104
		}
105
	}
106
}
107

108
// Test that GetCredentials() correctly parses what MakeXRegistryAuthHeader() produces
109
func TestMakeXRegistryAuthHeaderGetCredentialsRoundtrip(t *testing.T) {
110
	for _, tc := range []struct {
111
		name               string
112
		fileContents       string
113
		username, password string
114
		expectedOverride   *types.DockerAuthConfig
115
		expectedFileValues map[string]types.DockerAuthConfig
116
	}{
117
		{
118
			name:               "override",
119
			fileContents:       "",
120
			username:           "override-user",
121
			password:           "override-pass",
122
			expectedOverride:   &types.DockerAuthConfig{Username: "override-user", Password: "override-pass"},
123
			expectedFileValues: nil,
124
		},
125
		{
126
			name:               "file data",
127
			fileContents:       largeAuthFile,
128
			username:           "",
129
			password:           "",
130
			expectedFileValues: largeAuthFileValues,
131
		},
132
	} {
133
		sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
134
		defer cleanup()
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)
141
		}
142

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)
148
		} else {
149
			require.NotNil(t, override, tc.name)
150
			assert.Equal(t, *tc.expectedOverride, *override, tc.name)
151
		}
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)
156
		}
157
	}
158
}
159

160
func TestMakeXRegistryConfigHeader(t *testing.T) {
161
	for _, tc := range []struct {
162
		name               string
163
		fileContents       string
164
		username, password string
165
		shouldErr          bool
166
		expectedContents   string
167
	}{
168
		{
169
			name:             "no data",
170
			fileContents:     "",
171
			username:         "",
172
			password:         "",
173
			expectedContents: "",
174
		},
175
		{
176
			name:         "invalid JSON",
177
			fileContents: "@invalid JSON",
178
			username:     "",
179
			password:     "",
180
			shouldErr:    true,
181
		},
182
		{
183
			name:         "file data",
184
			fileContents: largeAuthFile,
185
			username:     "",
186
			password:     "",
187
			expectedContents: `{
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"}
192
			}`,
193
		},
194
		{
195
			name:         "file data + override",
196
			fileContents: largeAuthFile,
197
			username:     "override-user",
198
			password:     "override-pass",
199
			expectedContents: `{
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"}
205
				}`,
206
		},
207
	} {
208
		sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
209
		defer cleanup()
210
		res, err := MakeXRegistryConfigHeader(sys, tc.username, tc.password)
211
		if tc.shouldErr {
212
			assert.Error(t, err, tc.name)
213
		} else {
214
			require.NoError(t, err, tc.name)
215
			if tc.expectedContents == "" {
216
				assert.Empty(t, res, tc.name)
217
			} else {
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)
231
			}
232
		}
233
	}
234
}
235

236
func TestMakeXRegistryAuthHeader(t *testing.T) {
237
	for _, tc := range []struct {
238
		name               string
239
		fileContents       string
240
		username, password string
241
		shouldErr          bool
242
		expectedContents   string
243
	}{
244
		{
245
			name:             "override",
246
			fileContents:     "",
247
			username:         "override-user",
248
			password:         "override-pass",
249
			expectedContents: `{"username": "override-user", "password": "override-pass"}`,
250
		},
251
		{
252
			name:         "invalid JSON",
253
			fileContents: "@invalid JSON",
254
			username:     "",
255
			password:     "",
256
			shouldErr:    true,
257
		},
258
		{
259
			name:         "file data",
260
			fileContents: largeAuthFile,
261
			username:     "",
262
			password:     "",
263
			expectedContents: `{
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"}
268
			}`,
269
		},
270
	} {
271
		sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
272
		defer cleanup()
273
		res, err := MakeXRegistryAuthHeader(sys, tc.username, tc.password)
274
		if tc.shouldErr {
275
			assert.Error(t, err, tc.name)
276
		} else {
277
			require.NoError(t, err, tc.name)
278
			if tc.expectedContents == "" {
279
				assert.Empty(t, res, tc.name)
280
			} else {
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)
294
			}
295
		}
296
	}
297
}
298

299
func TestAuthConfigsToAuthFile(t *testing.T) {
300
	for _, tc := range []struct {
301
		name             string
302
		server           string
303
		shouldErr        bool
304
		expectedContains string
305
	}{
306
		{
307
			name:             "empty auth configs",
308
			server:           "",
309
			shouldErr:        false,
310
			expectedContains: "{}",
311
		},
312
		{
313
			name:             "registry with a namespace prefix",
314
			server:           "my-registry.local/username",
315
			shouldErr:        false,
316
			expectedContains: `"my-registry.local/username":`,
317
		},
318
		{
319
			name:             "URLs are interpreted as full registries",
320
			server:           "http://my-registry.local/username",
321
			shouldErr:        false,
322
			expectedContains: `"my-registry.local":`,
323
		},
324
		{
325
			name:             "the old-style docker registry URL is normalized",
326
			server:           "http://index.docker.io/v1/",
327
			shouldErr:        false,
328
			expectedContains: `"docker.io":`,
329
		},
330
		{
331
			name:             "docker.io vendor namespace",
332
			server:           "docker.io/vendor",
333
			shouldErr:        false,
334
			expectedContains: `"docker.io/vendor":`,
335
		},
336
	} {
337
		configs := map[string]types.DockerAuthConfig{}
338
		if tc.server != "" {
339
			configs[tc.server] = types.DockerAuthConfig{}
340
		}
341

342
		filePath, err := authConfigsToAuthFile(configs)
343

344
		if tc.shouldErr {
345
			assert.Error(t, err)
346
			assert.Empty(t, filePath)
347
		} else {
348
			assert.NoError(t, err)
349
			content, err := os.ReadFile(filePath)
350
			require.NoError(t, err)
351
			assert.Contains(t, string(content), tc.expectedContains)
352
			os.Remove(filePath)
353
		}
354
	}
355
}
356

357
func TestParseSingleAuthHeader(t *testing.T) {
358
	for _, tc := range []struct {
359
		input     string
360
		shouldErr bool
361
		expected  types.DockerAuthConfig
362
	}{
363
		{
364
			input:    "", // An empty (or missing) header
365
			expected: types.DockerAuthConfig{},
366
		},
367
		{
368
			input:    "null",
369
			expected: types.DockerAuthConfig{},
370
		},
371
		// Invalid JSON
372
		{input: "@", shouldErr: true},
373
		// Success
374
		{
375
			input:    base64.URLEncoding.EncodeToString([]byte(`{"username":"u1","password":"p1"}`)),
376
			expected: types.DockerAuthConfig{Username: "u1", Password: "p1"},
377
		},
378
	} {
379
		res, err := parseSingleAuthHeader(tc.input)
380
		if tc.shouldErr {
381
			assert.Error(t, err, tc.input)
382
		} else {
383
			require.NoError(t, err, tc.input)
384
			assert.Equal(t, tc.expected, res, tc.input)
385
		}
386
	}
387
}
388

389
func TestParseMultiAuthHeader(t *testing.T) {
390
	for _, tc := range []struct {
391
		input     string
392
		shouldErr bool
393
		expected  map[string]types.DockerAuthConfig
394
	}{
395
		// Empty header
396
		{input: "", expected: nil},
397
		// "null"
398
		{input: "null", expected: nil},
399
		// Invalid JSON
400
		{input: "@", shouldErr: true},
401
		// Success
402
		{
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"},
409
			},
410
		},
411
	} {
412
		res, err := parseMultiAuthHeader(tc.input)
413
		if tc.shouldErr {
414
			assert.Error(t, err, tc.input)
415
		} else {
416
			require.NoError(t, err, tc.input)
417
			assert.Equal(t, tc.expected, res, tc.input)
418
		}
419
	}
420
}
421

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.