fix(auth): add workaround for Docker client auth with mixed anonymous policies (#3868)

* fix(auth): add workaround for Docker client auth with mixed anonymous policies

Docker client fails to authenticate to protected repositories when basic auth
(htpasswd/LDAP) is used with mixed access policies (some repos anonymous,
some requiring auth). This happens because Docker determines whether to send
credentials based on the /v2/ response - if it returns 200, Docker assumes
no auth is needed anywhere.

Add `forceDockerClientAuth` config option that, when enabled, forces 401 on
/v2/ for Docker clients, triggering Docker's authentication flow.

This workaround only affects Docker clients (detected via User-Agent).
Podman and other OCI-compliant clients are unaffected.

Refs: https://github.com/opencontainers/wg-auth/blob/main/docs/implementations/moby.md

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>

* feat: remove ForceDockerClientAuth flag and use only authz policies to determine the docker specific behavior

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>

---------

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
This commit is contained in:
Andrei Aaron
2026-04-17 09:10:02 +03:00
committed by GitHub
parent d443346196
commit 7ceb01dcff
3 changed files with 137 additions and 1 deletions
+16
View File
@@ -186,6 +186,15 @@ func (a *AuthConfig) IsBasicAuthnEnabled() bool {
return a.IsHtpasswdAuthEnabled() || a.IsLdapAuthEnabled() || a.IsOpenIDAuthEnabled() || a.IsAPIKeyEnabled()
}
// CanAuthenticateWithBasicCredentials reports whether the server can authenticate a client
// using HTTP Basic credentials (username/password) in the Authorization header.
//
// This is intentionally narrower than IsBasicAuthnEnabled(): OpenID is not a Basic
// credential flow, while htpasswd/LDAP/API keys are.
func (a *AuthConfig) CanAuthenticateWithBasicCredentials() bool {
return a.IsHtpasswdAuthEnabled() || a.IsLdapAuthEnabled() || a.IsAPIKeyEnabled()
}
// GetFailDelay returns the configured fail delay for authentication attempts.
func (a *AuthConfig) GetFailDelay() int {
if a == nil {
@@ -508,6 +517,13 @@ func (config *AccessControlConfig) AnonymousPolicyExists() bool {
return false
}
// HasMixedAnonymousAndAuthenticatedPolicies reports whether the access control configuration contains
// at least one anonymous repository policy AND at least one authenticated-only policy
// (default/admin/user-specific).
func (config *AccessControlConfig) HasMixedAnonymousAndAuthenticatedPolicies() bool {
return config != nil && config.AnonymousPolicyExists() && !config.ContainsOnlyAnonymousPolicy()
}
// ContainsOnlyAnonymousPolicy checks if the access control configuration contains only anonymous policies.
func (config *AccessControlConfig) ContainsOnlyAnonymousPolicy() bool {
if config == nil {