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

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:

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:

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:

  1. 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.
  2. 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 creds gives you that CA so sslmode=verify-full can validate the server's identity, not just encrypt the wire.
  3. Normal Postgres password auth, unchanged from the in-cluster path — brokered live by adj creds, never stored by the control plane.
  4. Rate limiting at the router: capped connections per source IP and a global cap, to blunt brute-force/scanning traffic.
  5. 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