Skip to content

Threat model

This page makes the threat model explicit so you can audit it against your own threat assumptions. The structure is the standard "adversary → asset → control → out of scope".

Adversaries

Adversary Capability Goal
External attacker reaches the public Ampora hostname take over the management plane and pivot to the fleet
Compromised agent controls one collector binary escalate to other agents or to the server
Hostile operator has UI access with reduced privileges gain Admin powers
Malicious insider has Admin powers exfiltrate data, hide tracks
Compromised peer runs a federated Ampora that talks to ours exfiltrate or tamper with our fleet view
Supply-chain attacker controls a dependency or build pipeline sneak code into a release

Assets we protect

  • Agent control surface — every config push and package transfer.
  • CA signing keys — issuance authority for every agent cert.
  • Bootstrap tokens — single-use; expire fast; never stored plaintext at rest.
  • Audit log — append-only record of every state change.
  • OIDC client secret — the application's identity to your IdP.
  • GitOps source credentials — PATs and SSH keys for upstream repos.

Controls

Transport

  • mTLS between agents and Ampora after bootstrap. The server identifies the agent by certificate fingerprint, not instance_uid (which is agent-supplied and untrusted).
  • TLS on the operator path. Server TLS is your responsibility on the reverse proxy; Ampora always honours X-Forwarded-Proto.
  • mTLS + shared secret for federation peers. Both factors required.

AuthN / AuthZ

  • OIDC for operator login. PKCE on the Authorization Code flow.
  • RBAC with three roles (Admin, Operator, Viewer). All state-changing endpoints require an explicit role; not "any authenticated user".
  • Bootstrap tokens are short-lived, single-use, hashed at rest.
  • Capability gating for OpAMP: server-to-agent pushes only happen when the agent has signalled the matching capability.

Encryption at rest

  • IKeyProtector wraps every secret column with AES-GCM-256 by default. The same abstraction transparently delegates to AWS KMS, Azure Key Vault, GCP KMS, PKCS#11 or Vault Transit when configured.
  • Master key never lives in the database. It is read at startup from a secret you control.
  • KeyProtection:PreviousMasterKey allows a one-cycle rotation without downtime.

Audit and tamper-evidence

  • Append-only audit table. Every state change is recorded with actor, entity, before/after JSON snapshot, and timestamp.
  • No mutation API for audit rows.
  • RFC-3161 timestamping is opt-in for compliance-driven deployments.

Hard isolation between tenants

  • PostgreSQL Row-Level Security policies on every multi-tenant table. Database-layer enforcement, not just application-layer.
  • Per-tenant connection roles so even a SQL injection survives tenant-isolation.
  • Federation does not cross tenants — federated reads only see the caller's own tenant; tenant IDs never leave the local server.

Defense in depth on agent control

  • Capabilities are opt-in. AcceptsPackages and AcceptsConnectionSettings are off by default in agents we ship templates for; turning them on is an explicit choice.
  • Package signatures with cosign; agents reject unsigned binaries.
  • Health gates auto-pause rollouts on apply-failure rates above configurable thresholds.

Defense in depth on operator actions

  • Custom policies can require approvals (four-eyes) before publication.
  • Policy DSL evaluator runs fail-closed on a 50 ms budget so a pathological expression cannot DoS the rollout engine.

Out of scope

We do not claim to defend against:

  • Compromised PostgreSQL. If an attacker has SQL-level write access to the Ampora database, they own the management plane. Protect the DB the way you protect any other crown-jewel store.
  • Compromised secret manager. If an attacker can read the master key, every encrypted-at-rest column becomes plaintext. Same logic.
  • Hardware-level attacks on the Ampora host. The HSM/KMS adapter raises this bar, but a sufficiently determined physical attacker always wins.
  • Side-channel timing attacks at sub-millisecond resolution. We use constant-time compares for secrets but make no claims for cache timing on shared physical hardware.

Specific scenarios and the relevant controls

"An attacker steals an agent's mTLS cert."

  • They can connect to Ampora as that agent until the cert is revoked.
  • Control: revoke via the Identities UI; CRL/OCSP propagation is ≤ 15 minutes.
  • Detection: an unexpected source IP for a known agent fingerprint shows up in audit; the Bound agent column on Identities flags it.

"An attacker steals a bootstrap token before redemption."

  • They can register one fake agent as that identity.
  • Control: revoke the token (UI). New first-connect redemptions fail.
  • Pool case: rotate the pool admin secret if a pool-issued token leaked.

"An attacker steals the OIDC client secret."

  • They can mint OIDC tokens that will not be accepted by Ampora unless they also compromise the IdP — Ampora validates the issuer signature against the IdP's JWKS.
  • Control: rotate the secret in the IdP and in Ampora's Authentication__Oidc__ClientSecret.

"An attacker compromises one Ampora instance in HA."

  • The dispatch backplane gives them the ability to send dispatches to any agent.
  • Control: every dispatch carries a fencing token tied to the instance's lease — when the next leader is elected, the previous leader's fencing token becomes invalid.
  • Detection: every dispatch is audited with the originating instance ID.

"A federated peer turns hostile."

  • They can return any JSON for GET /api/federation/agents.
  • Control: federation is read-only; the caller never trusts the data for write decisions. The peer's name is shown explicitly in the UI ("from peer:eu-region") so an operator never confuses remote data with local.
  • Detection: outbound federation calls are audited; abnormal response shapes get an inline warning chip.