compat handlers: add X-Registry-Auth header support

* Support the `X-Registry-Auth` http-request header.

 * The content of the header is a base64 encoded JSON payload which can
   either be a single auth config or a map of auth configs (user+pw or
   token) with the corresponding registries being the keys.  Vanilla
   Docker, projectatomic Docker and the bindings are transparantly
   supported.

 * Add a hidden `--registries-conf` flag.  Buildah exposes the same
   flag, mostly for testing purposes.

 * Do all credential parsing in the client (i.e., `cmd/podman`) pass
   the username and password in the backend instead of unparsed
   credentials.

 * Add a `pkg/auth` which handles most of the heavy lifting.

 * Go through the authentication-handling code of most commands, bindings
   and endpoints.  Migrate them to the new code and fix issues as seen.
   A final evaluation and more tests is still required *after* this
   change.

 * The manifest-push endpoint is missing certain parameters and should
   use the ABI function instead.  Adding auth-support isn't really
   possible without these parts working.

 * The container commands and endpoints (i.e., create and run) have not
   been changed yet.  The APIs don't yet account for the authfile.

 * Add authentication tests to `pkg/bindings`.

Fixes: #6384
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2020-05-13 13:44:29 +02:00
parent 78c38460eb
commit dc80267b59
108 changed files with 2582 additions and 612 deletions

View File

@ -190,6 +190,7 @@ func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest strin
if err != nil {
return nil, "", err
}
logrus.Debugf("Content-Type from manifest GET is %q", res.Header.Get("Content-Type"))
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, "", errors.Wrapf(client.HandleErrorResponse(res), "Error reading manifest %s in %s", tagOrDigest, s.physicalRef.ref.Name())

View File

@ -172,7 +172,7 @@ func (m *OCI1) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*type
Architecture: v1.Architecture,
Os: v1.OS,
Layers: layerInfosToStrings(m.LayerInfos()),
Env: d1.Config.Env,
Env: v1.Config.Env,
}
return i, nil
}

View File

@ -7,6 +7,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/containers/image/v5/types"
@ -37,7 +38,12 @@ var (
xdgRuntimeDirPath = filepath.FromSlash("containers/auth.json")
dockerHomePath = filepath.FromSlash(".docker/config.json")
dockerLegacyHomePath = ".dockercfg"
nonLinuxAuthFilePath = filepath.FromSlash(".config/containers/auth.json")
// Note that the keyring support has been disabled as it was causing
// regressions. Before enabling, please revisit TODO(keyring) comments
// which need to be addressed if the need remerged to support the
// kernel keyring.
enableKeyring = false
// ErrNotLoggedIn is returned for users not logged into a registry
@ -73,6 +79,70 @@ func SetAuthentication(sys *types.SystemContext, registry, username, password st
})
}
// GetAllCredentials returns the registry credentials for all registries stored
// in either the auth.json file or the docker/config.json.
func GetAllCredentials(sys *types.SystemContext) (map[string]types.DockerAuthConfig, error) {
// Note: we need to read the auth files in the inverse order to prevent
// a priority inversion when writing to the map.
authConfigs := make(map[string]types.DockerAuthConfig)
paths := getAuthFilePaths(sys)
for i := len(paths) - 1; i >= 0; i-- {
path := paths[i]
// readJSONFile returns an empty map in case the path doesn't exist.
auths, err := readJSONFile(path.path, path.legacyFormat)
if err != nil {
return nil, errors.Wrapf(err, "error reading JSON file %q", path.path)
}
for registry, data := range auths.AuthConfigs {
conf, err := decodeDockerAuth(data)
if err != nil {
return nil, err
}
authConfigs[normalizeRegistry(registry)] = conf
}
// Credential helpers may override credentials from the auth file.
for registry, credHelper := range auths.CredHelpers {
username, password, err := getAuthFromCredHelper(credHelper, registry)
if err != nil {
if credentials.IsErrCredentialsNotFoundMessage(err.Error()) {
continue
}
return nil, err
}
conf := types.DockerAuthConfig{Username: username, Password: password}
authConfigs[normalizeRegistry(registry)] = conf
}
}
// TODO(keyring): if we ever reenable the keyring support, we had to
// query all credentials from the keyring here.
return authConfigs, nil
}
// getAuthFilePaths returns a slice of authPaths based on the system context
// in the order they should be searched. Note that some paths may not exist.
func getAuthFilePaths(sys *types.SystemContext) []authPath {
paths := []authPath{}
pathToAuth, lf, err := getPathToAuth(sys)
if err == nil {
paths = append(paths, authPath{path: pathToAuth, legacyFormat: lf})
} 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,
authPath{path: filepath.Join(homedir.Get(), dockerHomePath), legacyFormat: false},
authPath{path: filepath.Join(homedir.Get(), dockerLegacyHomePath), legacyFormat: true},
)
return paths
}
// GetCredentials returns the registry credentials stored in either auth.json
// file or .docker/config.json, including support for OAuth2 and IdentityToken.
// If an entry is not found, an empty struct is returned.
@ -93,21 +163,7 @@ func GetCredentials(sys *types.SystemContext, registry string) (types.DockerAuth
}
}
paths := []authPath{}
pathToAuth, lf, err := getPathToAuth(sys)
if err == nil {
paths = append(paths, authPath{path: pathToAuth, legacyFormat: lf})
} 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,
authPath{path: filepath.Join(homedir.Get(), dockerHomePath), legacyFormat: false},
authPath{path: filepath.Join(homedir.Get(), dockerLegacyHomePath), legacyFormat: true})
for _, path := range paths {
for _, path := range getAuthFilePaths(sys) {
authConfig, err := findAuthentication(registry, path.path, path.legacyFormat)
if err != nil {
logrus.Debugf("Credentials not found")
@ -189,10 +245,8 @@ func RemoveAllAuthentication(sys *types.SystemContext) error {
})
}
// getPath gets the path of the auth.json file
// The path can be overriden by the user if the overwrite-path flag is set
// 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
// getPathToAuth gets the path of the auth.json file used for reading and writting credentials
// returns the path, and a bool specifies whether the file is in legacy format
func getPathToAuth(sys *types.SystemContext) (string, bool, error) {
if sys != nil {
if sys.AuthFilePath != "" {
@ -205,6 +259,9 @@ func getPathToAuth(sys *types.SystemContext) (string, bool, error) {
return filepath.Join(sys.RootForImplicitAbsolutePaths, fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid())), false, nil
}
}
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
return filepath.Join(homedir.Get(), nonLinuxAuthFilePath), false, nil
}
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDir != "" {
@ -248,6 +305,13 @@ func readJSONFile(path string, legacyFormat bool) (dockerConfigFile, error) {
return dockerConfigFile{}, errors.Wrapf(err, "error unmarshaling JSON at %q", path)
}
if auths.AuthConfigs == nil {
auths.AuthConfigs = map[string]dockerAuthConfig{}
}
if auths.CredHelpers == nil {
auths.CredHelpers = make(map[string]string)
}
return auths, nil
}
@ -257,17 +321,15 @@ func modifyJSON(sys *types.SystemContext, editor func(auths *dockerConfigFile) (
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)
}
}
if legacyFormat {
return fmt.Errorf("writes to %s using legacy format are not supported", path)
}
dir := filepath.Dir(path)
if err = os.MkdirAll(dir, 0700); err != nil {
return err
}
auths, err := readJSONFile(path, false)
if err != nil {
return errors.Wrapf(err, "error reading JSON file %q", path)

View File

@ -17,11 +17,13 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/types"
"github.com/containers/storage/pkg/homedir"
"github.com/pkg/errors"
)
@ -34,6 +36,9 @@ var systemDefaultPolicyPath = builtinDefaultPolicyPath
// DO NOT change this, instead see systemDefaultPolicyPath above.
const builtinDefaultPolicyPath = "/etc/containers/policy.json"
// userPolicyFile is the path to the per user policy path.
var userPolicyFile = filepath.FromSlash(".config/containers/policy.json")
// InvalidPolicyFormatError is returned when parsing an invalid policy configuration.
type InvalidPolicyFormatError string
@ -53,13 +58,15 @@ func DefaultPolicy(sys *types.SystemContext) (*Policy, error) {
// defaultPolicyPath returns a path to the default policy of the system.
func defaultPolicyPath(sys *types.SystemContext) string {
if sys != nil {
if sys.SignaturePolicyPath != "" {
return sys.SignaturePolicyPath
}
if sys.RootForImplicitAbsolutePaths != "" {
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
}
if sys != nil && sys.SignaturePolicyPath != "" {
return sys.SignaturePolicyPath
}
userPolicyFilePath := filepath.Join(homedir.Get(), userPolicyFile)
if _, err := os.Stat(userPolicyFilePath); err == nil {
return userPolicyFilePath
}
if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
}
return systemDefaultPolicyPath
}

View File

@ -6,12 +6,12 @@ const (
// VersionMajor is for an API incompatible changes
VersionMajor = 5
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 4
VersionMinor = 5
// VersionPatch is for backwards-compatible bug fixes
VersionPatch = 4
VersionPatch = 0
// VersionDev indicates development branch. Releases will be empty string.
VersionDev = ""
VersionDev = "-dev"
)
// Version is the specification version that the package types support.