Skip to content
Snippets Groups Projects
Commit 3341c597 authored by Arran Walker's avatar Arran Walker
Browse files

Merge branch '28073-auth-config-namespace' into 'main'

Add namespace support for DOCKER_AUTH_CONFIG

Closes #28073

See merge request !4727



Merged-by: default avatarArran Walker <ajwalker@gitlab.com>
Approved-by: default avatarDavis Bickford <dbickford@gitlab.com>
Reviewed-by: default avatarDavis Bickford <dbickford@gitlab.com>
Co-authored-by: default avatarTobias Rautenkranz <mail@tobias.rautenkranz.ch>
parents c6e965f0 c10ef215
No related branches found
No related tags found
1 merge request!4727Add namespace support for DOCKER_AUTH_CONFIG
Pipeline #1463166597 failed
......@@ -10,6 +10,7 @@ import (
"os"
"os/user"
"path/filepath"
"regexp"
"strings"
"github.com/docker/cli/cli/config/configfile"
......@@ -45,6 +46,15 @@ type DebugLogger interface {
Debugln(args ...interface{})
}
// the parent directory of a path or ""
func parentPath(path string) string {
index := strings.LastIndex(path, "/")
if index == -1 {
return ""
}
return path[:index]
}
// ResolveConfigForImage returns the auth configuration for a particular image.
// Returns nil on no config found.
// See ResolveConfigs for source information.
......@@ -57,13 +67,15 @@ func ResolveConfigForImage(
return nil, err
}
indexName, _ := splitDockerImageName(imageName)
info, ok := authConfigs[indexName]
if !ok {
return nil, nil
path := dockerImageNamePath(imageName)
for p := path; p != ""; p = parentPath(p) {
info, ok := authConfigs[p]
if ok {
return &info, nil
}
}
return &info, nil
return nil, nil
}
// ResolveConfigs returns the authentication configuration for docker registries.
......@@ -95,27 +107,22 @@ func ResolveConfigs(
return nil, err
}
registryConfig := make(map[string]types.AuthConfig)
var hostnames []string
for registry, conf := range configs {
registryHostname := convertToHostname(registry)
registryConfig[registryHostname] = conf
hostnames = append(hostnames, registryHostname)
registryPath := convertToRegistryPath(registry)
hostnames = append(hostnames, registryPath)
if _, ok := res[registryPath]; !ok {
res[registryPath] = RegistryInfo{
Source: source,
AuthConfig: conf,
}
}
}
// Source can be blank if there is no home dir configuration
if source != "" {
logger.Debugln(fmt.Sprintf("Loaded Docker credentials, source = %q, hostnames = %v, error = %v", source, hostnames, err))
}
for registryHostname, conf := range registryConfig {
if _, ok := res[registryHostname]; !ok {
res[registryHostname] = RegistryInfo{
Source: source,
AuthConfig: conf,
}
}
}
}
return res, nil
......@@ -178,25 +185,30 @@ func getBuildConfiguration(credentials []common.Credentials) (string, map[string
return authConfigSourceNameJobPayload, authConfigs, nil
}
// splitDockerImageName breaks a reposName into an index name and remote name
func splitDockerImageName(reposName string) (string, string) {
nameParts := strings.SplitN(reposName, "/", 2)
var indexName, remoteName string
// Given a docker image reference get the path to lookup the authentication credentials
func dockerImageNamePath(imageName string) string {
imageIndex := strings.LastIndex(imageName, "/")
image := imageName
if imageIndex != -1 {
image = imageName[imageIndex+1:]
}
// remove tag
image, _, _ = strings.Cut(image, ":")
path := imageName[:imageIndex+1] + image
nameParts := strings.SplitN(imageName, "/", 2)
if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
// 'docker.io'
indexName = DefaultDockerRegistry
remoteName = reposName
} else {
indexName = nameParts[0]
remoteName = nameParts[1]
path = DefaultDockerRegistry + "/" + path
} else if nameParts[0] == "index."+DefaultDockerRegistry {
path, _ = strings.CutPrefix(path, "index.")
}
if indexName == "index."+DefaultDockerRegistry {
indexName = DefaultDockerRegistry
}
return indexName, remoteName
return pathWithLowerCaseHostname(path)
}
// readDockerConfigsFromHomeDir reads known docker config from home
......@@ -313,17 +325,46 @@ func addAll(to, from map[string]types.AuthConfig) {
}
}
func convertToHostname(url string) string {
url = strings.ToLower(url)
url = strings.TrimPrefix(url, "http://")
url = strings.TrimPrefix(url, "https://")
// convert hostname part to lower case.
// Since the hostname is case insensitive we convert it to lower case
// to allow matching with case sensitive comparison
func pathWithLowerCaseHostname(path string) string {
nameParts := strings.SplitN(path, "/", 2)
hostname := strings.ToLower(nameParts[0])
if len(nameParts) == 1 {
return hostname
}
return hostname + "/" + nameParts[1]
}
// Returns the normalized path for a docker registry reference for some credentials.
func convertToRegistryPath(imageRef string) string {
protocol := regexp.MustCompile("(?i)^http[s]://")
if protocol.MatchString(imageRef) {
// old style with protocol and maybe suffix /v1/
// just the use hostname
path := protocol.ReplaceAllString(imageRef, "")
nameParts := strings.SplitN(path, "/", 2)
path = strings.ToLower(nameParts[0])
if path == "index."+DefaultDockerRegistry {
return DefaultDockerRegistry
}
return path
}
nameParts := strings.SplitN(url, "/", 2)
url = nameParts[0]
path := strings.TrimSuffix(imageRef, "/")
if url == "index."+DefaultDockerRegistry {
return DefaultDockerRegistry
tagIndex := strings.LastIndex(path, ":")
pathIndex := strings.LastIndex(path, "/")
// remove image tag from path
if pathIndex != -1 && tagIndex > pathIndex {
path = path[:strings.LastIndex(path, ":")]
}
return url
return pathWithLowerCaseHostname(path)
}
......@@ -385,6 +385,84 @@ func setupTestHomeDirectoryConfig(t *testing.T, configFileContents string) func(
}
}
func TestDockerImage(t *testing.T) {
path := dockerImageNamePath("foo.bar:123/asdf/baz:latest")
assert.Equal(t, "foo.bar:123/asdf/baz", path)
path = dockerImageNamePath("foo.bar/asdf/baz:latest")
assert.Equal(t, "foo.bar/asdf/baz", path)
path = dockerImageNamePath("foo.bar/asdf/baz")
assert.Equal(t, "foo.bar/asdf/baz", path)
path = dockerImageNamePath("registry.local/ns/image")
assert.Equal(t, "registry.local/ns/image", path)
path = dockerImageNamePath("foo.bar:123/asdf/baz")
assert.Equal(t, "foo.bar:123/asdf/baz", path)
path = dockerImageNamePath("FOO.BAR:123/With/Case")
assert.Equal(t, "foo.bar:123/With/Case", path)
}
func TestConvertToRegistryPath(t *testing.T) {
path := convertToRegistryPath("my.hostname")
assert.Equal(t, "my.hostname", path)
path = convertToRegistryPath("my.hostname/with/path")
assert.Equal(t, "my.hostname/with/path", path)
path = convertToRegistryPath("MY.HOSTNAME/With/Path/CASE")
assert.Equal(t, "my.hostname/With/Path/CASE", path)
path = convertToRegistryPath("my.hostname/with/tag/image:latest")
assert.Equal(t, "my.hostname/with/tag/image", path)
path = convertToRegistryPath("https://index.docker.io/v1/")
assert.Equal(t, "docker.io", path)
path = convertToRegistryPath("HTTPS://INDEX.DOCKER.IO/V1/")
assert.Equal(t, "docker.io", path)
}
func TestPaths(t *testing.T) {
testDockerAuthConfigsPaths := `{"auths":{` +
`"registry.local":{"auth":"dGVzdF91c2VyXzE6dGVzdF9wYXNzd29yZF8x"},` +
`"registry.local/ns":{"auth":"dGVzdF91c2VyXzI6dGVzdF9wYXNzd29yZF8y"},` +
`"registry.local/ns/some/image":{"auth":"dGVzdF91c2VyXzM6dGVzdF9wYXNzd29yZF8z"}` +
`}}`
logger, _ := test.NewNullLogger()
result, err := ResolveConfigForImage("registry.local/foo/image:3",
testDockerAuthConfigsPaths, "", nil, logger)
assert.NoError(t, err)
assert.NotEmpty(t, result)
assert.Equal(t, "$DOCKER_AUTH_CONFIG", result.Source)
assert.Equal(t, "test_user_1", result.AuthConfig.Username)
assert.Equal(t, "test_password_1", result.AuthConfig.Password)
result, err = ResolveConfigForImage("registry.local/ns/image:5",
testDockerAuthConfigsPaths, "", nil, logger)
assert.NoError(t, err)
assert.NotEmpty(t, result)
assert.Equal(t, "$DOCKER_AUTH_CONFIG", result.Source)
assert.Equal(t, "test_user_2", result.AuthConfig.Username)
assert.Equal(t, "test_password_2", result.AuthConfig.Password)
result, err = ResolveConfigForImage("registry.local/ns/some/image:l",
testDockerAuthConfigsPaths, "", nil, logger)
assert.NoError(t, err)
assert.NotEmpty(t, result)
assert.Equal(t, "test_user_3", result.AuthConfig.Username)
assert.Equal(t, "test_password_3", result.AuthConfig.Password)
result, err = ResolveConfigForImage("no_auth_configured/image:l",
testDockerAuthConfigsPaths, "", nil, logger)
assert.NoError(t, err)
assert.Nil(t, result)
}
func TestGetConfigs(t *testing.T) {
cleanup := setupTestHomeDirectoryConfig(t, testFileAuthConfigs)
defer cleanup()
......@@ -459,46 +537,29 @@ func TestGetConfigs_DuplicatedRegistryCredentials(t *testing.T) {
assert.Equal(t, expectedResult, result)
}
func TestSplitDockerImageName(t *testing.T) {
remote, image := splitDockerImageName("tutum.co/user/ubuntu")
expectedRemote := "tutum.co"
expectedImage := "user/ubuntu"
if remote != expectedRemote {
t.Error("Expected ", expectedRemote, ", got ", remote)
}
if image != expectedImage {
t.Error("Expected ", expectedImage, ", got ", image)
}
func TestDockerImageNamePath(t *testing.T) {
path := dockerImageNamePath("tutum.co/user/ubuntu")
assert.Equal(t, "tutum.co/user/ubuntu", path)
}
func TestSplitDefaultDockerImageName(t *testing.T) {
remote, image := splitDockerImageName("user/ubuntu")
expectedRemote := "docker.io"
expectedImage := "user/ubuntu"
if remote != expectedRemote {
t.Error("Expected ", expectedRemote, ", got ", remote)
}
if image != expectedImage {
t.Error("Expected ", expectedImage, ", got ", image)
}
func TestDockerImageNamePathTag(t *testing.T) {
path := dockerImageNamePath("tutum.co/user/ubuntu:latest")
assert.Equal(t, "tutum.co/user/ubuntu", path)
}
func TestSplitDefaultIndexDockerImageName(t *testing.T) {
remote, image := splitDockerImageName("index.docker.io/user/ubuntu")
expectedRemote := "docker.io"
expectedImage := "user/ubuntu"
func TestDockerImageNamePathPort(t *testing.T) {
path := dockerImageNamePath("cr.internal:5000/user/ubuntu")
assert.Equal(t, "cr.internal:5000/user/ubuntu", path)
}
if remote != expectedRemote {
t.Error("Expected ", expectedRemote, ", got ", remote)
}
func TestDefaultDockerImageNamePath(t *testing.T) {
path := dockerImageNamePath("user/ubuntu")
assert.Equal(t, "docker.io/user/ubuntu", path)
}
if image != expectedImage {
t.Error("Expected ", expectedImage, ", got ", image)
}
func TestDefaultIndexDockerImageNamePath(t *testing.T) {
path := dockerImageNamePath("index.docker.io/user/ubuntu")
assert.Equal(t, "docker.io/user/ubuntu", path)
}
type configLocation struct {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment