podman

Форк
0
/
auth.go 
343 строки · 11.9 Кб
1
package auth
2

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

11
	imageAuth "github.com/containers/image/v5/pkg/docker/config"
12
	"github.com/containers/image/v5/types"
13
	dockerAPITypes "github.com/docker/docker/api/types/registry"
14
	"github.com/sirupsen/logrus"
15
)
16

17
// xRegistryAuthHeader is the key to the encoded registry authentication configuration in an http-request header.
18
// This header supports one registry per header occurrence. To support N registries provide N headers, one per registry.
19
// As of Docker API 1.40 and Libpod API 1.0.0, this header is supported by all endpoints.
20
const xRegistryAuthHeader = "X-Registry-Auth"
21

22
// xRegistryConfigHeader is the key to the encoded registry authentication configuration in an http-request header.
23
// This header supports N registries in one header via a Base64 encoded, JSON map.
24
// As of Docker API 1.40 and Libpod API 2.0.0, this header is supported by build endpoints.
25
const xRegistryConfigHeader = "X-Registry-Config"
26

27
// GetCredentials queries the http.Request for X-Registry-.* headers and extracts
28
// the necessary authentication information for libpod operations, possibly
29
// creating a config file. If that is the case, the caller must call RemoveAuthFile.
30
func GetCredentials(r *http.Request) (*types.DockerAuthConfig, string, error) {
31
	nonemptyHeaderValue := func(key string) ([]string, bool) {
32
		hdr := r.Header.Values(key)
33
		return hdr, len(hdr) > 0
34
	}
35
	var override *types.DockerAuthConfig
36
	var fileContents map[string]types.DockerAuthConfig
37
	var headerName string
38
	var err error
39
	if hdr, ok := nonemptyHeaderValue(xRegistryConfigHeader); ok {
40
		headerName = xRegistryConfigHeader
41
		override, fileContents, err = getConfigCredentials(r, hdr)
42
	} else if hdr, ok := nonemptyHeaderValue(xRegistryAuthHeader); ok {
43
		headerName = xRegistryAuthHeader
44
		override, fileContents, err = getAuthCredentials(hdr)
45
	} else {
46
		return nil, "", nil
47
	}
48
	if err != nil {
49
		return nil, "", fmt.Errorf("failed to parse %q header for %s: %w", headerName, r.URL.String(), err)
50
	}
51

52
	var authFile string
53
	if fileContents == nil {
54
		authFile = ""
55
	} else {
56
		authFile, err = authConfigsToAuthFile(fileContents)
57
		if err != nil {
58
			return nil, "", fmt.Errorf("failed to parse %q header for %s: %w", headerName, r.URL.String(), err)
59
		}
60
	}
61
	return override, authFile, nil
62
}
63

64
// getConfigCredentials extracts one or more docker.AuthConfig from a request and its
65
// xRegistryConfigHeader value.  An empty key will be used as default while a named registry will be
66
// returned as types.DockerAuthConfig
67
func getConfigCredentials(r *http.Request, headers []string) (*types.DockerAuthConfig, map[string]types.DockerAuthConfig, error) {
68
	var auth *types.DockerAuthConfig
69
	configs := make(map[string]types.DockerAuthConfig)
70

71
	for _, h := range headers {
72
		param, err := base64.URLEncoding.DecodeString(h)
73
		if err != nil {
74
			return nil, nil, fmt.Errorf("failed to decode %q: %w", xRegistryConfigHeader, err)
75
		}
76

77
		ac := make(map[string]dockerAPITypes.AuthConfig)
78
		err = json.Unmarshal(param, &ac)
79
		if err != nil {
80
			return nil, nil, fmt.Errorf("failed to unmarshal %q: %w", xRegistryConfigHeader, err)
81
		}
82

83
		for k, v := range ac {
84
			configs[k] = dockerAuthToImageAuth(v)
85
		}
86
	}
87

88
	// Empty key implies no registry given in API
89
	if c, found := configs[""]; found {
90
		auth = &c
91
	}
92

93
	// Override any default given above if specialized credentials provided
94
	if registries, found := r.URL.Query()["registry"]; found {
95
		for _, r := range registries {
96
			for k, v := range configs {
97
				if strings.Contains(k, r) {
98
					v := v
99
					auth = &v
100
					break
101
				}
102
			}
103
			if auth != nil {
104
				break
105
			}
106
		}
107

108
		if auth == nil {
109
			logrus.Debugf("%q header found in request, but \"registry=%v\" query parameter not provided",
110
				xRegistryConfigHeader, registries)
111
		} else {
112
			logrus.Debugf("%q header found in request for username %q", xRegistryConfigHeader, auth.Username)
113
		}
114
	}
115

116
	return auth, configs, nil
117
}
118

119
// getAuthCredentials extracts one or more DockerAuthConfigs from an xRegistryAuthHeader
120
// value.  The header could specify a single-auth config in which case the
121
// first return value is set.  In case of a multi-auth header, the contents are
122
// returned in the second return value.
123
func getAuthCredentials(headers []string) (*types.DockerAuthConfig, map[string]types.DockerAuthConfig, error) {
124
	authHeader := headers[0]
125

126
	// First look for a multi-auth header (i.e., a map).
127
	authConfigs, err := parseMultiAuthHeader(authHeader)
128
	if err == nil {
129
		return nil, authConfigs, nil
130
	}
131

132
	// Fallback to looking for a single-auth header (i.e., one config).
133
	authConfig, err := parseSingleAuthHeader(authHeader)
134
	if err != nil {
135
		return nil, nil, err
136
	}
137
	return &authConfig, nil, nil
138
}
139

140
// MakeXRegistryConfigHeader returns a map with the "X-Registry-Config" header set, which can
141
// conveniently be used in the http stack.
142
func MakeXRegistryConfigHeader(sys *types.SystemContext, username, password string) (http.Header, error) {
143
	if sys == nil {
144
		sys = &types.SystemContext{}
145
	}
146
	authConfigs, err := imageAuth.GetAllCredentials(sys)
147
	if err != nil {
148
		return nil, err
149
	}
150

151
	if username != "" {
152
		authConfigs[""] = types.DockerAuthConfig{
153
			Username: username,
154
			Password: password,
155
		}
156
	}
157

158
	if len(authConfigs) == 0 {
159
		return nil, nil
160
	}
161
	content, err := encodeMultiAuthConfigs(authConfigs)
162
	if err != nil {
163
		return nil, err
164
	}
165
	return http.Header{xRegistryConfigHeader: []string{content}}, nil
166
}
167

168
// MakeXRegistryAuthHeader returns a map with the "X-Registry-Auth" header set, which can
169
// conveniently be used in the http stack.
170
func MakeXRegistryAuthHeader(sys *types.SystemContext, username, password string) (http.Header, error) {
171
	if username != "" {
172
		content, err := encodeSingleAuthConfig(types.DockerAuthConfig{Username: username, Password: password})
173
		if err != nil {
174
			return nil, err
175
		}
176
		return http.Header{xRegistryAuthHeader: []string{content}}, nil
177
	}
178

179
	if sys == nil {
180
		sys = &types.SystemContext{}
181
	}
182
	authConfigs, err := imageAuth.GetAllCredentials(sys)
183
	if err != nil {
184
		return nil, err
185
	}
186
	content, err := encodeMultiAuthConfigs(authConfigs)
187
	if err != nil {
188
		return nil, err
189
	}
190
	return http.Header{xRegistryAuthHeader: []string{content}}, nil
191
}
192

193
// RemoveAuthfile is a convenience function that is meant to be called in a
194
// deferred statement. If non-empty, it removes the specified authfile and log
195
// errors.  It's meant to reduce boilerplate code at call sites of
196
// `GetCredentials`.
197
func RemoveAuthfile(authfile string) {
198
	if authfile == "" {
199
		return
200
	}
201
	if err := os.Remove(authfile); err != nil {
202
		logrus.Errorf("Removing temporary auth file %q: %v", authfile, err)
203
	}
204
}
205

206
// encodeSingleAuthConfig serializes the auth configuration as a base64 encoded JSON payload.
207
func encodeSingleAuthConfig(authConfig types.DockerAuthConfig) (string, error) {
208
	conf := imageAuthToDockerAuth(authConfig)
209
	buf, err := json.Marshal(conf)
210
	if err != nil {
211
		return "", err
212
	}
213
	return base64.URLEncoding.EncodeToString(buf), nil
214
}
215

216
// encodeMultiAuthConfigs serializes the auth configurations as a base64 encoded JSON payload.
217
func encodeMultiAuthConfigs(authConfigs map[string]types.DockerAuthConfig) (string, error) {
218
	confs := make(map[string]dockerAPITypes.AuthConfig)
219
	for registry, authConf := range authConfigs {
220
		confs[registry] = imageAuthToDockerAuth(authConf)
221
	}
222
	buf, err := json.Marshal(confs)
223
	if err != nil {
224
		return "", err
225
	}
226
	return base64.URLEncoding.EncodeToString(buf), nil
227
}
228

229
// authConfigsToAuthFile stores the specified auth configs in a temporary files
230
// and returns its path. The file can later be used as an auth file for contacting
231
// one or more container registries.  If tmpDir is empty, the system's default
232
// TMPDIR will be used.
233
func authConfigsToAuthFile(authConfigs map[string]types.DockerAuthConfig) (string, error) {
234
	// Initialize an empty temporary JSON file.
235
	tmpFile, err := os.CreateTemp("", "auth.json.")
236
	if err != nil {
237
		return "", err
238
	}
239
	if _, err := tmpFile.Write([]byte{'{', '}'}); err != nil {
240
		return "", fmt.Errorf("initializing temporary auth file: %w", err)
241
	}
242
	if err := tmpFile.Close(); err != nil {
243
		return "", fmt.Errorf("closing temporary auth file: %w", err)
244
	}
245
	authFilePath := tmpFile.Name()
246

247
	// Now use the c/image packages to store the credentials. It's battle
248
	// tested, and we make sure to use the same code as the image backend.
249
	sys := types.SystemContext{AuthFilePath: authFilePath}
250
	for authFileKey, config := range authConfigs {
251
		key := normalizeAuthFileKey(authFileKey)
252

253
		// Note that we do not validate the credentials here. We assume
254
		// that all credentials are valid. They'll be used on demand
255
		// later.
256
		if err := imageAuth.SetAuthentication(&sys, key, config.Username, config.Password); err != nil {
257
			return "", fmt.Errorf("storing credentials in temporary auth file (key: %q / %q, user: %q): %w", authFileKey, key, config.Username, err)
258
		}
259
	}
260

261
	return authFilePath, nil
262
}
263

264
// normalizeAuthFileKey takes an auth file key and converts it into a new-style credential key
265
// in the canonical format, as interpreted by c/image/pkg/docker/config.
266
func normalizeAuthFileKey(authFileKey string) string {
267
	stripped := strings.TrimPrefix(authFileKey, "http://")
268
	stripped = strings.TrimPrefix(stripped, "https://")
269

270
	if stripped != authFileKey { // URLs are interpreted to mean complete registries
271
		stripped, _, _ = strings.Cut(stripped, "/")
272
	}
273

274
	// Only non-namespaced registry names (or URLs) need to be normalized; repo namespaces
275
	// always use the simple format.
276
	switch stripped {
277
	case "registry-1.docker.io", "index.docker.io":
278
		return "docker.io"
279
	default:
280
		return stripped
281
	}
282
}
283

284
// dockerAuthToImageAuth converts a docker auth config to one we're using
285
// internally from c/image.  Note that the Docker types look slightly
286
// different, so we need to convert to be extra sure we're not running into
287
// undesired side-effects when unmarshalling directly to our types.
288
func dockerAuthToImageAuth(authConfig dockerAPITypes.AuthConfig) types.DockerAuthConfig {
289
	return types.DockerAuthConfig{
290
		Username:      authConfig.Username,
291
		Password:      authConfig.Password,
292
		IdentityToken: authConfig.IdentityToken,
293
	}
294
}
295

296
// reverse conversion of `dockerAuthToImageAuth`.
297
func imageAuthToDockerAuth(authConfig types.DockerAuthConfig) dockerAPITypes.AuthConfig {
298
	return dockerAPITypes.AuthConfig{
299
		Username:      authConfig.Username,
300
		Password:      authConfig.Password,
301
		IdentityToken: authConfig.IdentityToken,
302
	}
303
}
304

305
// parseSingleAuthHeader extracts a DockerAuthConfig from an xRegistryAuthHeader value.
306
// The header content is a single DockerAuthConfig.
307
func parseSingleAuthHeader(authHeader string) (types.DockerAuthConfig, error) {
308
	// Accept "null" and handle it as empty value for compatibility reason with Docker.
309
	// Some java docker clients pass this value, e.g. this one used in Eclipse.
310
	if len(authHeader) == 0 || authHeader == "null" {
311
		return types.DockerAuthConfig{}, nil
312
	}
313

314
	authConfig := dockerAPITypes.AuthConfig{}
315
	authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader))
316
	if err := json.NewDecoder(authJSON).Decode(&authConfig); err != nil {
317
		return types.DockerAuthConfig{}, err
318
	}
319
	return dockerAuthToImageAuth(authConfig), nil
320
}
321

322
// parseMultiAuthHeader extracts a DockerAuthConfig from an xRegistryAuthHeader value.
323
// The header content is a map[string]DockerAuthConfigs.
324
func parseMultiAuthHeader(authHeader string) (map[string]types.DockerAuthConfig, error) {
325
	// Accept "null" and handle it as empty value for compatibility reason with Docker.
326
	// Some java docker clients pass this value, e.g. this one used in Eclipse.
327
	if len(authHeader) == 0 || authHeader == "null" {
328
		return nil, nil
329
	}
330

331
	dockerAuthConfigs := make(map[string]dockerAPITypes.AuthConfig)
332
	authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader))
333
	if err := json.NewDecoder(authJSON).Decode(&dockerAuthConfigs); err != nil {
334
		return nil, err
335
	}
336

337
	// Now convert to the internal types.
338
	authConfigs := make(map[string]types.DockerAuthConfig)
339
	for server := range dockerAuthConfigs {
340
		authConfigs[server] = dockerAuthToImageAuth(dockerAuthConfigs[server])
341
	}
342
	return authConfigs, nil
343
}
344

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

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

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

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