diff --git a/docker/docker_image_src.go b/docker/docker_image_src.go index a2b6dbed78..c8f6ba3055 100644 --- a/docker/docker_image_src.go +++ b/docker/docker_image_src.go @@ -1,7 +1,9 @@ package docker import ( + "bytes" "context" + "encoding/json" "errors" "fmt" "io" @@ -11,6 +13,7 @@ import ( "net/http" "net/url" "os" + "os/exec" "strings" "sync" @@ -162,6 +165,34 @@ func newImageSourceAttempt(ctx context.Context, sys *types.SystemContext, logica client.Close() return nil, err } + + if h, err := sysregistriesv2.AdditionalLayerStoreAuthHelper(endpointSys); err == nil && h != "" { + acf := map[string]struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + IdentityToken string `json:"identityToken,omitempty"` + }{ + physicalRef.ref.String(): { + Username: client.auth.Username, + Password: client.auth.Password, + IdentityToken: client.auth.IdentityToken, + }, + } + acfD, err := json.Marshal(acf) + if err != nil { + logrus.Warnf("failed to marshal auth config: %v", err) + } else { + cmd := exec.Command(h) + cmd.Stdin = bytes.NewReader(acfD) + if err := cmd.Run(); err != nil { + var stderr string + if ee, ok := err.(*exec.ExitError); ok { + stderr = string(ee.Stderr) + } + logrus.Warnf("Failed to call additional-layer-store-auth-helper (stderr:%s): %v", stderr, err) + } + } + } return s, nil } diff --git a/docs/containers-registries.conf.5.md b/docs/containers-registries.conf.5.md index 0263b79f8e..ed17fff840 100644 --- a/docs/containers-registries.conf.5.md +++ b/docs/containers-registries.conf.5.md @@ -19,6 +19,12 @@ Container engines will use the `$HOME/.config/containers/registries.conf` if it `credential-helpers` : An array of default credential helpers used as external credential stores. Note that "containers-auth.json" is a reserved value to use auth files as specified in containers-auth.json(5). The credential helpers are set to `["containers-auth.json"]` if none are specified. +`additional-layer-store-auth-helper` +: A string containing the helper binary name. This enables passing registry credentials to an + Additional Layer Store every time an image is read using the `docker://` + transport so that it can access private registries. See the 'Enabling Additional Layer Store to access to private registries' section below for + more details. + ### NAMESPACED `[[registry]]` SETTINGS The bulk of the configuration is represented as an array of `[[registry]]` @@ -254,6 +260,30 @@ in order, and use the first one that exists. Note that a mirror is associated only with the current `[[registry]]` TOML table. If using the example above, pulling the image `registry.com/image:latest` will hence only reach out to `mirror.registry.com`, and the mirrors associated with `example.com/foo` will not be considered. +### Enabling Additional Layer Store to access to private registries + +The `additional-layer-store-auth-helper` option enables passing registry +credentials to an Additional Layer Store so that it can access private registries. + +When accessing a private registry via an Additional Layer Store, a helper binary needs to be provided. This helper binary is +registered via the `additional-layer-store-auth-helper` option. Every time an image +is read using the `docker://` transport, the specified helper binary is executed +and receives registry credentials from stdin in the following format. + +```json +{ + "$image_reference": { + "username": "$username", + "password": "$password", + "identityToken": "$identityToken" + } +} +``` + +The format of `$image_reference` is `$repo{:$tag|@$digest}`. + +Additional Layer Stores can use this helper binary to access the private registry. + ## VERSION 1 FORMAT - DEPRECATED VERSION 1 format is still supported but it does not support using registry mirrors, longest-prefix matches, or location rewriting. diff --git a/pkg/sysregistriesv2/system_registries_v2.go b/pkg/sysregistriesv2/system_registries_v2.go index 45427a350f..1b161474da 100644 --- a/pkg/sysregistriesv2/system_registries_v2.go +++ b/pkg/sysregistriesv2/system_registries_v2.go @@ -248,6 +248,11 @@ type V2RegistriesConf struct { // potentially use all unqualified-search registries ShortNameMode string `toml:"short-name-mode"` + // AdditionalLayerStoreAuthHelper is a helper binary that receives + // registry credentials pass them to Additional Layer Store for + // registry authentication. These credentials are only collected when pulling (not pushing). + AdditionalLayerStoreAuthHelper string `toml:"additional-layer-store-auth-helper"` + shortNameAliasConf // If you add any field, make sure to update Nonempty() below. @@ -825,6 +830,16 @@ func CredentialHelpers(sys *types.SystemContext) ([]string, error) { return config.partialV2.CredentialHelpers, nil } +// AdditionalLayerStoreAuthHelper returns the helper for passing registry +// credentials to Additional Layer Store. +func AdditionalLayerStoreAuthHelper(sys *types.SystemContext) (string, error) { + config, err := getConfig(sys) + if err != nil { + return "", err + } + return config.partialV2.AdditionalLayerStoreAuthHelper, nil +} + // refMatchingSubdomainPrefix returns the length of ref // iff ref, which is a registry, repository namespace, repository or image reference (as formatted by // reference.Domain(), reference.Named.Name() or reference.Reference.String() @@ -1051,6 +1066,11 @@ func (c *parsedConfig) updateWithConfigurationFrom(updates *parsedConfig) { c.shortNameMode = updates.shortNameMode } + // == Merge AdditionalLayerStoreAuthHelper: + if updates.partialV2.AdditionalLayerStoreAuthHelper != "" { + c.partialV2.AdditionalLayerStoreAuthHelper = updates.partialV2.AdditionalLayerStoreAuthHelper + } + // == Merge aliasCache: // We don’t maintain (in fact we actively clear) c.partialV2.shortNameAliasConf. c.aliasCache.updateWithConfigurationFrom(updates.aliasCache) diff --git a/pkg/sysregistriesv2/system_registries_v2_test.go b/pkg/sysregistriesv2/system_registries_v2_test.go index ca88c6ea78..04e020641b 100644 --- a/pkg/sysregistriesv2/system_registries_v2_test.go +++ b/pkg/sysregistriesv2/system_registries_v2_test.go @@ -57,6 +57,7 @@ var v2RegistriesConfEmptyTestData = []struct { {nonempty: true, hasSetField: true, v: V2RegistriesConf{CredentialHelpers: []string{"a"}}}, {nonempty: true, hasSetField: true, v: V2RegistriesConf{ShortNameMode: "enforcing"}}, {nonempty: true, hasSetField: true, v: V2RegistriesConf{shortNameAliasConf: shortNameAliasConf{Aliases: map[string]string{"a": "example.com/b"}}}}, + {nonempty: true, hasSetField: true, v: V2RegistriesConf{AdditionalLayerStoreAuthHelper: "example"}}, } func TestV2RegistriesConfNonempty(t *testing.T) {