Skip to content
Closed
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
46 changes: 21 additions & 25 deletions image/docker/docker_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ import (
"go.podman.io/image/v5/pkg/sysregistriesv2"
"go.podman.io/image/v5/pkg/tlsclientconfig"
"go.podman.io/image/v5/types"
"go.podman.io/storage/pkg/configfile"
"go.podman.io/storage/pkg/fileutils"
"go.podman.io/storage/pkg/homedir"
"go.podman.io/storage/pkg/unshare"
"golang.org/x/sync/semaphore"
)

Expand All @@ -60,19 +61,10 @@ const (
backoffMaxDelay = 60 * time.Second
)

type certPath struct {
path string
absolute bool
var perHostCertDirs = []string{
etcDir + "/docker/certs.d",
}

var (
homeCertDir = filepath.FromSlash(".config/containers/certs.d")
perHostCertDirs = []certPath{
{path: etcDir + "/containers/certs.d", absolute: true},
{path: etcDir + "/docker/certs.d", absolute: true},
}
)

// extensionSignature and extensionSignatureList come from github.com/openshift/origin/pkg/dockerregistry/server/signaturedispatcher.go:
// signature represents a Docker image signature.
type extensionSignature struct {
Expand Down Expand Up @@ -167,22 +159,26 @@ func dockerCertDir(sys *types.SystemContext, hostPort string) (string, error) {
return filepath.Join(sys.DockerPerHostCertDirPath, hostPort), nil
}

var (
hostCertDir string
fullCertDirPath string
)
conf := &configfile.Directory{
Name: "certs",
UserId: unshare.GetRootlessUID(),
ExtraDirs: perHostCertDirs,
}

for _, perHostCertDir := range append([]certPath{{path: filepath.Join(homedir.Get(), homeCertDir), absolute: false}}, perHostCertDirs...) {
if sys != nil && sys.RootForImplicitAbsolutePaths != "" && perHostCertDir.absolute {
hostCertDir = filepath.Join(sys.RootForImplicitAbsolutePaths, perHostCertDir.path)
} else {
hostCertDir = perHostCertDir.path
}
if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
conf.RootForImplicitAbsolutePaths = sys.RootForImplicitAbsolutePaths
}

dirs, err := configfile.ContainersResourceDirs(conf)
if err != nil {
return "", err
}

fullCertDirPath = filepath.Join(hostCertDir, hostPort)
for _, baseDir := range dirs {
fullCertDirPath := filepath.Join(baseDir, hostPort)
err := fileutils.Exists(fullCertDirPath)
if err == nil {
break
return fullCertDirPath, nil
}
if os.IsNotExist(err) {
continue
Expand All @@ -193,7 +189,7 @@ func dockerCertDir(sys *types.SystemContext, hostPort string) (string, error) {
}
return "", err
}
return fullCertDirPath, nil
return "", nil
}

// newDockerClientFromRef returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
Expand Down
34 changes: 25 additions & 9 deletions image/docker/docker_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
Expand All @@ -21,21 +22,36 @@ import (
)

func TestDockerCertDir(t *testing.T) {
const nondefaultFullPath = "/this/is/not/the/default/full/path"
const nondefaultPerHostDir = "/this/is/not/the/default/certs.d"
t.Helper()

tempRoot := t.TempDir()

nondefaultFullPath := filepath.Join(tempRoot, "nondefault", "full", "path")
nondefaultPerHostDir := filepath.Join(tempRoot, "nondefault", "certs.d")
const variableReference = "$HOME"
const rootPrefix = "/root/prefix"
rootPrefix := filepath.Join(tempRoot, "rootprefix")
const registryHostPort = "thishostdefinitelydoesnotexist:5000"

systemPerHostResult := filepath.Join(perHostCertDirs[len(perHostCertDirs)-1].path, registryHostPort)
hostDirs := []string{
"/etc/containers/certs.d",
"/etc/docker/certs.d",
}

// Create RootForImplicitAbsolutePaths-prefixed locations.
for _, d := range hostDirs {
require.NoError(t, os.MkdirAll(filepath.Join(rootPrefix, d, registryHostPort), 0o755))
}
// Create nondefault per-host override directory.
require.NoError(t, os.MkdirAll(filepath.Join(nondefaultPerHostDir, registryHostPort), 0o755))

for _, c := range []struct {
sys *types.SystemContext
expected string
}{
// The common case
{nil, systemPerHostResult},
// There is a context, but it does not override the path.
{&types.SystemContext{}, systemPerHostResult},
// Work with nil SystemContext.
{nil, ""},
// Work with empty SystemContext.
{&types.SystemContext{}, ""},
// Full path overridden
{&types.SystemContext{DockerCertPath: nondefaultFullPath}, nondefaultFullPath},
// Per-host path overridden
Expand All @@ -54,7 +70,7 @@ func TestDockerCertDir(t *testing.T) {
// Root overridden
{
&types.SystemContext{RootForImplicitAbsolutePaths: rootPrefix},
filepath.Join(rootPrefix, systemPerHostResult),
filepath.Join(rootPrefix, "/etc/containers/certs.d", registryHostPort),
},
// Root and path overrides present simultaneously,
{
Expand Down
44 changes: 26 additions & 18 deletions image/docker/registries_d.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import (
"go.podman.io/image/v5/docker/reference"
"go.podman.io/image/v5/internal/rootless"
"go.podman.io/image/v5/types"
"go.podman.io/storage/pkg/fileutils"
"go.podman.io/storage/pkg/configfile"
"go.podman.io/storage/pkg/homedir"
"go.podman.io/storage/pkg/unshare"
"gopkg.in/yaml.v3"
)

Expand All @@ -29,9 +30,6 @@ var systemRegistriesDirPath = builtinRegistriesDirPath
// DO NOT change this, instead see systemRegistriesDirPath above.
const builtinRegistriesDirPath = etcDir + "/containers/registries.d"

// userRegistriesDirPath is the path to the per user registries.d.
var userRegistriesDir = filepath.FromSlash(".config/containers/registries.d")

// defaultUserDockerDir is the default lookaside directory for unprivileged user
var defaultUserDockerDir = filepath.FromSlash(".local/share/containers/sigstore")

Expand Down Expand Up @@ -78,31 +76,41 @@ func SignatureStorageBaseURL(sys *types.SystemContext, ref types.ImageReference,

// loadRegistryConfiguration returns a registryConfiguration appropriate for sys.
func loadRegistryConfiguration(sys *types.SystemContext) (*registryConfiguration, error) {
dirPath := registriesDirPath(sys)
dirPath, err := registriesDirPath(sys)
if err != nil {
return nil, err
}
logrus.Debugf(`Using registries.d directory %s`, dirPath)
return loadAndMergeConfig(dirPath)
}

// registriesDirPath returns a path to registries.d
func registriesDirPath(sys *types.SystemContext) string {
return registriesDirPathWithHomeDir(sys, homedir.Get())
}

// registriesDirPathWithHomeDir is an internal implementation detail of registriesDirPath,
// it exists only to allow testing it with an artificial home directory.
func registriesDirPathWithHomeDir(sys *types.SystemContext, homeDir string) string {
func registriesDirPath(sys *types.SystemContext) (string, error) {
if sys != nil && sys.RegistriesDirPath != "" {
return sys.RegistriesDirPath
return sys.RegistriesDirPath, nil
}
userRegistriesDirPath := filepath.Join(homeDir, userRegistriesDir)
if err := fileutils.Exists(userRegistriesDirPath); err == nil {
return userRegistriesDirPath

conf := &configfile.Directory{
Name: "registries",
UserId: unshare.GetRootlessUID(),
}
if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
conf.RootForImplicitAbsolutePaths = sys.RootForImplicitAbsolutePaths
}

return systemRegistriesDirPath
dirs, err := configfile.ContainersResourceDirs(conf)
if err != nil {
return "", err
}
if len(dirs) > 0 {
return dirs[0], nil
}

// Preserve the historical behavior: default to the system path even if it doesn't exist.
if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath), nil
}
return systemRegistriesDirPath, nil
}

// loadAndMergeConfig loads configuration files in dirPath
Expand Down
101 changes: 89 additions & 12 deletions image/docker/registries_d_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,24 +71,30 @@ func TestRegistriesDirPath(t *testing.T) {
const variableReference = "$HOME"
const rootPrefix = "/root/prefix"
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(tempHome, ".config"))
userRegistriesDir := filepath.FromSlash(".config/containers/registries.d")
userRegistriesDirPath := filepath.Join(tempHome, userRegistriesDir)
for _, c := range []struct {
sys *types.SystemContext
userFilePresent bool
expected string
rooted bool
setup func(t *testing.T, root string)
expected func(root string) string
}{
// The common case
{nil, false, systemRegistriesDirPath},
{nil, false, false, nil, func(string) string { return systemRegistriesDirPath }},
// There is a context, but it does not override the path.
{&types.SystemContext{}, false, systemRegistriesDirPath},
{&types.SystemContext{}, false, false, nil, func(string) string { return systemRegistriesDirPath }},
// Path overridden
{&types.SystemContext{RegistriesDirPath: nondefaultPath}, false, nondefaultPath},
{&types.SystemContext{RegistriesDirPath: nondefaultPath}, false, false, nil, func(string) string { return nondefaultPath }},
// Root overridden
{
&types.SystemContext{RootForImplicitAbsolutePaths: rootPrefix},
false,
filepath.Join(rootPrefix, systemRegistriesDirPath),
false,
nil,
func(string) string { return filepath.Join(rootPrefix, systemRegistriesDirPath) },
},
// Root and path overrides present simultaneously,
{
Expand All @@ -97,33 +103,104 @@ func TestRegistriesDirPath(t *testing.T) {
RegistriesDirPath: nondefaultPath,
},
false,
nondefaultPath,
false,
nil,
func(string) string { return nondefaultPath },
},
// User registries.d present, not overridden
{&types.SystemContext{}, true, userRegistriesDirPath},
{&types.SystemContext{}, true, false, nil, func(string) string { return userRegistriesDirPath }},
// Context and user User registries.d preset simultaneously
{&types.SystemContext{RegistriesDirPath: nondefaultPath}, true, nondefaultPath},
{&types.SystemContext{RegistriesDirPath: nondefaultPath}, true, false, nil, func(string) string { return nondefaultPath }},
// Root and user registries.d overrides present simultaneously,
{
&types.SystemContext{
RootForImplicitAbsolutePaths: rootPrefix,
RegistriesDirPath: nondefaultPath,
},
true,
nondefaultPath,
false,
nil,
func(string) string { return nondefaultPath },
},
// No environment expansion happens in the overridden paths
{&types.SystemContext{RegistriesDirPath: variableReference}, false, variableReference},
{&types.SystemContext{RegistriesDirPath: variableReference}, false, false, nil, func(string) string { return variableReference }},

// RootForImplicitAbsolutePaths: system dir exists
{
&types.SystemContext{},
false,
true,
func(t *testing.T, root string) {
t.Helper()
require.NoError(t, os.MkdirAll(filepath.Join(root, "/usr/share/containers/registries.d"), 0o755))
},
func(root string) string { return filepath.Join(root, "/usr/share/containers/registries.d") },
},
// RootForImplicitAbsolutePaths: override dir beats system dir
{
&types.SystemContext{},
false,
true,
func(t *testing.T, root string) {
t.Helper()
require.NoError(t, os.MkdirAll(filepath.Join(root, "/etc/containers/registries.d"), 0o755))
require.NoError(t, os.MkdirAll(filepath.Join(root, "/usr/share/containers/registries.d"), 0o755))
},
func(root string) string { return filepath.Join(root, "/etc/containers/registries.d") },
},
// RootForImplicitAbsolutePaths: user dir beats override+system
{
&types.SystemContext{},
false,
true,
func(t *testing.T, root string) {
t.Helper()
require.NoError(t, os.MkdirAll(filepath.Join(root, "/etc/containers/registries.d"), 0o755))
require.NoError(t, os.MkdirAll(filepath.Join(root, "/usr/share/containers/registries.d"), 0o755))
require.NoError(t, os.MkdirAll(userRegistriesDirPath, 0o700))
t.Cleanup(func() { require.NoError(t, os.RemoveAll(userRegistriesDirPath)) })
},
func(string) string { return userRegistriesDirPath },
},
// RootForImplicitAbsolutePaths: non-directory override path is skipped
{
&types.SystemContext{},
false,
true,
func(t *testing.T, root string) {
t.Helper()
ovPath := filepath.Join(root, "/etc/containers/registries.d")
require.NoError(t, os.MkdirAll(filepath.Dir(ovPath), 0o755))
require.NoError(t, os.WriteFile(ovPath, []byte("not a directory"), 0o600))
require.NoError(t, os.MkdirAll(filepath.Join(root, "/usr/share/containers/registries.d"), 0o755))
},
func(root string) string { return filepath.Join(root, "/usr/share/containers/registries.d") },
},
} {
root := ""
sys := c.sys
if c.rooted {
root = t.TempDir()
if sys == nil {
sys = &types.SystemContext{}
}
sys.RootForImplicitAbsolutePaths = root
}

if c.userFilePresent {
err := os.MkdirAll(userRegistriesDirPath, 0o700)
require.NoError(t, err)
} else {
err := os.RemoveAll(userRegistriesDirPath)
require.NoError(t, err)
}
path := registriesDirPathWithHomeDir(c.sys, tempHome)
assert.Equal(t, c.expected, path)
if c.setup != nil {
c.setup(t, root)
}

path, err := registriesDirPath(sys)
require.NoError(t, err)
assert.Equal(t, c.expected(root), path)
}
}

Expand Down
21 changes: 20 additions & 1 deletion image/docs/containers-certs.d.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,28 @@
containers-certs.d - Directory for storing custom container-registry TLS configurations

# DESCRIPTION
A custom TLS configuration for a container registry can be configured by creating a directory under `$HOME/.config/containers/certs.d` or `/etc/containers/certs.d`.
A custom TLS configuration for a container registry can be configured by creating a directory under `$XDG_CONFIG_HOME/containers/certs.d` (or `$HOME/.config/containers/certs.d` if `XDG_CONFIG_HOME` is unset), `/etc/containers/certs.d`, or `/usr/share/containers/certs.d`.
The name of the directory must correspond to the `host`[`:port`] of the registry (e.g., `my-registry.com:5000`).

Depending on whether the process is running as root or rootless, additional configuration directories are consulted to allow for system-wide defaults and per-user overrides:

- For both rootful and rootless:
- `/usr/share/containers/certs.d/`
- `/etc/containers/certs.d/`
- `/etc/docker/certs.d/`
- For rootful (UID == 0):
- `/usr/share/containers/certs.rootful.d/`
- `/etc/containers/certs.rootful.d/`
- For rootless (UID > 0):
- `/usr/share/containers/certs.rootless.d/`
- `/usr/share/containers/certs.rootless.d/<UID>/`
- `/etc/containers/certs.rootless.d/`
- `/etc/containers/certs.rootless.d/<UID>/`
- For per-user configuration:
- `$XDG_CONFIG_HOME/containers/certs.d/` (or `$HOME/.config/containers/certs.d/` if `XDG_CONFIG_HOME` is unset)

If a given `host`[`:port`] directory exists in multiple locations, the effective configuration is determined by the unified configfile search order: user configuration takes precedence over `/etc`, which in turn takes precedence over `/usr/share`.

The port part presence / absence must precisely match the port usage in image references,
e.g. to affect `podman pull registry.example/foo`,
use a directory named `registry.example`, not `registry.example:443`.
Expand Down
Loading
Loading