From 480020500db2b2ca5ab850c6657a5df4d0b98904 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 21 Feb 2019 05:35:37 -0800 Subject: [PATCH] types: Add SystemContext.GetAuth The previous system is difficult to configure, with several SystemContext properties feeding the auth-getting logic. It also assumed that there was either a single username/password (DockerAuthConfig) or that the auth information was on disk somewhere. With this commit, I'm pushing all of the complexity down into a user-supplied getter (falling back to the old logic if the getter is unset). That makes it easy to plug in your own alternative without worrying about the default logic. The Docker-config AuthStore has a public Config property to make it easy for callers to drop in their own configuration for read-only access without having to go through either the filesystem or JSON. I've made the backing types public to support that use-case, and set custom JSON serialization for the newly-public Auth structure to allow Go callers to avoid having to understand the base64 wrapping used in the JSON serialization. Ideally the auth interface would support challenge/response auth like [1], but for now it just uses Docker's "we'll be able to hard-code authorization for each authority" approach. URI authorities are specified in [2]. [1]: https://tools.ietf.org/html/rfc7235 [2]: https://tools.ietf.org/html/rfc3986#section-3.2 Signed-off-by: W. Trevor King --- credentials/single/single.go | 18 ++ docker/docker_client.go | 12 +- pkg/docker/config/config.go | 488 ++++++++++++++++++++----------- pkg/docker/config/config_test.go | 29 +- types/types.go | 16 +- 5 files changed, 381 insertions(+), 182 deletions(-) create mode 100644 credentials/single/single.go diff --git a/credentials/single/single.go b/credentials/single/single.go new file mode 100644 index 0000000000..40f587d75a --- /dev/null +++ b/credentials/single/single.go @@ -0,0 +1,18 @@ +// Package single implements a basic credentials helper with a single username and secret. +package single + +import ( + "github.com/docker/docker-credential-helpers/credentials" +) + +// AuthStore is a basic credentials store that holds a single entry. +type AuthStore credentials.Credentials + +// Get retrieves credentials from the store. +func (s *AuthStore) Get(serverURL string) (string, string, error) { + if s == nil || serverURL != s.ServerURL { + return "", "", credentials.NewErrCredentialsNotFound() + } + + return s.Username, s.Secret, nil +} diff --git a/docker/docker_client.go b/docker/docker_client.go index 43eb22ba22..52ec96d579 100644 --- a/docker/docker_client.go +++ b/docker/docker_client.go @@ -197,10 +197,17 @@ func dockerCertDir(sys *types.SystemContext, hostPort string) (string, error) { // “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection) func newDockerClientFromRef(sys *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) { registry := reference.Domain(ref.ref) - username, password, err := config.GetAuthentication(sys, reference.Domain(ref.ref)) + getAuth := sys.GetAuth + if getAuth == nil { + getAuth = func(serverURL string) (string, string, error) { + return config.GetAuthentication(sys, serverURL) + } + } + username, password, err := getAuth(registry) if err != nil { return nil, errors.Wrapf(err, "error getting username and password") } + sigBase, err := configuredSignatureStorageBase(sys, ref, write) if err != nil { return nil, err @@ -327,8 +334,7 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima v2Res := &V2Results{} v1Res := &V1Results{} - // Get credentials from authfile for the underlying hostname - username, password, err := config.GetAuthentication(sys, registry) + username, password, err := sys.GetAuth(registry) if err != nil { return nil, errors.Wrapf(err, "error getting username and password") } diff --git a/pkg/docker/config/config.go b/pkg/docker/config/config.go index 1f576253dc..bb2f8c49de 100644 --- a/pkg/docker/config/config.go +++ b/pkg/docker/config/config.go @@ -1,6 +1,7 @@ package config import ( + "bytes" "encoding/base64" "encoding/json" "fmt" @@ -8,22 +9,68 @@ import ( "os" "path/filepath" "strings" + "sync" "github.com/containers/image/types" helperclient "github.com/docker/docker-credential-helpers/client" "github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker/pkg/homedir" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) -type dockerAuthConfig struct { - Auth string `json:"auth,omitempty"` +// Auth holds per-URI-authority credentials. +type Auth struct { + Username string + Secret string } -type dockerConfigFile struct { - AuthConfigs map[string]dockerAuthConfig `json:"auths"` - CredHelpers map[string]string `json:"credHelpers,omitempty"` +// MarshalJSON interface for Auth. +func (auth Auth) MarshalJSON() ([]byte, error) { + return json.Marshal(base64.StdEncoding.EncodeToString([]byte(auth.Username + ":" + auth.Secret))) +} + +// UnmarshalJSON interface for Auth. +func (auth *Auth) UnmarshalJSON(b []byte) error { + var base64auth string + err := json.Unmarshal(b, &base64auth) + if err != nil { + return err + } + + decoded, err := base64.StdEncoding.DecodeString(base64auth) + if err != nil { + return err + } + + parts := strings.SplitN(string(decoded), ":", 2) + if len(parts) != 2 { + // if it's invalid just skip, as docker does + auth.Username = "" + auth.Secret = "" + return nil + } + + auth.Username = parts[0] + auth.Secret = strings.Trim(parts[1], "\x00") + return nil +} + +// AuthEntry holds a value from the configurations Auths map. +type AuthEntry struct { + // Auth holds the the credentials for this auth entry. + Auth Auth `json:"auth"` + + // Email holds an email address for this auth entry. + Email string `json:"email,omitempty"` +} + +// Config holds the full authorization configuration. +type Config struct { + // Auths holds a map of per-URI-authority Auth values. + Auths map[string]AuthEntry `json:"auths"` + + // CredHelpers holds a map of per-URI-authority credential helpers. + CredHelpers map[string]string `json:"credHelpers,omitempty"` } var ( @@ -37,111 +84,260 @@ var ( ErrNotLoggedIn = errors.New("not logged in") ) +// AuthStore implements a credentials store based on Docker's +// config.json file format. It stores the data in memory, so multiple +// reads will only hit the disk once. That means it is not safe to +// simultaneously edit different AuthStore instances which are backed +// by the same file. The store implements internal locking so +// concurrent access to a single AuthStore instance is safe (although +// direct access to the Config property bypasses the lock). +type AuthStore struct { + // Path holks the path to the config file. If left empty, it will + // be poplutated with a reasonable default or auto-detected existing + // file during the first save or load call. + Path string + + // Config holds the structured authorization configuration. + Config *Config + + original []byte + sync.Mutex +} + +// Add appends credentials to the store. +func (s *AuthStore) Add(creds *credentials.Credentials) error { + s.Lock() + defer s.Unlock() + + if s.Config == nil { + err := s.load() + if err != nil && !credentials.IsErrCredentialsNotFound(err) { + return err + } + } + + if credHelper, exists := s.Config.CredHelpers[creds.ServerURL]; exists { + helperName := fmt.Sprintf("docker-credential-%s", credHelper) + p := helperclient.NewShellProgramFunc(helperName) + return helperclient.Store(p, creds) + } + + newAuth := AuthEntry{ + Auth: Auth{ + Username: creds.Username, + Secret: creds.Secret, + }, + } + if newAuth != s.Config.Auths[creds.ServerURL] { + s.Config.Auths[creds.ServerURL] = newAuth + return s.save() + } + + return nil +} + +// Get retrieves credentials from the store. It returns the username +// and secret as strings. +func (s *AuthStore) Get(serverURL string) (string, string, error) { + s.Lock() + defer s.Unlock() + + if s.Config == nil { + err := s.load() + if err != nil { + return "", "", err + } + } + + // First try cred helpers. They should always be normalized. + if credHelper, exists := s.Config.CredHelpers[serverURL]; exists { + helperName := fmt.Sprintf("docker-credential-%s", credHelper) + p := helperclient.NewShellProgramFunc(helperName) + creds, err := helperclient.Get(p, serverURL) + if err != nil { + return "", "", err + } + return creds.Username, creds.Secret, nil + } + + // I'm feeling lucky + if val, exists := s.Config.Auths[serverURL]; exists { + return val.Auth.Username, val.Auth.Secret, nil + } + + // bad luck; let's normalize the entries first + serverURL = normalizeRegistry(serverURL) + normalizedAuths := map[string]Auth{} + for k, v := range s.Config.Auths { + normalizedAuths[normalizeRegistry(k)] = v.Auth + } + if val, exists := normalizedAuths[serverURL]; exists { + return val.Username, val.Secret, nil + } + return "", "", credentials.NewErrCredentialsNotFound() +} + +// Delete removes credentials from the store. +func (s *AuthStore) Delete(serverURL string) error { + s.Lock() + defer s.Unlock() + + if s.Config == nil { + err := s.load() + if err != nil { + if credentials.IsErrCredentialsNotFound(err) { + return nil + } + return err + } + } + + // First try cred helpers. + if credHelper, exists := s.Config.CredHelpers[serverURL]; exists { + helperName := fmt.Sprintf("docker-credential-%s", credHelper) + p := helperclient.NewShellProgramFunc(helperName) + return helperclient.Erase(p, serverURL) + } + + changed := false + if _, ok := s.Config.Auths[serverURL]; ok { + delete(s.Config.Auths, serverURL) + changed = true + } else if _, ok := s.Config.Auths[normalizeRegistry(serverURL)]; ok { + delete(s.Config.Auths, normalizeRegistry(serverURL)) + changed = true + } + if changed { + return s.save() + } + + return nil +} + // SetAuthentication stores the username and password in the auth.json file +// +// Deprecated: Use an AuthStore. func SetAuthentication(sys *types.SystemContext, registry, username, password string) error { - return modifyJSON(sys, func(auths *dockerConfigFile) (bool, error) { - if ch, exists := auths.CredHelpers[registry]; exists { - return false, setAuthToCredHelper(ch, registry, username, password) - } + path, err := getPathWithContext(sys) + if err != nil { + return err + } - creds := base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) - newCreds := dockerAuthConfig{Auth: creds} - auths.AuthConfigs[registry] = newCreds - return true, nil + authStore := &AuthStore{Path: path} + return authStore.Add(&credentials.Credentials{ + ServerURL: registry, + Username: username, + Secret: password, }) } // GetAuthentication returns the registry credentials stored in // either auth.json file or .docker/config.json // If an entry is not found empty strings are returned for the username and password +// +// Deprecated: Use an AuthStore. func GetAuthentication(sys *types.SystemContext, registry string) (string, string, error) { if sys != nil && sys.DockerAuthConfig != nil { return sys.DockerAuthConfig.Username, sys.DockerAuthConfig.Password, nil } - dockerLegacyPath := filepath.Join(homedir.Get(), dockerLegacyHomePath) - var paths []string - pathToAuth, err := getPathToAuth(sys) + path, err := getPathWithContext(sys) + if err != nil { + return "", "", err + } + + authStore := &AuthStore{Path: path} + username, secret, err := authStore.Get(registry) if err == nil { - paths = append(paths, pathToAuth) - } else { - // Error means that the path set for XDG_RUNTIME_DIR does not exist - // but we don't want to completely fail in the case that the user is pulling a public image - // Logging the error as a warning instead and moving on to pulling the image - logrus.Warnf("%v: Trying to pull image in the event that it is a public image.", err) - } - paths = append(paths, filepath.Join(homedir.Get(), dockerHomePath), dockerLegacyPath) - - for _, path := range paths { - legacyFormat := path == dockerLegacyPath - username, password, err := findAuthentication(registry, path, legacyFormat) + return username, secret, nil + } + if !credentials.IsErrCredentialsNotFound(err) { + return "", "", err + } + + home := homedir.Get() + for _, relPath := range []string{dockerHomePath, dockerLegacyHomePath} { + path := filepath.Join(home, relPath) + raw, err := ioutil.ReadFile(path) if err != nil { + if os.IsNotExist(err) { + continue + } return "", "", err } - if username != "" && password != "" { - return username, password, nil + + if relPath == dockerLegacyHomePath { + authStore.Config = &Config{ + CredHelpers: map[string]string{}, + } + + if err = json.Unmarshal(raw, &authStore.Config.Auths); err != nil { + return "", "", errors.Wrapf(err, "error unmarshaling JSON at %q", path) + } + } else { + if err = json.Unmarshal(raw, &authStore.Config); err != nil { + return "", "", errors.Wrapf(err, "error unmarshaling JSON at %q\n%s", path, string(raw)) + } + } + + username, secret, err = authStore.Get(registry) + if err == nil { + return username, secret, nil } } + return "", "", nil } // GetUserLoggedIn returns the username logged in to registry from either // auth.json or XDG_RUNTIME_DIR // Used to tell the user if someone is logged in to the registry when logging in +// +// Deprecated: Use an AuthStore. func GetUserLoggedIn(sys *types.SystemContext, registry string) (string, error) { - path, err := getPathToAuth(sys) + username, _, err := GetAuthentication(sys, registry) if err != nil { return "", err } - username, _, _ := findAuthentication(registry, path, false) - if username != "" { - return username, nil - } - return "", nil + return username, nil } // RemoveAuthentication deletes the credentials stored in auth.json +// +// Deprecated: Use an AuthStore. func RemoveAuthentication(sys *types.SystemContext, registry string) error { - return modifyJSON(sys, func(auths *dockerConfigFile) (bool, error) { - // First try cred helpers. - if ch, exists := auths.CredHelpers[registry]; exists { - return false, deleteAuthFromCredHelper(ch, registry) - } + path, err := getPathWithContext(sys) + if err != nil { + return err + } - if _, ok := auths.AuthConfigs[registry]; ok { - delete(auths.AuthConfigs, registry) - } else if _, ok := auths.AuthConfigs[normalizeRegistry(registry)]; ok { - delete(auths.AuthConfigs, normalizeRegistry(registry)) - } else { - return false, ErrNotLoggedIn - } - return true, nil - }) + authStore := &AuthStore{Path: path} + return authStore.Delete(registry) } // RemoveAllAuthentication deletes all the credentials stored in auth.json +// +// Deprecated: Use an AuthStore. func RemoveAllAuthentication(sys *types.SystemContext) error { - return modifyJSON(sys, func(auths *dockerConfigFile) (bool, error) { - auths.CredHelpers = make(map[string]string) - auths.AuthConfigs = make(map[string]dockerAuthConfig) - return true, nil - }) + path, err := getPathWithContext(sys) + if err != nil { + return err + } + + authStore := &AuthStore{ + Path: path, + Config: &Config{ + Auths: map[string]AuthEntry{}, + CredHelpers: map[string]string{}, + }, + } + return authStore.save() } -// getPath gets the path of the auth.json file -// The path can be overriden by the user if the overwrite-path flag is set +// getPath gets the path of the credentials JSON file. // If the flag is not set and XDG_RUNTIME_DIR is set, the auth.json file is saved in XDG_RUNTIME_DIR/containers // Otherwise, the auth.json file is stored in /run/containers/UID -func getPathToAuth(sys *types.SystemContext) (string, error) { - if sys != nil { - if sys.AuthFilePath != "" { - return sys.AuthFilePath, nil - } - if sys.RootForImplicitAbsolutePaths != "" { - return filepath.Join(sys.RootForImplicitAbsolutePaths, fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid())), nil - } - } - +func getPath() (string, error) { runtimeDir := os.Getenv("XDG_RUNTIME_DIR") if runtimeDir != "" { // This function does not in general need to separately check that the returned path exists; that’s racy, and callers will fail accessing the file anyway. @@ -158,141 +354,83 @@ func getPathToAuth(sys *types.SystemContext) (string, error) { return fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid()), nil } -// readJSONFile unmarshals the authentications stored in the auth.json file and returns it -// or returns an empty dockerConfigFile data structure if auth.json does not exist -// if the file exists and is empty, readJSONFile returns an error -func readJSONFile(path string, legacyFormat bool) (dockerConfigFile, error) { - var auths dockerConfigFile - - raw, err := ioutil.ReadFile(path) - if err != nil { - if os.IsNotExist(err) { - auths.AuthConfigs = map[string]dockerAuthConfig{} - return auths, nil - } - return dockerConfigFile{}, err - } - - if legacyFormat { - if err = json.Unmarshal(raw, &auths.AuthConfigs); err != nil { - return dockerConfigFile{}, errors.Wrapf(err, "error unmarshaling JSON at %q", path) +func getPathWithContext(sys *types.SystemContext) (string, error) { + if sys != nil { + if sys.AuthFilePath != "" { + return sys.AuthFilePath, nil + } else if sys.RootForImplicitAbsolutePaths != "" { + return filepath.Join(sys.RootForImplicitAbsolutePaths, fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid())), nil } - return auths, nil } - if err = json.Unmarshal(raw, &auths); err != nil { - return dockerConfigFile{}, errors.Wrapf(err, "error unmarshaling JSON at %q", path) - } - - return auths, nil + return getPath() } -// modifyJSON writes to auth.json if the dockerConfigFile has been updated -func modifyJSON(sys *types.SystemContext, editor func(auths *dockerConfigFile) (bool, error)) error { - path, err := getPathToAuth(sys) - if err != nil { - return err - } - - dir := filepath.Dir(path) - if _, err := os.Stat(dir); os.IsNotExist(err) { - if err = os.MkdirAll(dir, 0700); err != nil { - return errors.Wrapf(err, "error creating directory %q", dir) +func (s *AuthStore) load() error { + var err error + if s.Path == "" { + s.Path, err = getPath() + if err != nil { + return err } } - auths, err := readJSONFile(path, false) - if err != nil { - return errors.Wrapf(err, "error reading JSON file %q", path) - } - - updated, err := editor(&auths) + s.original, err = ioutil.ReadFile(s.Path) if err != nil { - return errors.Wrapf(err, "error updating %q", path) - } - if updated { - newData, err := json.MarshalIndent(auths, "", "\t") - if err != nil { - return errors.Wrapf(err, "error marshaling JSON %q", path) + if os.IsNotExist(err) { + s.Config = &Config{ + Auths: map[string]AuthEntry{}, + CredHelpers: map[string]string{}, + } + return credentials.NewErrCredentialsNotFound() } + return err + } - if err = ioutil.WriteFile(path, newData, 0755); err != nil { - return errors.Wrapf(err, "error writing to file %q", path) - } + if err = json.Unmarshal(s.original, &s.Config); err != nil { + return errors.Wrapf(err, "error unmarshaling JSON at %q", s.Path) } return nil } -func getAuthFromCredHelper(credHelper, registry string) (string, string, error) { - helperName := fmt.Sprintf("docker-credential-%s", credHelper) - p := helperclient.NewShellProgramFunc(helperName) - creds, err := helperclient.Get(p, registry) - if err != nil { - return "", "", err +func (s *AuthStore) save() error { + var err error + if s.Path == "" { + s.Path, err = getPath() + if err != nil { + return err + } } - return creds.Username, creds.Secret, nil -} -func setAuthToCredHelper(credHelper, registry, username, password string) error { - helperName := fmt.Sprintf("docker-credential-%s", credHelper) - p := helperclient.NewShellProgramFunc(helperName) - creds := &credentials.Credentials{ - ServerURL: registry, - Username: username, - Secret: password, + dir := filepath.Dir(s.Path) + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err = os.MkdirAll(dir, 0700); err != nil { + return errors.Wrapf(err, "error creating directory %q", dir) + } } - return helperclient.Store(p, creds) -} - -func deleteAuthFromCredHelper(credHelper, registry string) error { - helperName := fmt.Sprintf("docker-credential-%s", credHelper) - p := helperclient.NewShellProgramFunc(helperName) - return helperclient.Erase(p, registry) -} -// findAuthentication looks for auth of registry in path -func findAuthentication(registry, path string, legacyFormat bool) (string, string, error) { - auths, err := readJSONFile(path, legacyFormat) - if err != nil { - return "", "", errors.Wrapf(err, "error reading JSON file %q", path) + // FIXME: this comparison is racy without a flock guard or similar + data, err := ioutil.ReadFile(s.Path) + if err != nil && !os.IsNotExist(err) { + return err } - // First try cred helpers. They should always be normalized. - if ch, exists := auths.CredHelpers[registry]; exists { - return getAuthFromCredHelper(ch, registry) + if !bytes.Equal(data, s.original) { + return errors.Errorf("%q modified since we loaded it", s.Path) } - // I'm feeling lucky - if val, exists := auths.AuthConfigs[registry]; exists { - return decodeDockerAuth(val.Auth) + newConfig, err := json.MarshalIndent(s.Config, "", "\t") + if err != nil { + return errors.Wrapf(err, "error marshaling JSON %q", s.Path) } - // bad luck; let's normalize the entries first - registry = normalizeRegistry(registry) - normalizedAuths := map[string]dockerAuthConfig{} - for k, v := range auths.AuthConfigs { - normalizedAuths[normalizeRegistry(k)] = v - } - if val, exists := normalizedAuths[registry]; exists { - return decodeDockerAuth(val.Auth) + tempPath := s.Path + ".tmp" + if err = ioutil.WriteFile(tempPath, newConfig, 0600); err != nil { + return err } - return "", "", nil -} -func decodeDockerAuth(s string) (string, string, error) { - decoded, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return "", "", err - } - parts := strings.SplitN(string(decoded), ":", 2) - if len(parts) != 2 { - // if it's invalid just skip, as docker does - return "", "", nil - } - user := parts[0] - password := strings.Trim(parts[1], "\x00") - return user, password, nil + return os.Rename(tempPath, s.Path) } // convertToHostname converts a registry url which has http|https prepended diff --git a/pkg/docker/config/config_test.go b/pkg/docker/config/config_test.go index fde5ca32d6..f4117e3fb1 100644 --- a/pkg/docker/config/config_test.go +++ b/pkg/docker/config/config_test.go @@ -17,7 +17,32 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetPathToAuth(t *testing.T) { +func TestAuthJSON(t *testing.T) { + auth := Auth{ + Username: "alice", + Secret: "password", + } + encoded := "\"YWxpY2U6cGFzc3dvcmQ=\"" + + actual, err := json.Marshal(auth) + assert.NoError(t, err) + if err == nil { + assert.Equal(t, encoded, string(actual)) + } + + var decoded Auth + err = json.Unmarshal([]byte(encoded), &decoded) + assert.NoError(t, err) + if err == nil { + assert.Equal(t, auth, decoded) + } + + err = json.Unmarshal([]byte("\"\""), &decoded) + assert.NoError(t, err) + assert.Equal(t, Auth{}, decoded) +} + +func TestGetPathWithContext(t *testing.T) { uid := fmt.Sprintf("%d", os.Getuid()) tmpDir, err := ioutil.TempDir("", "TestGetPathToAuth") @@ -54,7 +79,7 @@ func TestGetPathToAuth(t *testing.T) { } else { os.Unsetenv("XDG_RUNTIME_DIR") } - res, err := getPathToAuth(c.sys) + res, err := getPathWithContext(c.sys) if c.expected == "" { assert.Error(t, err) } else { diff --git a/types/types.go b/types/types.go index 9fdab2314a..47f60c8fc4 100644 --- a/types/types.go +++ b/types/types.go @@ -401,11 +401,17 @@ type ImageInspectInfo struct { } // DockerAuthConfig contains authorization information for connecting to a registry. +// +// Deprecated: Use github.com/containers/image/credentials/single's AuthStore instead. type DockerAuthConfig struct { Username string Password string } +// AuthGetter retrieves credentials for HTTP(S) access. It returns +// the username and secret as strings. +type AuthGetter func(serverURL string) (string, string, error) + // OptionalBool is a boolean with an additional undefined value, which is meant // to be used in the context of user input to distinguish between a // user-specified value and a default value. @@ -451,7 +457,9 @@ type SystemContext struct { RegistriesDirPath string // Path to the system-wide registries configuration file SystemRegistriesConfPath string - // If not "", overrides the default path for the authentication file + // If not "", overrides the default path for the authentication file. Ignored for reading if AuthConfig is not "". + // + // Deprecated: Use the GetAuth property and github.com/containers/image/pkg/docker/config's AuthStore instead. AuthFilePath string // If not "", overrides the use of platform.GOARCH when choosing an image or verifying architecture match. ArchitectureChoice string @@ -459,6 +467,8 @@ type SystemContext struct { OSChoice string // If not "", overrides the system's default directory containing a blob info cache. BlobInfoCacheDir string + // GetAuth retrieves credentials for authenticated HTTP(S) access. + GetAuth AuthGetter // Additional tags when creating or copying a docker-archive. DockerArchiveAdditionalTags []reference.NamedTagged @@ -485,7 +495,9 @@ type SystemContext struct { DockerPerHostCertDirPath string // Allow contacting docker registries over HTTP, or HTTPS with failed TLS verification. Note that this does not affect other TLS connections. DockerInsecureSkipTLSVerify OptionalBool - // if nil, the library tries to parse ~/.docker/config.json to retrieve credentials + // if nil, the library tries to parse ~/.docker/config.json to retrieve credentials. + // + // Deprecated: Use the GetAuth property and github.com/containers/image/credentials/single's AuthStore instead. DockerAuthConfig *DockerAuthConfig // if not "", an User-Agent header is added to each request when contacting a registry. DockerRegistryUserAgent string