neondatabase/neon
Security
This page covers the trust boundaries in a Neon installation, the authentication mechanisms between components, and a few notable hardening measures. The authoritative document on inter-service auth is docs/authentication.md.
Trust boundaries
graph LR
User[User / SQL client] -->|TLS + SCRAM| Proxy[Proxy]
Proxy -->|libpq + JWT (tenant scope)| Compute[Compute<br/>postgres + neon ext]
Compute -->|libpq + JWT (tenant scope)| PS[Pageserver]
Compute -->|libpq + JWT (safekeeperdata)| SK[Safekeeper]
PS -->|libpq + JWT (safekeeperdata)| SK
Storcon[Storage Controller] -->|HTTP + JWT (pageserverapi)| PS
Storcon -->|HTTP + JWT (safekeeperdata)| SK
Storcon -->|compute_hook HTTP + JWT| ComputeThe trust boundaries:
- External user ↔ proxy. TLS terminates here. The proxy authenticates the user with SCRAM-SHA-256 (or alternative auth backends).
- Proxy ↔ compute. TLS optional. Authentication is via Postgres
cloud_adminor per-role passwords stored in the compute's HBA. - Compute ↔ storage (pageserver, safekeeper). TLS optional. Authentication is JWT with a
tenant-scoped token that names the specific tenant the compute can access. The token is created at compute start by the control plane. - Inter-service control (storage controller ↔ pageserver/safekeeper). TLS optional. JWT with
pageserverapiorsafekeeperdatascope. - Pageserver ↔ remote object storage. Cloud-provider-native auth (AWS SigV4, Azure Shared Key or AAD, GCS service account).
JWT mechanics
Implementation: libs/utils/src/auth.rs.
- Algorithm: EdDSA (Ed25519). RFC 8037.
- Header:
{ "alg": "EdDSA", "typ": "JWT" } - Payload:
{ "scope": "...", "tenant_id": "..." }(tenant is required only fortenant-scoped tokens). - Key pair: one per installation. The private key signs; every component that verifies has the public key in its config.
- No built-in rotation. Rotating the key requires restarting every component. The plan is to add overlapping-key support; until then, key rotation is operator-coordinated.
Scopes (utils::auth::Scope enum):
| Scope | Grants |
|---|---|
tenant |
Access to a specific tenant's data on a specific pageserver/safekeeper. |
pageserverapi |
Blanket access to all tenants on a pageserver plus pageserver-wide HTTP routes. |
safekeeperdata |
Blanket access to all timelines on a safekeeper. Also used for pageserver→safekeeper replication. |
generations_api |
Access to the storage controller's upcall API. |
admin |
Access to controller and admin APIs. |
docs/authentication.md has notes on psql truncating passwords at 100 chars (so JWTs need PGPASSWORD instead).
Tokens in logs
Tokens must not appear in logs. The JSON tracing formatter and the structured logging conventions don't include token fields. Configuration files may contain tokens; those should be considered sensitive.
Compute SCRAM
User-facing auth at the proxy uses Postgres SCRAM-SHA-256 (proxy/src/scram/). SCRAM secrets are pre-computed by the control plane and cached in the proxy. This means the proxy never sees plaintext passwords.
For non-SCRAM environments the proxy supports:
- A passwordless link flow (login email with a one-time link).
- A JWT flow (the auth-broker / REST-broker mode).
- A local postgres auth backend (development only).
SAFETY: WAL-redo subprocess sandbox
The pageserver's postgres --wal-redo subprocess is the most critical sandbox. A malformed WAL record from an attacker-controlled tenant should not be able to escape the redo process.
Defenses:
- Per-tenant subprocess. Each tenant has its own redo process. A crash or exploit affects only that tenant's redo.
- seccomp-bpf filter (Linux). The harness in
pgxn/neon_walredo/installs a syscall filter that allows only the syscalls strictly needed for redo: read/write on the pipe, mmap, and a small set of memory primitives. No network, no fs writes outside the pipes. - No real
PGDATA. The redo subprocess is started without a meaningful data directory; it cannot persist anything that matters. - Process re-spawn on error. If redo returns an error or panics, the manager kills and respawns the process. State is not reused across crashes.
Remote-storage credentials
The pageserver's connection to S3/GCS/Azure uses cloud-provider-native credential mechanisms. In particular:
- AWS: AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY, IAM roles, instance profile metadata.
- Azure: AAD via
azure_identity. - GCS: Application Default Credentials.
The credentials are loaded at startup; rotation requires a restart unless using IRSA / IAM-role-based auth.
TLS certificates
- The proxy holds the user-facing TLS certificate. Configured via
-c <cert>and-k <key>CLI args. - Pageserver / safekeeper / storage controller HTTP can be served over TLS but are most commonly behind a TLS-terminating load balancer.
- Inter-service TLS is supported but not required by default. Consider it when your infrastructure does not provide a private network.
Proxy IP allowlists
Per-project IP allowlists are enforced in the proxy (proxy/src/control_plane/). The list is fetched from the console API and cached.
Rate limiting
Multi-tier rate limiting in proxy/src/rate_limiter/ applies per-IP, per-project, per-endpoint, and per-connection. Limits are configurable.
Dependency policy
cargo deny (deny.toml) enforces:
- License allowlist (Apache-2.0, MIT, BSD variants, …).
- Banned crates (yanked versions, known-vulnerable dependencies).
- Source allowlist (only crates.io and the project's own forks on github.com/neondatabase/...).
The CI gate is .github/workflows/cargo-deny.yml.
Audit areas to be aware of
- Generation numbers are the single most important data-safety invariant. The storage controller DB must be durable. See Storage controller.
- Layer file integrity is checksummed but not signed. A compromised remote-storage bucket is out of scope for the threat model — operator infra is trusted.
- JWT keys rotation requires restart. Plan accordingly.
See also
- Patterns and conventions — error handling, logging.
docs/authentication.md— the full inter-service auth doc.- Proxy — user-facing auth surface.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.