podman

Форк
0
395 строк · 10.8 Кб
1
// Copyright 2015 go-dockerclient authors. All rights reserved.
2
// Use of this source code is governed by a BSD-style
3
// license that can be found in the LICENSE file.
4

5
package docker
6

7
import (
8
	"bytes"
9
	"context"
10
	"encoding/base64"
11
	"encoding/json"
12
	"errors"
13
	"io"
14
	"net/http"
15
	"os"
16
	"os/exec"
17
	"path"
18
	"strings"
19
)
20

21
// ErrCannotParseDockercfg is the error returned by NewAuthConfigurations when the dockercfg cannot be parsed.
22
var ErrCannotParseDockercfg = errors.New("failed to read authentication from dockercfg")
23

24
// AuthConfiguration represents authentication options to use in the PushImage
25
// method. It represents the authentication in the Docker index server.
26
type AuthConfiguration struct {
27
	Username      string `json:"username,omitempty"`
28
	Password      string `json:"password,omitempty"`
29
	Email         string `json:"email,omitempty"`
30
	ServerAddress string `json:"serveraddress,omitempty"`
31

32
	// IdentityToken can be supplied with the identitytoken response of the AuthCheck call
33
	// see https://pkg.go.dev/github.com/docker/docker/api/types?tab=doc#AuthConfig
34
	// It can be used in place of password not in conjunction with it
35
	IdentityToken string `json:"identitytoken,omitempty"`
36

37
	// RegistryToken can be supplied with the registrytoken
38
	RegistryToken string `json:"registrytoken,omitempty"`
39
}
40

41
func (c AuthConfiguration) isEmpty() bool {
42
	return c == AuthConfiguration{}
43
}
44

45
func (c AuthConfiguration) headerKey() string {
46
	return "X-Registry-Auth"
47
}
48

49
// AuthConfigurations represents authentication options to use for the
50
// PushImage method accommodating the new X-Registry-Config header
51
type AuthConfigurations struct {
52
	Configs map[string]AuthConfiguration `json:"configs"`
53
}
54

55
func (c AuthConfigurations) isEmpty() bool {
56
	return len(c.Configs) == 0
57
}
58

59
func (AuthConfigurations) headerKey() string {
60
	return "X-Registry-Config"
61
}
62

63
// merge updates the configuration. If a key is defined in both maps, the one
64
// in c.Configs takes precedence.
65
func (c *AuthConfigurations) merge(other AuthConfigurations) {
66
	for k, v := range other.Configs {
67
		if c.Configs == nil {
68
			c.Configs = make(map[string]AuthConfiguration)
69
		}
70
		if _, ok := c.Configs[k]; !ok {
71
			c.Configs[k] = v
72
		}
73
	}
74
}
75

76
// AuthConfigurations119 is used to serialize a set of AuthConfigurations
77
// for Docker API >= 1.19.
78
type AuthConfigurations119 map[string]AuthConfiguration
79

80
func (c AuthConfigurations119) isEmpty() bool {
81
	return len(c) == 0
82
}
83

84
func (c AuthConfigurations119) headerKey() string {
85
	return "X-Registry-Config"
86
}
87

88
// dockerConfig represents a registry authentation configuration from the
89
// .dockercfg file.
90
type dockerConfig struct {
91
	Auth          string `json:"auth"`
92
	Email         string `json:"email"`
93
	IdentityToken string `json:"identitytoken"`
94
	RegistryToken string `json:"registrytoken"`
95
}
96

97
// NewAuthConfigurationsFromFile returns AuthConfigurations from a path containing JSON
98
// in the same format as the .dockercfg file.
99
func NewAuthConfigurationsFromFile(path string) (*AuthConfigurations, error) {
100
	r, err := os.Open(path)
101
	if err != nil {
102
		return nil, err
103
	}
104
	return NewAuthConfigurations(r)
105
}
106

107
func cfgPaths(dockerConfigEnv string, homeEnv string) []string {
108
	if dockerConfigEnv != "" {
109
		return []string{
110
			path.Join(dockerConfigEnv, "plaintext-passwords.json"),
111
			path.Join(dockerConfigEnv, "config.json"),
112
		}
113
	}
114
	if homeEnv != "" {
115
		return []string{
116
			path.Join(homeEnv, ".docker", "plaintext-passwords.json"),
117
			path.Join(homeEnv, ".docker", "config.json"),
118
			path.Join(homeEnv, ".dockercfg"),
119
		}
120
	}
121
	return nil
122
}
123

124
// NewAuthConfigurationsFromDockerCfg returns AuthConfigurations from system
125
// config files. The following files are checked in the order listed:
126
//
127
// If the environment variable DOCKER_CONFIG is set to a non-empty string:
128
//
129
// - $DOCKER_CONFIG/plaintext-passwords.json
130
// - $DOCKER_CONFIG/config.json
131
//
132
// Otherwise, it looks for files in the $HOME directory and the legacy
133
// location:
134
//
135
// - $HOME/.docker/plaintext-passwords.json
136
// - $HOME/.docker/config.json
137
// - $HOME/.dockercfg
138
func NewAuthConfigurationsFromDockerCfg() (*AuthConfigurations, error) {
139
	pathsToTry := cfgPaths(os.Getenv("DOCKER_CONFIG"), os.Getenv("HOME"))
140
	if len(pathsToTry) < 1 {
141
		return nil, errors.New("no docker configuration found")
142
	}
143
	return newAuthConfigurationsFromDockerCfg(pathsToTry)
144
}
145

146
func newAuthConfigurationsFromDockerCfg(pathsToTry []string) (*AuthConfigurations, error) {
147
	var result *AuthConfigurations
148
	var auths *AuthConfigurations
149
	var err error
150
	for _, path := range pathsToTry {
151
		auths, err = NewAuthConfigurationsFromFile(path)
152
		if err != nil {
153
			continue
154
		}
155

156
		if result == nil {
157
			result = auths
158
		} else {
159
			result.merge(*auths)
160
		}
161
	}
162

163
	if result != nil {
164
		return result, nil
165
	}
166
	return result, err
167
}
168

169
// NewAuthConfigurations returns AuthConfigurations from a JSON encoded string in the
170
// same format as the .dockercfg file.
171
func NewAuthConfigurations(r io.Reader) (*AuthConfigurations, error) {
172
	var auth *AuthConfigurations
173
	confs, err := parseDockerConfig(r)
174
	if err != nil {
175
		return nil, err
176
	}
177
	auth, err = authConfigs(confs)
178
	if err != nil {
179
		return nil, err
180
	}
181
	return auth, nil
182
}
183

184
func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) {
185
	buf := new(bytes.Buffer)
186
	buf.ReadFrom(r)
187
	byteData := buf.Bytes()
188

189
	confsWrapper := struct {
190
		Auths map[string]dockerConfig `json:"auths"`
191
	}{}
192
	if err := json.Unmarshal(byteData, &confsWrapper); err == nil {
193
		if len(confsWrapper.Auths) > 0 {
194
			return confsWrapper.Auths, nil
195
		}
196
	}
197

198
	var confs map[string]dockerConfig
199
	if err := json.Unmarshal(byteData, &confs); err != nil {
200
		return nil, err
201
	}
202
	return confs, nil
203
}
204

205
// authConfigs converts a dockerConfigs map to a AuthConfigurations object.
206
func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
207
	c := &AuthConfigurations{
208
		Configs: make(map[string]AuthConfiguration),
209
	}
210

211
	for reg, conf := range confs {
212
		if conf.Auth == "" {
213
			continue
214
		}
215

216
		// support both padded and unpadded encoding
217
		data, err := base64.StdEncoding.DecodeString(conf.Auth)
218
		if err != nil {
219
			data, err = base64.StdEncoding.WithPadding(base64.NoPadding).DecodeString(conf.Auth)
220
		}
221
		if err != nil {
222
			return nil, errors.New("error decoding plaintext credentials")
223
		}
224

225
		userpass := strings.SplitN(string(data), ":", 2)
226
		if len(userpass) != 2 {
227
			return nil, ErrCannotParseDockercfg
228
		}
229

230
		authConfig := AuthConfiguration{
231
			Email:         conf.Email,
232
			Username:      userpass[0],
233
			Password:      userpass[1],
234
			ServerAddress: reg,
235
		}
236

237
		// if identitytoken provided then zero the password and set it
238
		if conf.IdentityToken != "" {
239
			authConfig.Password = ""
240
			authConfig.IdentityToken = conf.IdentityToken
241
		}
242

243
		// if registrytoken provided then zero the password and set it
244
		if conf.RegistryToken != "" {
245
			authConfig.Password = ""
246
			authConfig.RegistryToken = conf.RegistryToken
247
		}
248
		c.Configs[reg] = authConfig
249
	}
250

251
	return c, nil
252
}
253

254
// AuthStatus returns the authentication status for Docker API versions >= 1.23.
255
type AuthStatus struct {
256
	Status        string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"`
257
	IdentityToken string `json:"IdentityToken,omitempty" yaml:"IdentityToken,omitempty" toml:"IdentityToken,omitempty"`
258
}
259

260
// AuthCheck validates the given credentials. It returns nil if successful.
261
//
262
// For Docker API versions >= 1.23, the AuthStatus struct will be populated, otherwise it will be empty.`
263
//
264
// See https://goo.gl/6nsZkH for more details.
265
func (c *Client) AuthCheck(conf *AuthConfiguration) (AuthStatus, error) {
266
	return c.AuthCheckWithContext(conf, context.TODO())
267
}
268

269
// AuthCheckWithContext validates the given credentials. It returns nil if successful. The context object
270
// can be used to cancel the request.
271
//
272
// For Docker API versions >= 1.23, the AuthStatus struct will be populated, otherwise it will be empty.
273
//
274
// See https://goo.gl/6nsZkH for more details.
275
func (c *Client) AuthCheckWithContext(conf *AuthConfiguration, ctx context.Context) (AuthStatus, error) {
276
	var authStatus AuthStatus
277
	if conf == nil {
278
		return authStatus, errors.New("conf is nil")
279
	}
280
	resp, err := c.do(http.MethodPost, "/auth", doOptions{data: conf, context: ctx})
281
	if err != nil {
282
		return authStatus, err
283
	}
284
	defer resp.Body.Close()
285
	data, err := io.ReadAll(resp.Body)
286
	if err != nil {
287
		return authStatus, err
288
	}
289
	if len(data) == 0 {
290
		return authStatus, nil
291
	}
292
	if err := json.Unmarshal(data, &authStatus); err != nil {
293
		return authStatus, err
294
	}
295
	return authStatus, nil
296
}
297

298
// helperCredentials represents credentials commit from an helper
299
type helperCredentials struct {
300
	Username string `json:"Username,omitempty"`
301
	Secret   string `json:"Secret,omitempty"`
302
}
303

304
// NewAuthConfigurationsFromCredsHelpers returns AuthConfigurations from
305
// installed credentials helpers
306
func NewAuthConfigurationsFromCredsHelpers(registry string) (*AuthConfiguration, error) {
307
	// Load docker configuration file in order to find a possible helper provider
308
	pathsToTry := cfgPaths(os.Getenv("DOCKER_CONFIG"), os.Getenv("HOME"))
309
	if len(pathsToTry) < 1 {
310
		return nil, errors.New("no docker configuration found")
311
	}
312

313
	provider, err := getHelperProviderFromDockerCfg(pathsToTry, registry)
314
	if err != nil {
315
		return nil, err
316
	}
317

318
	c, err := getCredentialsFromHelper(provider, registry)
319
	if err != nil {
320
		return nil, err
321
	}
322

323
	creds := new(AuthConfiguration)
324
	creds.Username = c.Username
325
	creds.Password = c.Secret
326
	return creds, nil
327
}
328

329
func getHelperProviderFromDockerCfg(pathsToTry []string, registry string) (string, error) {
330
	for _, path := range pathsToTry {
331
		content, err := os.ReadFile(path)
332
		if err != nil {
333
			// if we can't read the file keep going
334
			continue
335
		}
336

337
		provider, err := parseCredsDockerConfig(content, registry)
338
		if err != nil {
339
			continue
340
		}
341
		if provider != "" {
342
			return provider, nil
343
		}
344
	}
345
	return "", errors.New("no docker credentials provider found")
346
}
347

348
func parseCredsDockerConfig(config []byte, registry string) (string, error) {
349
	creds := struct {
350
		CredsStore  string            `json:"credsStore,omitempty"`
351
		CredHelpers map[string]string `json:"credHelpers,omitempty"`
352
	}{}
353
	err := json.Unmarshal(config, &creds)
354
	if err != nil {
355
		return "", err
356
	}
357

358
	provider, ok := creds.CredHelpers[registry]
359
	if ok {
360
		return provider, nil
361
	}
362
	return creds.CredsStore, nil
363
}
364

365
// Run and parse the found credential helper
366
func getCredentialsFromHelper(provider string, registry string) (*helperCredentials, error) {
367
	helpercreds, err := runDockerCredentialsHelper(provider, registry)
368
	if err != nil {
369
		return nil, err
370
	}
371

372
	c := new(helperCredentials)
373
	err = json.Unmarshal(helpercreds, c)
374
	if err != nil {
375
		return nil, err
376
	}
377

378
	return c, nil
379
}
380

381
func runDockerCredentialsHelper(provider string, registry string) ([]byte, error) {
382
	cmd := exec.Command("docker-credential-"+provider, "get")
383

384
	var stdout bytes.Buffer
385

386
	cmd.Stdin = bytes.NewBuffer([]byte(registry))
387
	cmd.Stdout = &stdout
388

389
	err := cmd.Run()
390
	if err != nil {
391
		return nil, err
392
	}
393

394
	return stdout.Bytes(), nil
395
}
396

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

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

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

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