diff --git a/image/docker/docker_client.go b/image/docker/docker_client.go index b166bfbf2e..c4f6dfdab0 100644 --- a/image/docker/docker_client.go +++ b/image/docker/docker_client.go @@ -120,6 +120,9 @@ type dockerClient struct { signatureBase lookasideStorageBase useSigstoreAttachments bool scope authScope + // namespaceProxy enables OCI distribution-spec registry proxying. + // When set, an "ns" query parameter is appended to all requests. + namespaceProxy string // The following members are detected registry properties: // They are set after a successful detectProperties(), and never change afterwards. @@ -491,6 +494,11 @@ func (c *dockerClient) resolveRequestURL(path string) (*url.URL, error) { if err != nil { return nil, err } + if c.namespaceProxy != "" { + q := res.Query() + q.Set("ns", c.namespaceProxy) + res.RawQuery = q.Encode() + } return res, nil } diff --git a/image/docker/docker_client_test.go b/image/docker/docker_client_test.go index 229fea332f..05554ae7bb 100644 --- a/image/docker/docker_client_test.go +++ b/image/docker/docker_client_test.go @@ -445,3 +445,58 @@ func TestIsManifestUnknownError(t *testing.T) { assert.True(t, res, "%s: %#v", c.name, err) } } + +func TestResolveRequestURLWithNamespaceProxy(t *testing.T) { + for _, c := range []struct { + name string + registry string + scheme string + path string + namespaceProxy string + expected string + }{ + { + name: "no namespace proxy", + registry: "registry.example.com", + scheme: "https", + path: "/v2/library/nginx/manifests/latest", + namespaceProxy: "", + expected: "https://registry.example.com/v2/library/nginx/manifests/latest", + }, + { + name: "with namespace proxy for docker.io", + registry: "proxy.example.com", + scheme: "https", + path: "/v2/library/nginx/manifests/latest", + namespaceProxy: "docker.io", + expected: "https://proxy.example.com/v2/library/nginx/manifests/latest?ns=docker.io", + }, + { + name: "with namespace proxy for quay.io", + registry: "proxy.example.com", + scheme: "https", + path: "/v2/coreos/etcd/blobs/sha256:abc123", + namespaceProxy: "quay.io", + expected: "https://proxy.example.com/v2/coreos/etcd/blobs/sha256:abc123?ns=quay.io", + }, + { + name: "with namespace proxy and port", + registry: "proxy.example.com:5000", + scheme: "http", + path: "/v2/myimage/manifests/v1.0", + namespaceProxy: "gcr.io", + expected: "http://proxy.example.com:5000/v2/myimage/manifests/v1.0?ns=gcr.io", + }, + } { + t.Run(c.name, func(t *testing.T) { + client := &dockerClient{ + registry: c.registry, + scheme: c.scheme, + namespaceProxy: c.namespaceProxy, + } + result, err := client.resolveRequestURL(c.path) + require.NoError(t, err) + assert.Equal(t, c.expected, result.String()) + }) + } +} diff --git a/image/docker/docker_image_src.go b/image/docker/docker_image_src.go index 4003af5d27..e93d338d28 100644 --- a/image/docker/docker_image_src.go +++ b/image/docker/docker_image_src.go @@ -150,6 +150,7 @@ func newImageSourceAttempt(ctx context.Context, sys *types.SystemContext, logica return nil, err } client.tlsClientConfig.InsecureSkipVerify = pullSource.Endpoint.Insecure + client.namespaceProxy = pullSource.Endpoint.NamespaceProxy s := &dockerImageSource{ PropertyMethodsInitialize: impl.PropertyMethods(impl.Properties{ diff --git a/image/pkg/sysregistriesv2/system_registries_v2.go b/image/pkg/sysregistriesv2/system_registries_v2.go index 0cf44571d3..9531b67253 100644 --- a/image/pkg/sysregistriesv2/system_registries_v2.go +++ b/image/pkg/sysregistriesv2/system_registries_v2.go @@ -71,6 +71,11 @@ type Endpoint struct { // This can only be set in a registry's Mirror field, not in the registry's primary Endpoint. // This per-mirror setting is allowed only when mirror-by-digest-only is not configured for the primary registry. PullFromMirror string `toml:"pull-from-mirror,omitempty"` + // NamespaceProxy enables OCI distribution-spec registry proxying. + // When set, an "ns" query parameter with this value is appended to all requests, + // allowing a single proxy to route to multiple upstream registries. + // See https://github.com/opencontainers/distribution-spec/blob/main/spec.md#registry-proxying + NamespaceProxy string `toml:"namespace-proxy,omitempty"` } // userRegistriesFile is the path to the per user registry configuration file.