Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Additional Layer Store] Enable Additional Layer Store to perform registry authentication #2417

Merged
merged 2 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docker/docker_image_src.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package docker

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
Expand All @@ -11,6 +13,7 @@ import (
"net/http"
"net/url"
"os"
"os/exec"
"strings"
"sync"

Expand Down Expand Up @@ -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 {
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
IdentityToken string `json:"identityToken,omitempty"`
}{
physicalRef.ref.String(): {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be .ref.Name()? Credentials are, for the most part, repo-scoped.

Admittedly the FUSE interface uses 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
}

Expand Down
30 changes: 30 additions & 0 deletions docs/containers-registries.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]]`
Expand Down Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions pkg/sysregistriesv2/system_registries_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
mtrmac marked this conversation as resolved.
Show resolved Hide resolved
mtrmac marked this conversation as resolved.
Show resolved Hide resolved

shortNameAliasConf

// If you add any field, make sure to update Nonempty() below.
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions pkg/sysregistriesv2/system_registries_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down