Send a projected flex-auth caller token on every check
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 1m7s

TENANT_ENGINE_FLEX_AUTH_TOKEN_FILE is read per request so hourly
projection rotation needs no restart. Missing or unreadable file fails
closed as a local deny and never calls flex-auth. Needed before
flex-auth-tenant-engine can enforce (FLEX-WP-0015-T02).
This commit is contained in:
tegwick 2026-08-19 14:31:39 +02:00
parent 0809af063c
commit 2063470ac8
4 changed files with 38 additions and 2 deletions

View file

@ -760,6 +760,7 @@ def _build_authorizer(settings: Settings) -> WriteAuthorizer:
client = FlexAuthCheckClient(
base_url=settings.flex_auth_base_url,
timeout_seconds=settings.flex_auth_timeout_seconds,
bearer_token_file=settings.flex_auth_token_file,
)
return FlexAuthWriteAuthorizer(client=client)

View file

@ -11,6 +11,7 @@ class Settings:
host: str
port: int
database_path: str | None = None
flex_auth_token_file: str | None = None
@classmethod
def from_env(cls) -> "Settings":
@ -20,4 +21,5 @@ class Settings:
host=os.getenv("TENANT_ENGINE_HOST", "127.0.0.1"),
port=int(os.getenv("TENANT_ENGINE_HTTP_PORT", "8090")),
database_path=os.getenv("TENANT_ENGINE_DATABASE_PATH") or None,
flex_auth_token_file=os.getenv("TENANT_ENGINE_FLEX_AUTH_TOKEN_FILE") or None,
)

View file

@ -62,9 +62,11 @@ class FlexAuthCheckClient:
base_url: str,
timeout_seconds: float = 3.0,
transport: httpx.BaseTransport | None = None,
bearer_token_file: str | None = None,
) -> None:
self.base_url = base_url.rstrip("/")
self.timeout_seconds = timeout_seconds
self.bearer_token_file = bearer_token_file
self._client = httpx.Client(
base_url=self.base_url,
timeout=httpx.Timeout(timeout_seconds),
@ -73,8 +75,17 @@ class FlexAuthCheckClient:
def is_allowed(self, request: CheckRequest) -> bool:
try:
response = self._client.post("/v1/check", json=request.to_json())
except httpx.HTTPError:
headers: dict[str, str] = {}
if self.bearer_token_file:
# Projected ServiceAccount tokens rotate. Read on each check
# instead of pinning the token for the lifetime of the process.
with open(self.bearer_token_file, encoding="utf-8") as token_file:
token = token_file.read().strip()
if not token:
return False
headers["Authorization"] = f"Bearer {token}"
response = self._client.post("/v1/check", json=request.to_json(), headers=headers)
except (httpx.HTTPError, OSError):
return False
if response.status_code != 200:

View file

@ -1,3 +1,5 @@
from pathlib import Path
import httpx
import pytest
@ -89,3 +91,23 @@ def test_request_body_matches_schema_shape() -> None:
assert seen["action"] == "tenant.create"
assert seen["subject"] == {"id": "tenant-engine", "type": "service"}
assert seen["resource"] == {"id": "t-1", "type": "tenant", "system": "tenant-engine"}
def test_rotating_caller_token_is_read_for_each_check(tmp_path: Path) -> None:
token_file = tmp_path / "token"
token_file.write_text("token-one\n")
seen: list[str] = []
def handler(request: httpx.Request) -> httpx.Response:
seen.append(request.headers["authorization"])
return httpx.Response(200, json={"id": "d-1", "effect": "allow"})
client = FlexAuthCheckClient(
base_url="https://flex-auth.example.test",
transport=httpx.MockTransport(handler),
bearer_token_file=str(token_file),
)
assert client.is_allowed(_request()) is True
token_file.write_text("token-two\n")
assert client.is_allowed(_request()) is True
assert seen == ["Bearer token-one", "Bearer token-two"]