Feature: improve ResolveConfigForImage to support DOCKER_AUTH_CONFIG with namespaced docker registries
Description
We are using a namespaced Docker registry, which means, that the repository url is set up like "https : //<hostname>/<namesapce>/<repository>". Credentials can only be shared inside a namespace. That means, that repositories in different namespaces can not have the same credentials.
So, using Docker images from different namespaces for job eexecution in the CI/CD Pipeline requires to have multiple credentials. So our approach is to set up DOCKER_AUTH_CONFIG like this:
{
"auths": {
"dockerreg.host/dockerreg-namespace1": {
"auth": "Zmhl...zUVk=",
},
"dockerreg.host/dockerreg-namespace2": {
"auth": "Zmhl...VldN",
},
...
"dockerreg.host/dockerreg-namespaceN": {
"auth": "Zmhl...VlFE",
}
}
}
But this did not work, because in ResolveConfigForImage https://gitlab.com/gitlab-org/gitlab-runner/-/blob/master/helpers/docker/auth/auth.go#L44 there is the following code:
func ResolveConfigForImage(imageName, dockerAuthConfig, ...) *RegistryInfo {
...
indexName, _ := splitDockerImageName(imageName) <-- indexName is only the first part split at "/"
for registry, info := range authConfigs {
if indexName == convertToHostname(registry) { <-- convertToHostname also splits at the first "/"
return &info
}
}
...
}
So if we use the repository "dockerreg.host/dockerreg-namespace2/dockerreg-repo2B" all our auths are cut down the the hostname "dockerreg.host" and compared against the host of the image, which is also always "dockerreg.host". So all out auths match and randomly one of them is taken and not the correct one for "dockerreg.host/dockerreg-namespace2"
Proposal
We found a workaround. Because there is no interpretation of the hostname and port this can be (mis-)used as a hack to distinguish different auths for the same host:
{
"auths": {
"dockerreg.host:0443": {
"auth": "Zmhl...zUVk=",
},
"dockerreg.host:00443": {
"auth": "Zmhl...VldN",
},
...
"dockerreg.host:000443": {
"auth": "Zmhl...VlFE",
}
}
}
Now we can use "dockerreg.host:00443/dockerreg-namespace2/dockerreg-repo2B:latest" as image name in the CI/CD Pipeline and the credentials for namespace2 are taken, because of the match "dockerreg.host:00443". But this is an error prone and ugly solution, so we want to have better support for namespaced docker registries.
From my point of view this can be done by doing a first loop over all auths to check, if any of the auths is exactly the same as the start of the image-name. So something like this:
func ResolveConfigForImage(imageName, dockerAuthConfig, ...) *RegistryInfo {
...
// ----- proposal start -----
strippedImageName := removeProtocol(imageName) <-- should remove "http://" and "https://" prefix like in convertToHostname()
for registry, info := range authConfigs {
if strings.HasPrefix(strippedImageName, removeProtocol(registry)) { <-- find first auth, that is completely contained in imageName
return &info
}
}
// ----- proposal end -----
indexName, _ := splitDockerImageName(imageName) <-- indexName is only the first part split at "/"
for registry, info := range authConfigs {
if indexName == convertToHostname(registry) { <-- convertToHostname also splits at the first "/"
return &info
}
}
...
}
This would solve all of our problems.
Of course you can think about a more advanced solution, like reducing the image name part by part (each "/" and ":" should be treated as separator) and find the auth which has the reduced image part as prefix.
Example:
auth1: "dockerreg.host/dockerreg-namespace1/dockerreg-repoA"
auth2: "dockerreg.host/dockerreg-namespace2/dockerreg-repoB"
auth3: "dockerreg.host/dockerreg-namespace2/dockerreg-repoc"
image: "dockerreg.host/dockerreg-namespace2/dockerreg-repoD"
For this constellation the second auth would match, because "dockerreg.host/dockerreg-namespace2" is the longest matching part, while the other auths only match "dockerreg.host".