Skip to content

Commit cd380ef

Browse files
better basic auth support
1 parent 2c4aeb5 commit cd380ef

5 files changed

Lines changed: 49 additions & 10 deletions

File tree

docs/admin/guides/auth/workload_identity.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ and you want its permissions scoped to specific repositories without long-lived
1717

1818
## How it works
1919

20-
On each request the token is read from the `Authorization` header,
21-
either as a `Bearer` token or as the password of a `Basic` header (the way `docker login` sends a token).
20+
On each request the token is read from the `Authorization: Bearer` header.
2221
The `iss` claim selects a configured provider,
2322
the signature is verified against the provider's JWKS,
2423
and `iss`, `aud` and `exp` are checked.
@@ -28,7 +27,6 @@ A token that matches no rule is rejected with a 401.
2827
## Enabling
2928

3029
Add the authentication class to `DEFAULT_AUTHENTICATION_CLASSES`,
31-
before `BasicAuthentication` so the `docker login` path reaches it,
3230
then populate `WORKLOAD_IDENTITY`:
3331

3432
```python title="settings.py"

pulpcore/app/checks.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,33 @@ def check_artifact_checksums(app_configs, **kwargs):
123123
)
124124

125125
return messages
126+
127+
128+
@register(deploy=True)
129+
def workload_identity_reserved_username(app_configs, **kwargs):
130+
from pulpcore.app.workload_identity import config
131+
132+
messages = []
133+
if not config.config():
134+
return messages
135+
136+
username = config.basic_username()
137+
try:
138+
from django.contrib.auth import get_user_model
139+
140+
collides = get_user_model().objects.filter(username=username).exists()
141+
except Exception:
142+
return messages
143+
144+
if collides:
145+
messages.append(
146+
CheckWarning(
147+
f"The WORKLOAD_IDENTITY basic_auth_username '{username}' is also a database user. "
148+
"A token presented with this username over Basic auth is validated as a workload "
149+
"identity token, not as that user's password. Set basic_auth_username to a name "
150+
"that is not a real user to avoid ambiguity.",
151+
id="pulpcore.W006",
152+
)
153+
)
154+
155+
return messages

pulpcore/app/workload_identity/authentication.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""DRF authentication that validates a third-party OIDC token against its provider's JWKS.
22
3-
The token arrives as a ``Bearer`` token or as the password in a ``Basic`` header (``docker login``).
4-
On success its claims map to grants and a stateless ``WorkloadIdentityPrincipal`` is returned.
3+
The token arrives as a ``Bearer`` token, or as the password of a ``Basic`` header whose username
4+
is the reserved workload-identity name. On success its claims map to grants and a stateless
5+
``WorkloadIdentityPrincipal`` is returned.
56
"""
67

78
import base64
@@ -28,7 +29,12 @@ class WorkloadIdentityAuthentication(BaseAuthentication):
2829
"""
2930

3031
def _get_token(self, request):
31-
"""Return the token from the Authorization header (Bearer, or Basic password), or None."""
32+
"""Return the token from the Authorization header, or None.
33+
34+
Accepts a ``Bearer`` token, or a token carried as the password of a ``Basic`` header whose
35+
username is the reserved workload-identity name. Any other ``Basic`` header is left for the
36+
regular authenticators.
37+
"""
3238
header = request.META.get("HTTP_AUTHORIZATION", "")
3339
parts = header.split()
3440
if len(parts) != 2:
@@ -42,9 +48,9 @@ def _get_token(self, request):
4248
decoded = base64.b64decode(value).decode("utf-8")
4349
except (binascii.Error, ValueError, UnicodeDecodeError):
4450
return None
45-
if ":" not in decoded:
51+
username, sep, password = decoded.partition(":")
52+
if not sep or username != config.basic_username():
4653
return None
47-
_, _, password = decoded.partition(":")
4854
return password
4955
return None
5056

pulpcore/app/workload_identity/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ def providers():
1818
return config().get("providers", {}) or {}
1919

2020

21+
def basic_username():
22+
"""The reserved username that flags a token carried in the ``Basic`` password field."""
23+
return config().get("basic_auth_username", "workload-identity")
24+
25+
2126
def provider_for_issuer(issuer):
2227
"""Return the provider entry whose ``issuer`` matches, or ``None``."""
2328
for entry in providers().values():

pulpcore/app/workload_identity/principal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,5 @@ def get_group_permissions(self, obj=None):
5555
return set()
5656

5757
def __str__(self):
58-
"""The username, or ``"oidc-principal"`` when it is empty."""
59-
return self.username or "oidc-principal"
58+
"""The username, or ``"workload-identity"`` when it is empty."""
59+
return self.username or "workload-identity"

0 commit comments

Comments
 (0)