Skip to content

Commit

Permalink
Disable registry fallback for repository credentials
Browse files Browse the repository at this point in the history
As mentioned in
containers/common#659 (comment), we
now do not normalize the registry when being in non legacy format. This
means we do not match "quay.io/foo" if "quay.io/bar" is provided as
credentials, because both would be normalized to "quay.io" before this
patch.

We also add a new corner case where auth.json has been external
modified to contain an entry like "https://quay.io/v1/", which now also
matches if "quay.io/foo" has been provided via the credentials.

Signed-off-by: Sascha Grunert <[email protected]>
  • Loading branch information
saschagrunert committed Jul 20, 2021
1 parent d695b98 commit b78c420
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 17 deletions.
60 changes: 43 additions & 17 deletions pkg/docker/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,30 +649,42 @@ func findAuthentication(ref reference.Named, registry, path string, legacyFormat
keys = []string{registry}
}

// It may be possible that auth.json has been externally modified to
// contain an entry like "https://quay.io/v1/". In that case we still want
// to match.
strippedAuths := map[string]dockerAuthConfig{}
for k, v := range auths.AuthConfigs {
strippedAuths[stripRegistry(k)] = v
}

// Repo or namespace keys are only supported as exact matches. For registry
// keys we prefer exact matches as well.
for _, key := range keys {
if val, exists := auths.AuthConfigs[key]; exists {
return decodeDockerAuth(val)
}
if val, exists := strippedAuths[key]; exists {
return decodeDockerAuth(val)
}
}

// bad luck; let's normalize the entries first
// This primarily happens for legacyFormat, which for a time used API URLs
// (http[s:]//…/v1/) as keys.
// Secondarily, (docker login) accepted URLs with no normalization for
// several years, and matched registry hostnames against that, so support
// those entries even in non-legacyFormat ~/.docker/config.json.
// The docker.io registry still uses the /v1/ key with a special host name,
// so account for that as well.
registry = normalizeRegistry(registry)
normalizedAuths := map[string]dockerAuthConfig{}
for k, v := range auths.AuthConfigs {
normalizedAuths[normalizeRegistry(k)] = v
}
if legacyFormat {
// bad luck; let's normalize the entries first
// This primarily happens for legacyFormat, which for a time used API URLs
// (http[s:]//…/v1/) as keys.
// Secondarily, (docker login) accepted URLs with no normalization for
// several years, and matched registry hostnames against that, so support
// those entries even in non-legacyFormat ~/.docker/config.json.
// The docker.io registry still uses the /v1/ key with a special host name,
// so account for that as well.
normalizedAuths := map[string]dockerAuthConfig{}
for k, v := range auths.AuthConfigs {
normalizedAuths[normalizeRegistry(k)] = v
}

if val, exists := normalizedAuths[registry]; exists {
return decodeDockerAuth(val)
if val, exists := normalizedAuths[registry]; exists {
return decodeDockerAuth(val)
}
}

return types.DockerAuthConfig{}, nil
Expand Down Expand Up @@ -739,13 +751,27 @@ func stripScheme(url string) string {
return stripped
}

// normalizeRegistry converts the provided registry to contain only the hostname.
func normalizeRegistry(registry string) string {
normalized := convertToHostname(registry)
switch normalized {
return convertDockerHost(normalized)
}

// stripRegistry removes known prefixes as well as suffixes from the registry.
func stripRegistry(registry string) string {
stripped := stripScheme(registry)
stripped = strings.TrimSuffix(stripped, "/v1/")
return convertDockerHost(stripped)
}

// convertDockerHost converts the provided registry if a known docker.io host
// is provided.
func convertDockerHost(registry string) string {
switch registry {
case "registry-1.docker.io", "docker.io":
return "index.docker.io"
}
return normalized
return registry
}

// validateKey verifies that the input key does not have a prefix that is not
Expand Down
89 changes: 89 additions & 0 deletions pkg/docker/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -886,3 +886,92 @@ func TestValidateKey(t *testing.T) {
assert.Equal(t, tc.isNamespaced, isNamespaced)
}
}

func TestSetGetCredentials(t *testing.T) {
const (
username = "username"
password = "password"
)

getAuth := func(sys *types.SystemContext, input string) types.DockerAuthConfig {
ref, err := reference.ParseNamed(input)
require.NoError(t, err)
auth, err := GetCredentialsForRef(sys, ref)
require.NoError(t, err)
return auth
}

// Unset the home dir to make the test not fallback to other auth files
// there.
tmpDir, err := ioutil.TempDir("", "auth-test-")
require.NoError(t, err)
os.Setenv("HOME", tmpDir)
defer os.RemoveAll(tmpDir)

for _, tc := range []struct {
name string
set string
get string
setByAPI bool
shouldAuth bool
}{
{
name: "Should match namespace",
set: "quay.io/foo",
get: "quay.io/foo/a",
setByAPI: true,
shouldAuth: true,
},
{
name: "Should match registry if repository provided",
set: "quay.io",
get: "quay.io/foo",
setByAPI: true,
shouldAuth: true,
},
{
name: "Should not match different repository",
set: "quay.io/foo",
get: "quay.io/bar",
setByAPI: true,
shouldAuth: false,
},
{
name: "Should match legacy registry entry",
set: "https://quay.io/v1/",
get: "quay.io/foo",
setByAPI: false,
shouldAuth: true,
},
} {

// Create a new empty SystemContext referring an empty auth.json
tmpFile, err := ioutil.TempFile("", "auth.json-")
require.NoError(t, err)
defer os.RemoveAll(tmpFile.Name())
sys := &types.SystemContext{AuthFilePath: tmpFile.Name()}

if tc.setByAPI {
_, err = tmpFile.WriteString("{}")
require.NoError(t, err)

_, err = SetCredentials(sys, tc.set, username, password)
assert.NoError(t, err, tc.name)
} else {
_, err = tmpFile.WriteString(fmt.Sprintf(
`{"auths":{"%s":{"auth":"dXNlcm5hbWU6cGFzc3dvcmQ="}}}`, tc.set,
))
require.NoError(t, err)
}

// Try to authenticate against them
auth := getAuth(sys, tc.get)
if tc.shouldAuth {
assert.Equal(t, username, auth.Username, tc.name)
assert.Equal(t, password, auth.Password, tc.name)
} else {
assert.Empty(t, auth.Username, tc.name)
assert.Empty(t, auth.Password, tc.name)
}
}
}

0 comments on commit b78c420

Please sign in to comment.