Repository navigation
Host-signed integration-proxy calls for installations, with a loopback exception for exactly the proxy (#1700) - #1702
Conversation
…ode agent (#1700) Server-side plugins reach the integration proxy through ctx.http, signed by the host (ontola/atomic-plugins#54, decisions 8 and 12): - --integration-proxy-url / ATOMIC_INTEGRATION_PROXY_URL: the proxy origin, validated at boot and held on the Db. - HostCore::fetch signs requests to exactly that origin with a v2 request signature as this node's app agent for the installation (get_authentication_headers_v2 inside with_app_agent). Plugin-supplied x-atomic-* headers are dropped; an installation without an agent on this node is refused before connecting. - egress::ProxyOrigin / destination_addresses: a loopback exception for exactly the configured scheme, host and port, connecting to loopback without a lookup. Every other loopback or private target stays refused. - A test that GET /app-agent reports the identity activation mints for a JS Installation (decision 2): catalog installations already get one per node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merge-risk triage (2026-09-28). This is a read-only review of this PR's own diff. The rule it applies: nothing that works today may break for existing users or clients, and new behaviour is added alongside the old. The stack is being rebased onto Verdict: SAFE Signing, and the loopback exception for exactly the proxy origin, only apply when Note: 1702 → 1725 → 1731 → 1831 is a clean, flag-gated server chain and can land first. 🤖 Generated with Claude Code |
|
Closing as part of the plugin-host stack cleanup (Q-101): not needed by atomic-plugins now. See the inventory at #1726 (comment). The branch is kept, and pin candidates are unaffected. Closed by Claude (VPS coordinator) on behalf of Michiel. |
Pieces 1 and 3 of #1700, the atomic-server host side of phase 2 of ontola/atomic-plugins#54 (decisions 2, 8 and 12).
Base: #1696 (
claude/atomic-signature-v2), not #1697. This PR only needsget_authentication_headers_v2from #1696. It touches nothing in #1697's frame capabilities or page code, and nothing in #1689's catalog installs. Stacking on #1697 would add its browser diff to this review without making anything here work better. Once #1696 merges, this retargets tofeat/plugin-debug.Piece 1: an app identity for installed catalog plugins
Reading the code, a JS Installation already gets one. Activating it mints an app agent on that node (
ensure_js_identity,Tree::AppAgentkeyed by drive and installation), and every node that activates it mints its own. So it is already per (installation, node), which is the shape decision 10 asks for. What was missing was proof that the page can read it, and a use for it. This PR adds both:GET /app-agent?drive&app=<installation>reports that agent. That's the agent a page delegates to or registers withPOST /runtimes;#1697's refusal ("only apps with their own agent") only hits drive apps without a key. Catalog drive apps from #1689 go through
createAppand get one.The proxy needs
app≠ runtime, so which agent counts as the installation'sapp, and where that's recorded, is not settled by #54. It's open question 1 in #1700, so this PR doesn't pick one.Piece 3: the host signs
ctx.httpto the proxy--integration-proxy-url/ATOMIC_INTEGRATION_PROXY_URL: the proxy's origin, exactly itsBASE_URL. It is validated at boot: http(s), no path, query, fragment or credentials. It's held on theDb, next to the node key, so everyHostCoresees it without passing a new field through the ~20StoreHostconstruction sites.HostCore::fetch): a request to exactly that origin is signed with a v2 signature (method, full URL with query, SHA-256 of the body). It signs as this node's app agent for the installation, usingget_authentication_headers_v2insidewith_app_agent, so the key never leaves the store.x-atomic-*headers are dropped first, so a plugin can't pick its signer.egress::ProxyOrigin,destination_addresses): only when the configured origin is on loopback (localhost,127.x,::1), and only for exactly that scheme, host and port.localhostis never resolved, so DNS can't redirect it.Tests (written first, cheapest layer: Rust unit tests)
plugins::host_core:a_request_to_the_configured_proxy_is_signed_v2_as_the_installations_node_agent: a real request to a one-shot loopback server standing in for the proxy. It checks the agent header and the single v2 signature. The signature is verified withcheck_auth_signatureover method, full URL and body, and fails for another body;an_installation_without_an_agent_on_this_node_cannot_reach_the_proxy;the_proxy_exception_does_not_open_other_loopback_origins: another port, the other scheme,localhostwhen127.0.0.1is configured, and127.0.0.2. All are refused even when the manifest declares them.plugins::egress:a_proxy_origin_is_an_origin_and_nothing_more;the_configured_loopback_proxy_is_reached_at_loopback_without_a_lookup;every_other_loopback_target_is_still_refused(also covers[::1], credentials in the URL, and no proxy configured);a_proxy_elsewhere_gets_no_exception.handlers::app_endpoints_test::an_active_installation_reports_its_agent_on_this_node.cargo test -p atomic-server --lib: 274 passed.cargo test -p atomic_lib --features db-redb --lib: 657 passed.cargo clippy -p atomic-server -p atomic_lib --testsis clean.No UI changes.
Refs #1700, ontola/atomic-plugins#54, ontola/atomic-plugins#122.
🤖 Generated with Claude Code