test(cloud-auth): cover authorization failures
This commit is contained in:
@@ -31,7 +31,7 @@
|
|||||||
- [x] 4.2 Add public scopes for task submission/read, pool read, plugin read, and plugin administration and enforce them on every `/v1` route.
|
- [x] 4.2 Add public scopes for task submission/read, pool read, plugin read, and plugin administration and enforce them on every `/v1` route.
|
||||||
- [x] 4.3 Add host principals bound to one `host_id` and reject cross-host heartbeat, claim, renewal, or result operations.
|
- [x] 4.3 Add host principals bound to one `host_id` and reject cross-host heartbeat, claim, renewal, or result operations.
|
||||||
- [x] 4.4 Make missing production credentials a startup/readiness failure and permit anonymous mode only through the explicit non-production override.
|
- [x] 4.4 Make missing production credentials a startup/readiness failure and permit anonymous mode only through the explicit non-production override.
|
||||||
- [ ] 4.5 Add authentication tests covering invalid tokens, missing scopes, host impersonation, plugin administration, and secret redaction.
|
- [x] 4.5 Add authentication tests covering invalid tokens, missing scopes, host impersonation, plugin administration, and secret redaction.
|
||||||
|
|
||||||
## 5. Host Agent Internal API
|
## 5. Host Agent Internal API
|
||||||
|
|
||||||
|
|||||||
@@ -147,3 +147,18 @@ def test_public_principal_cannot_act_as_host() -> None:
|
|||||||
assert principal is not None
|
assert principal is not None
|
||||||
with pytest.raises(HostPrincipalRequiredError):
|
with pytest.raises(HostPrincipalRequiredError):
|
||||||
principal.require_host("host-a")
|
principal.require_host("host-a")
|
||||||
|
|
||||||
|
|
||||||
|
def test_authentication_failure_does_not_log_bearer_secret(caplog) -> None:
|
||||||
|
provider = ConfiguredBearerAuthProvider(
|
||||||
|
[BearerCredential(principal_id="integrator", token="valid-secret")]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
provider.authenticate(
|
||||||
|
_Request(headers={"authorization": "Bearer invalid-secret"})
|
||||||
|
)
|
||||||
|
is None
|
||||||
|
)
|
||||||
|
assert "invalid-secret" not in caplog.text
|
||||||
|
assert "valid-secret" not in caplog.text
|
||||||
|
|||||||
@@ -287,3 +287,56 @@ def test_every_public_route_enforces_its_scope(
|
|||||||
assert unauthorized.headers["www-authenticate"] == "Bearer"
|
assert unauthorized.headers["www-authenticate"] == "Bearer"
|
||||||
assert forbidden.status_code == 403
|
assert forbidden.status_code == 403
|
||||||
assert authorized.status_code not in {401, 403}
|
assert authorized.status_code not in {401, 403}
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_admin_scope_is_checked_before_registration(
|
||||||
|
tmp_path,
|
||||||
|
monkeypatch,
|
||||||
|
) -> None:
|
||||||
|
provider = ConfiguredBearerAuthProvider(
|
||||||
|
[
|
||||||
|
BearerCredential(
|
||||||
|
principal_id="plugin-reader",
|
||||||
|
token="reader-token",
|
||||||
|
scopes=frozenset({"plugins:read"}),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
app, _, _, plugin_registry = _build_app(tmp_path, auth_provider=provider)
|
||||||
|
registration_called = False
|
||||||
|
|
||||||
|
def fail_if_called(_manifest) -> None:
|
||||||
|
nonlocal registration_called
|
||||||
|
registration_called = True
|
||||||
|
raise AssertionError("plugin registration must not run before authorization")
|
||||||
|
|
||||||
|
monkeypatch.setattr(plugin_registry, "register", fail_if_called)
|
||||||
|
response = _client_for(app).post(
|
||||||
|
"/v1/plugins",
|
||||||
|
headers={"Authorization": "Bearer reader-token"},
|
||||||
|
json={
|
||||||
|
"name": "forbidden-plugin",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"entry_point_kind": "driver",
|
||||||
|
"target": "secret.module:builder",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 403
|
||||||
|
assert registration_called is False
|
||||||
|
assert "secret.module" not in response.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_token_is_rejected_by_public_router(tmp_path) -> None:
|
||||||
|
provider = ConfiguredBearerAuthProvider(
|
||||||
|
[BearerCredential(principal_id="integrator", token="valid-token")]
|
||||||
|
)
|
||||||
|
app, _, _, _ = _build_app(tmp_path, auth_provider=provider)
|
||||||
|
|
||||||
|
response = _client_for(app).get(
|
||||||
|
"/v1/devices",
|
||||||
|
headers={"Authorization": "Bearer invalid-token"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 401
|
||||||
|
assert response.headers["www-authenticate"] == "Bearer"
|
||||||
|
|||||||
Reference in New Issue
Block a user