QONTO-WP-0004-T04: live flex-auth + tenant-engine authorization gate
Replaces the config-only QONTO_ASSISTANT_ENFORCE_SCOPE cached-claim check with two live-checked facts, per docs/SecurityPractice.md #4: 1. flex-auth POST /v1/check on finance.qonto.read for the calling actor/tenant (FlexAuthCheckClient, modeled on tenant-engine's own client for the same API). Registration lives in the flex-auth repo (examples/qonto-assistant/) -- rules + embedded tests verified with flex-auth test-policy/load-registry/check, and a live flex-auth serve hit by this exact client over real HTTP (not a mock). 2. tenant-engine's live capability-role lookup (GET /tenants/{id}/roles/live), denying unless the tenant currently holds one of QONTO_TENANT_ENGINE_REQUIRED_ROLES (default VEN,CUS) -- optional and additive to the flex-auth check. Both clients fail closed by construction (unreachable/malformed/non-2xx all deny, never grant), matching FlexAuthCheckClient's existing fail-closed philosophy elsewhere in the fleet. LiveAuthorizationGate combines both and is wired into CapabilityService._execute ahead of the internal policy kernel; off by default (no QONTO_FLEX_AUTH_URL set) so existing deployments are unaffected until configured. Verified beyond mocked unit tests: ran a real `flex-auth serve` loaded with the registered policy, and a real tenant-engine instance seeded with a VEN grant for tenant:friendly:binky, and exercised this repo's actual FlexAuthCheckClient/TenantEngineClient/LiveAuthorizationGate against both live processes over real HTTP -- allow for the correct tenant, live_authz_denied for a mismatched tenant. 28 new unit tests (flex_auth_client, tenant_engine_client, live_authorization_gate + CapabilityService integration). Full suite -> 80 passed; REST/MCP smokes and compileall still clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
aa28ef353a
commit
1f0f979e36
12 changed files with 649 additions and 1 deletions
|
|
@ -16,7 +16,9 @@ from qonto_assistant.auth import actor_claims_from_request
|
|||
from qonto_assistant.config import Settings
|
||||
from qonto_assistant.credentials import build_credential_provider
|
||||
from qonto_assistant.errors import QontoAssistantError, UpstreamError
|
||||
from qonto_assistant.flex_auth_client import FlexAuthCheckClient
|
||||
from qonto_assistant.key_cape_auth import KeyCapeTokenVerifier
|
||||
from qonto_assistant.live_authorization import LiveAuthorizationGate
|
||||
from qonto_assistant.mcp_auth import BearerTokenAuthMiddleware
|
||||
from qonto_assistant.mcp_server import create_mcp_server
|
||||
from qonto_assistant.policy import PolicyEngine
|
||||
|
|
@ -24,6 +26,7 @@ from qonto_assistant.qonto_client import FixtureQontoClient, QontoClient
|
|||
from qonto_assistant.rate_limits import ConcurrencyLimiter, RateLimiter
|
||||
from qonto_assistant.security_watch import DenyEscalationTracker
|
||||
from qonto_assistant.service import CapabilityService
|
||||
from qonto_assistant.tenant_engine_client import TenantEngineClient
|
||||
|
||||
|
||||
def create_app(
|
||||
|
|
@ -193,6 +196,28 @@ def _build_service(
|
|||
if settings.deny_escalation_enabled
|
||||
else None
|
||||
),
|
||||
live_authorization_gate=_build_live_authorization_gate(settings),
|
||||
)
|
||||
|
||||
|
||||
def _build_live_authorization_gate(settings: Settings) -> LiveAuthorizationGate | None:
|
||||
if not settings.flex_auth_base_url:
|
||||
return None
|
||||
tenant_engine_client = (
|
||||
TenantEngineClient(
|
||||
base_url=settings.tenant_engine_base_url,
|
||||
timeout_seconds=settings.tenant_engine_timeout_seconds,
|
||||
)
|
||||
if settings.tenant_engine_base_url
|
||||
else None
|
||||
)
|
||||
return LiveAuthorizationGate(
|
||||
flex_auth_client=FlexAuthCheckClient(
|
||||
base_url=settings.flex_auth_base_url,
|
||||
timeout_seconds=settings.flex_auth_timeout_seconds,
|
||||
),
|
||||
tenant_engine_client=tenant_engine_client,
|
||||
required_tenant_roles=settings.tenant_engine_required_roles,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue