qonto-assistant/workplans/QONTO-WP-0004-security-hardening-and-scale-to-zero.md

13 KiB
Raw Blame History

id type title domain repo status owner topic_slug created updated state_hub_workstream_id
QONTO-WP-0004 workplan Security hardening and Knative runtime path for internet exposure infotech qonto-assistant finished claude the-custodian 2026-07-23 2026-07-27 81a476ac-06d5-4e98-a2ed-1518b16863aa

Security hardening and Knative runtime path for internet exposure

Implements docs/SecurityPractice.md, written ahead of deploying qonto-assistant to the Railiance runtime fleet. This is the first fleet service that must be reachable by clients outside the cluster (laptop-based agent harness sessions) while holding a real company bank credential — a risk class above every other agent-facing service shipped so far.

The intended runtime direction is now explicit: use reef-railiance, establish a new rail-knative, and dynamically run a future rapp-qonto package on that rail. This repo remains the current ownership home for the Qonto domain code and the place where the security contract is first made concrete, but the remaining deployment-oriented work in this plan should feed that repo-family path rather than extend a permanent direct qonto-assistant deployment target.

Some of this workplan's scope depends on systems this repo does not own (key-cape, flex-auth, tenant-engine, Railiance placement). Those dependencies are tracked explicitly per task rather than assumed away; the kings-guard-owned portion is tracked as a separate intake against KG-WP-0002, not duplicated here.

As of Sunday, July 26, 2026, the cross-repo framework direction for that path is tracked in railiance-master/workplans/RMASTER-WP-0019-knative-qonto-runtime-on-reef-railiance.md. Tasks already marked done below remain valid as ownership-repo hardening and migration input; the remaining open tasks are now about handing the runtime path into reef-railiance, rail-knative, and rapp-qonto.

Task: Security Genome record and audit-stream review

id: QONTO-WP-0004-T01
status: done
priority: high
state_hub_task_id: "e93b2027-dd28-464b-8ea3-f7540f91caad"

2026-07-23: specs/security-genome.yaml written per kings-guard/specs/NetKingdomImmuneArchitecture.md §9.1's schema — capabilities provided/consumed, expected egress (OpenBao + Qonto API only), data classification, recovery expectations, and explicit tolerances for today's known-temporary gaps (shared-secret bearer auth, self-asserted actor claims). Confirmed AuditLogger's existing event shape (actor, capability, decision, deny_reason, latency, upstream status, policy version) already matches the Immune Observation contract closely enough that no schema rework is anticipated once a real consumer exists.

Done when: genome record exists and is reviewed against the blueprint schema; no code changes required for this task.

Task: Deny-escalation lockout

id: QONTO-WP-0004-T02
status: done
priority: high
state_hub_task_id: "3b10dd59-b03f-4d2b-8164-aa2a1c8c06c4"

2026-07-23: src/qonto_assistant/security_watch.py::DenyEscalationTracker locks out an actor who triggers arg_constraint/credential_exfil policy denials QONTO_DENY_ESCALATION_THRESHOLD times (default 3) within QONTO_DENY_ESCALATION_WINDOW_SECONDS (default 60s), for QONTO_DENY_ESCALATION_LOCKOUT_SECONDS (default 300s). Wired into CapabilityService._execute ahead of the policy kernel call; on by default via QONTO_DENY_ESCALATION_ENABLED. Ordinary denies (authz_denied, tenant_scope, unknown_capability) never count. This needed no external dependency — it closes a real gap using only what already exists (the audit stream and rate limiter).

Verified: tests/test_deny_escalation.py (8 new tests: tracker unit tests

  • service integration for both the escalating and non-escalating paths); full suite pytest → 39 passed; REST and MCP smoke scripts both pass against fixtures; compileall clean.

Done when: threshold/window/lockout are configurable, on by default, and proven not to affect ordinary policy-deny traffic.

Task: Replace shared-secret bearer token with key-cape identity

id: QONTO-WP-0004-T03
status: done
priority: high
state_hub_task_id: "f02b1cc0-4afc-44e5-b71d-545a13a3e12b"

2026-07-24: No key-cape-side coordination was actually needed — its /jwks is a standard RS256 endpoint. KeyCapeTokenVerifier (src/qonto_assistant/key_cape_auth.py) fetches/caches signing keys over httpx, validates iss/aud/exp and the IAM Profile v0.3 required claims, and derives ActorClaims from the token. Wired into auth.actor_claims_from_headers (the single seam both REST and MCP use); a verified bearer token now takes precedence over self-asserted X-Actor-* headers, and can be made mandatory via QONTO_KEY_CAPE_REQUIRED once real tokens are issued to callers. Off by default (no QONTO_KEY_CAPE_JWKS_URL set). QONTO_ASSISTANT_MCP_TOKEN remains a documented local-dev/legacy fallback per docs/mcp-integration.md.

Verified: 13 new tests using a real generated RSA keypair + JWKS served over httpx.MockTransport (valid/expired/wrong-audience/wrong-issuer/ missing-claim/unknown-key/rotated-key tokens, plus the auth.py precedence and required-vs-optional paths).

Task: Register and enforce finance.qonto.read in flex-auth

id: QONTO-WP-0004-T04
status: done
priority: high
state_hub_task_id: "fce7bf0e-674a-461f-8f2f-42d7dde3a722"

2026-07-24: Registered qonto-assistant in flex-auth (flex-auth/examples/qonto-assistant/, modeled directly on examples/tenant-engine/): one resource type (finance-snapshot), one action (finance.qonto.read), a Rego policy gating on resource.system + action + subject.type + tenant match. Tenant capability-role/plan liveness (VEN/CUS) is deliberately not encoded in that policy — kept as a separate tenant-engine live-lookup check, per both repos' policy-scope notes.

LiveAuthorizationGate (src/qonto_assistant/live_authorization.py) combines FlexAuthCheckClient.is_allowed() (live POST /v1/check) and TenantEngineClient.active_roles() (live GET /tenants/{id}/roles/live, gated on QONTO_TENANT_ENGINE_REQUIRED_ROLES, default VEN,CUS) and is wired into CapabilityService._execute ahead of the internal policy kernel. Both clients fail closed by construction. Replaces QONTO_ASSISTANT_ENFORCE_SCOPE's cached-claim check as the live authorization control point; off by default (no QONTO_FLEX_AUTH_URL set).

Verified beyond mocked unit tests: ran a real flex-auth serve loaded with the registered policy (flex-auth test-policy/load-registry/check all pass) and a real tenant-engine instance seeded with a VEN grant for tenant:friendly:binky, and exercised this repo's actual clients against both live processes over real HTTP — allow for the correct tenant, live_authz_denied for a mismatched one. 28 new unit tests.

Task: Define the rail-knative internet-facing runtime contract

id: QONTO-WP-0004-T05
status: done
priority: high
state_hub_task_id: "e8a87292-488d-4871-be3e-59a5b6a38694"

Per docs/SecurityPractice.md §2/§6, the internet-facing wake path must authenticate and authorize before waking the real service, and the backend must still scale back to zero when idle. Under the new framework direction, this repo should no longer assume a custom in-repo facade or a permanent direct Deployment target. The default plan is now a new rail-knative hosted by reef-railiance, with this repo describing the app-side contract that rail must satisfy.

This task should define, from qonto-assistant's point of view:

  • the pre-wake authn/authz behavior the rail must guarantee
  • the cold-start and idle-scale semantics the package path depends on
  • which behavior belongs in rail-knative versus the future rapp-qonto package versus this ownership repo
  • which parts of docs/SecurityPractice.md need rewriting now that the target runtime path is explicit

Depends on: RMASTER-WP-0019 defining the cross-repo architecture boundary, plus the future rail-knative bootstrap work that will follow from it.

Done when: the required runtime contract is written clearly enough that future rail-knative and rapp-qonto implementation work can start without reopening the security model, and no new direct-deployment assumption is left as the default target.

2026-07-26: Added docs/knative-runtime-and-rapp-handoff.md. It defines pre-wake controls, cold-start/idempotency/failure behavior, raw-port exposure, identity and credential boundaries, and the evidence required before production approval.

Task: Isolation placement request to Railiance

id: QONTO-WP-0004-T06
status: done
priority: medium
state_hub_task_id: "7cd52269-ca0a-4b09-ab51-f02ed71604f2"

2026-07-24: Made the request concrete and reviewable (this task's own stated scope — not to implement scheduling itself). Delivered:

  • deploy/k8s/qonto-assistant/ — dedicated namespace (not shared with activity-core), Deployment at replicas: 0 (meant to be scaled 0↔1 by the facade, T05), ClusterIP-only Service, default-deny NetworkPolicy. Modeled on llm-connect's real deployment, tightened per docs/SecurityPractice.md §7. Verified: kubectl kustomize renders all six resources cleanly.
  • CCR-2026-0009 (in railiance-platform, status: proposed) — a workload-scoped Kubernetes-auth access lane into the existing tenants/binky/qonto-api credential, since CCR-2026-0008 is human/OIDC admin access only and unusable by a running pod. Mirrors CCR-2026-0003's llm-connect pattern. Paired draft ClusterSecretStore also added. Both validated against schemas/credential-change-request.schema.yaml.
  • railiance/app.toml — staged-promotion contract, criticality = "critical" (mandatory human approval before Stage 2 exposure and Stage 3 promotion, per the contract's own rule for production-critical workloads). Validated against railiance-cluster/schemas/railiance-app.schema.json.

Still pending, explicitly not this task's to resolve: CCR-2026-0009 needs platform-operator + binky-tenant-owner approval; the actual isolation tier (I1 vs I2) is Railiance's scheduling call once the manifests are reviewed; whether the facade (T05) co-locates in this namespace is assumed in networkpolicy.yaml but not yet decided.

2026-07-26 direction update: this direct Kubernetes placement package now serves as migration input and isolation evidence rather than the intended final runtime topology. Future implementation should treat these manifests, the CCR, and the staged-promotion contract as baseline material to be re-homed into the reef-railiance + rail-knative + rapp-qonto path, not as the long-term deployment target of this repo.

Task: Plan the rapp-qonto extraction and runtime handoff

id: QONTO-WP-0004-T08
status: done
priority: high
state_hub_task_id: "e265cce1-df09-48ca-a0fd-f38897c44b4c"

Define how the current qonto-assistant deployment-oriented material hands off into a future rapp-qonto package repo while preserving this repo as the ownership home for the Qonto domain logic.

At minimum, this task must name:

  • what stays in qonto-assistant
  • what becomes package/runtime content in rapp-qonto
  • how KEY-WP-0004's workload-identity lane and the OpenBao secret lane bind to the future runtime principal
  • which current files are migration input only and must stop growing in place

Done when: the extraction and handoff boundary is written; the dependency on RMASTER-WP-0019 is explicit; and no new direct deploy/k8s/qonto-assistant implementation starts without an explicit exception note.

2026-07-26: The handoff document now assigns domain behavior and application policy to this repo, package/runtime assets to rapp-qonto, and generic activation/revision behavior to rail-knative. Existing direct Kubernetes assets are explicitly migration input.

Task: Closure review

id: QONTO-WP-0004-T07
status: done
priority: low
state_hub_task_id: "2a2da206-d182-4a72-aea4-56e58ae74c76"

Close when T03T06 and T08 land or are explicitly deferred with a recorded reason. T01/T02 already shipped without waiting on external dependencies, and T06 now counts as migration input rather than the final runtime shape. Run statehub fix-consistency.

2026-07-27: Closure review passed. T03T06 and T08 are complete, the ownership/runtime split is implemented by rapp-qonto and rail-knative, the OpenBao-backed secret lane is live, and restricted Qonto egress is enforced by a controlled proxy plus a fail-closed startup gate. Remaining deployment, rollback, and reef risk evidence belongs to REEF-RAILIANCE-WP-0003, not this source-repo hardening plan.