Connectivity
How does a laptop actually reach a clone? It depends on one org-level setting: the public endpoints policy. This page covers both paths — the public one (when your admin has opted in) and the in-cluster fallback (always available) — plus the security model behind the public path.
The two paths at a glance
| Public endpoints ON | Public endpoints OFF (default) | |
|---|---|---|
| Reachable from | anywhere on the internet | inside the data-plane VPC only |
| Hostname | e-<instance-token>.c-<cluster-token>.db.<domain>:5432 |
i-<uuid>-rw.ns-<namespace>.svc:5432 (in-cluster DNS) |
| TLS | mandatory, sslmode=verify-full |
not enforced |
| How you get there | adj creds prints a ready-to-use URI |
VPN/bastion into the VPC, or kubectl port-forward via adj kubeconfig |
Every org starts with public endpoints off. An admin opts in explicitly — see Enabling it below.
Public endpoints (policy on)
Hostname anatomy
A publicly reachable instance gets a hostname of the form:
e-<instance-token>.c-<cluster-token>.db.<domain>:5432
<instance-token>is a crypto-random, unguessable token minted for that specific instance at launch time.<cluster-token>is a crypto-random token minted once per cluster.- The capability to reach that one database lives in the hostname itself — knowing the instance token is what lets a connection route to your instance at all. Nothing about the org, user, or golden name appears in the hostname.
Connecting
$ adj creds 0ab2b1ab-bd4a-4e1a-9ab8-c3b0251ee37d --uri
postgres://app:***@e-a7f3d9c1e2b04f5a.c-9d2e1f4a7b3c0d8e.db.stagdb.com:5432/app?sslmode=verify-full&sslrootcert=/home/you/.config/adj/ca/0ab2b1ab-bd4a-4e1a-9ab8-c3b0251ee37d.pem
When an instance has a public endpoint, adj creds transparently:
- returns a URI pointed at the public hostname instead of the in-cluster service name,
- sets
sslmode=verify-full, - saves the cluster's private CA certificate to
<config-dir>/ca/<instance-id>.pem(mode0600) and appendssslrootcert=<path>to the URI.
adj creds --env does the equivalent for shell export, additionally
setting PGSSLROOTCERT and PGSSLMODE=verify-full:
$ eval "$(adj creds 0ab2b1ab-bd4a-4e1a-9ab8-c3b0251ee37d --env)"
$ psql
As always, credentials are fetched live and never written to the CLI config — only the CA certificate (public material, not a secret) touches disk.
Note on adj ls / adj get: when an instance has a public endpoint,
adj ls shows the public hostname in the ENDPOINT column (instances
without one keep showing the in-cluster service name), and adj get adds a
Public host row alongside the in-cluster Endpoint. The raw field is
public_host with -o json. For a ready-to-use connection string, reach
for adj creds.
Enabling it
Any org member can check the current setting; only an admin can change it:
$ adj org policy
Public endpoints: false
$ adj org policy set public-endpoints on
Public endpoints: true
Turning it on does not retroactively expose already-running instances — public hostnames are minted at launch time. New instances launched after the toggle get one automatically. The change itself is an audited action (see Usage and audit).
Turning it off
Being explicit about what adj org policy set public-endpoints off does —
and does not — do:
- New instances launched after the toggle get no public hostname.
adj credsstops returning public URIs for every instance, immediately — you get the in-cluster endpoint form instead.- Existing public endpoints keep working. An instance that already has a public hostname stays routable on it until the instance is destroyed (or its TTL reaps it): its endpoint token, the DNS wildcard, and the TLS certificates all persist. Turning the policy off is a "stop minting new ones" switch, not a kill switch.
- Rotation is owner-only.
adj rotate-endpointinvalidates a leaked hostname, but only the instance's owner can run it — admins cannot rotate someone else's endpoint. - To fully cut off an endpoint, destroy the instance (
adj destroy, which admins can do org-wide). That is the guaranteed revocation path.
Rotating an endpoint
If an instance's hostname needs to be invalidated (leaked in a log, shared too widely, etc.), the owner can mint a fresh one:
$ adj rotate-endpoint 0ab2b1ab-bd4a-4e1a-9ab8-c3b0251ee37d
Rotated endpoint for instance 0ab2b1ab-bd4a-4e1a-9ab8-c3b0251ee37d; new public host: e-6b1c8f2a9d3e7104.c-9d2e1f4a7b3c0d8e.db.stagdb.com
The old hostname stops routing at the edge router within a few seconds. Only the instance owner can rotate it — not other org members, not admins.
Security model
The public path is layered defense, not a single secret:
- Unguessable hostname (~128-bit capability token). The instance token is crypto-random; there's no directory or enumeration path from the outside — you have to already know the hostname to route to it.
- Mandatory TLS. The router refuses any connection that isn't TLS —
plaintext Postgres wire traffic is rejected outright. The router reads
only the TLS SNI to route (passthrough); it never terminates or
inspects the encrypted session. The actual TLS session terminates at
the CloudNativePG instance's own certificate, signed by a private CA
generated per cluster.
adj credsgives you that CA sosslmode=verify-fullcan validate the server's identity, not just encrypt the wire. - Normal Postgres password auth, unchanged from the in-cluster path —
brokered live by
adj creds, never stored by the control plane. - Rate limiting at the router: capped connections per source IP and a global cap, to blunt brute-force/scanning traffic.
- Unchanged NetworkPolicy isolation inside the cluster — the router is the only ingress path into instance namespaces from outside, and the NLB exposes exactly one port (5432 over TLS). Cross-namespace and external-egress default-deny policies are untouched by any of this.
Known caveat: treat the cluster token as a soft secret. The wildcard
certificate for *.c-<cluster-token>.db.<domain> is signed by the
cluster's private CA — it is never submitted to Certificate
Transparency, so it doesn't leak there — but the token still appears in
DNS: it is part of every public hostname (so it shows up in DNS resolver
and query logs along the resolution path), and the wildcard DNS record
itself carries it. Knowing the cluster token alone doesn't get you into any
instance — you still need an instance token — but don't rely on it staying
private. The instance token never appears anywhere except the client's
own connection string.
Honesty check: a live penetration-test pass against this policy stack hasn't been run yet (tracked the same way as the rest of the open items on the Security model page).
In-cluster only (policy off, or before you opt in)
This is the default, and always available regardless of the public policy:
$ adj ls
ID GOLDEN VER PHASE SIZE TTL EXPIRES ENDPOINT
0ab2b1ab-bd4a-4e1a-9ab8-c3b0251ee37d prod-pg16 3 Ready medium 72h 47h12m i-0ab2b1ab-bd4a-4e1a-9ab8-c3b0251ee37d-rw.ns-11d3e4c2-58ea-4a7b-9c19-2f4f60d5a3b7.svc:5432
That ENDPOINT value is a Kubernetes in-cluster DNS name — reachable only
from inside the data-plane VPC. Two ways to get there from a laptop:
VPN/VPC access. If your org already has VPN or bastion access into the
data-plane VPC, connect directly using the .svc endpoint and adj creds
for the password.
kubectl port-forward, using the kubeconfig adj itself manages:
$ export KUBECONFIG=$(adj kubeconfig)
$ kubectl -n ns-11d3e4c2-58ea-4a7b-9c19-2f4f60d5a3b7 port-forward svc/i-0ab2b1ab-bd4a-4e1a-9ab8-c3b0251ee37d-rw 5432:5432
Forwarding from 127.0.0.1:5432 -> 5432
Then, in another terminal:
$ eval "$(adj creds 0ab2b1ab-bd4a-4e1a-9ab8-c3b0251ee37d --env)"
$ PGHOST=localhost psql
adj kubeconfig prints the path to the adj-owned kubeconfig for your
active context (written by adj cluster provision) — it never touches
your own ~/.kube/config.
Next
- Security model — the full picture: what the control plane stores, how credentials broker, isolation between developers.
- Getting started: admin — where the policy toggle fits in the provisioning flow.