Activate ACTIVITY-WP-0025: Authelia SSO ingress for ops and Temporal UI
Mark workplan active. Add Traefik ForwardAuth middleware and Ingress manifests for activity.coulomb.social and activity-temporal.coulomb.social. Prefer Authelia SSO identity for ops mutations; document DNS gate and fleet pattern (docs/ops-sso-access.md).
This commit is contained in:
parent
7e71c0c837
commit
f885697e96
13 changed files with 366 additions and 57 deletions
|
|
@ -1,13 +1,17 @@
|
|||
"""Unit tests for operator token auth (ACTIVITY-WP-0024-T02/T06)."""
|
||||
"""Unit tests for operator token + SSO auth (ACTIVITY-WP-0024/0025)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
from activity_core.ops_auth import (
|
||||
extract_operator_token,
|
||||
extract_sso_principal,
|
||||
operator_token_configured,
|
||||
require_operator,
|
||||
verify_operator_token,
|
||||
)
|
||||
|
||||
|
|
@ -42,7 +46,7 @@ def test_verify_allows_anonymous_dev(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
|
||||
def test_verify_token_match(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("ACTIVITY_CORE_OPERATOR_TOKEN", "correct-horse")
|
||||
assert verify_operator_token("correct-horse") == "operator"
|
||||
assert verify_operator_token("correct-horse") == "operator-token"
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
verify_operator_token("wrong")
|
||||
assert exc.value.status_code == 401
|
||||
|
|
@ -56,3 +60,37 @@ def test_operator_token_configured(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
assert operator_token_configured() is False
|
||||
monkeypatch.setenv("ACTIVITY_CORE_OPERATOR_TOKEN", "x")
|
||||
assert operator_token_configured() is True
|
||||
|
||||
|
||||
def test_extract_sso_principal_remote_user() -> None:
|
||||
req = MagicMock()
|
||||
req.headers = {"Remote-User": "bernd", "Remote-Email": "bernd@example.com"}
|
||||
# MagicMock headers.get needs side_effect
|
||||
headers = {"Remote-User": "bernd", "Remote-Email": "bernd@example.com"}
|
||||
|
||||
class H(dict):
|
||||
def get(self, key, default=None): # type: ignore[no-untyped-def]
|
||||
for k, v in self.items():
|
||||
if k.lower() == str(key).lower():
|
||||
return v
|
||||
return default
|
||||
|
||||
req.headers = H(headers)
|
||||
assert extract_sso_principal(req) == "bernd"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_operator_prefers_sso(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("ACTIVITY_CORE_OPERATOR_TOKEN", "tok")
|
||||
|
||||
class H(dict):
|
||||
def get(self, key, default=None): # type: ignore[no-untyped-def]
|
||||
for k, v in self.items():
|
||||
if k.lower() == str(key).lower():
|
||||
return v
|
||||
return default
|
||||
|
||||
req = MagicMock()
|
||||
req.headers = H({"Remote-User": "alice"})
|
||||
principal = await require_operator(req, x_operator_token=None, authorization=None)
|
||||
assert principal == "sso:alice"
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ async def test_ui_index_renders(ops_app: FastAPI, monkeypatch: pytest.MonkeyPatc
|
|||
assert "Daily Triage" in res.text
|
||||
assert "Operator token" in res.text
|
||||
assert "Temporal UI" in res.text
|
||||
assert "127.0.0.1:8080" in res.text
|
||||
assert "activity-temporal.coulomb.social" in res.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue