DocsGuidesAccess control

Access control

Two independent, per-tunnel controls, both enforced at the edge before a request or connection ever reaches your local app: HTTP basic auth gates ngstone port tunnels, and source-IP allowlisting gates both ngstone port and ngstone tcp.

HTTP basic auth#

Require a username and password before the edge forwards anything:

$ ngstone port 3000 --basic-auth alice:secret
https://amber-fox-42.example.com (basic auth: 1 credential)

Repeat the flag for multiple valid credentials — any one of them succeeds:

$ ngstone port 3000 --basic-auth alice:secret --basic-auth bob:hunter2

Load credentials from a file instead — one user:pass pair per line, blank lines and lines starting with # ignored:

creds.txttext
# staging credentials
alice:secret
bob:hunter2
$ chmod 600 creds.txt
$ ngstone port 3000 --basic-auth-file creds.txt

Or set NGSTONE_BASIC_AUTH, one credential per line, same as the file format:

$ export NGSTONE_BASIC_AUTH=$'alice:secret\nbob:hunter2'
$ ngstone port 3000
--basic-authstring
user:pass. Repeatable for multiple credentials.
--basic-auth-filestring
Path to a file with one user:pass credential per line.
NGSTONE_BASIC_AUTHenv
One user:pass credential per line.

Credential precedence#

Only one source is used — they don't merge. The first one present wins:

1. --basic-authhighest precedence
Any use of the flag, even once, takes over completely and ignores the other two sources.
2. --basic-auth-file
Used if --basic-auth was never passed.
3. NGSTONE_BASIC_AUTHlowest precedence
Used only if neither flag was passed.

IP allowlisting#

Restrict a tunnel to a set of source CIDRs — works for both HTTP and raw TCP tunnels:

$ ngstone tcp 22 --allow-cidr 203.0.113.0/24
tcp://amber-fox-42.example.com:14201 -> 127.0.0.1:22

Comma-separate multiple ranges:

$ ngstone port 3000 --allow-cidr 203.0.113.0/24,198.51.100.42/32
--allow-cidrstring
Comma-separated CIDRs allowed to reach the tunnel. Empty allows any source.

A connection from outside every listed range is rejected before it reaches your app: 403 Forbidden for HTTP, a closed connection for TCP.

Combining both#

Both controls can apply to the same HTTP tunnel at once. The edge checks the source IP first, then basic auth — a request that fails the allowlist never gets a chance to present credentials:

$ ngstone port 3000 --allow-cidr 203.0.113.0/24 --basic-auth alice:secret
Basic auth is HTTP-only. ngstone tcp accepts --allow-cidr but rejects --basic-authat bind time — there's no HTTP request to attach credentials to inside a raw TCP stream.

Security notes#

Prefer the file or the env var over the flag

Command-line arguments are visible to any other local user for the lifetime of the process — via ps or /proc/<pid>/cmdline on Linux. On a shared or multi-tenant machine, --basic-auth leaks your credentials to anyone who can run ps auxww. Use --basic-auth-file (mode 0600) or NGSTONE_BASIC_AUTH instead.

Credentials never cross the wire in plaintext. The agent hashes each user:pass pair with SHA-256 before sending it in the Bind message, and the edge stores and compares only that digest — it never sees or stores your password.

Digests are unsalted

Identical credentials always hash to the same digest. That's fine against a passive eavesdropper, but a captured digest is still subject to an offline dictionary or rainbow-table attack against common user:pass combinations. Use strong, unique passwords, not a reused low-entropy one.

--allow-cidrtrusts whatever the edge believes is the connection's source IP. Behind a front proxy with -proxy-protocol, that means trusting the PROXY header the proxy attaches — and the edge must be told which peers are allowed to send one, with -proxy-protocol-trusted <CIDRs> on ngstoned. Without that, or without firewalling the public port to the proxy alone, a direct client can open a raw connection, forge its own PROXY header, and spoof an allowed source IP straight past the allowlist. See Behind an existing reverse proxy.

Once the edge has verified basic auth for a request, it deletes the Authorization header before forwarding — your local app and the request inspector never see the credential. If no basic auth is configured for that tunnel, Authorization passes through untouched, exactly like every other header.

Requires protocol v2#

Access control is carried in wire protocol v2. If an agent requests --basic-auth or --allow-cidr and the edge only negotiates v1, the agent refuses to bind — it fails closed rather than silently opening an unprotected tunnel:

ngstone: edge does not support access control (negotiated protocol v1); upgrade ngstoned to use --basic-auth/--allow-cidr

Upgrade ngstoned to a version that offers protocol 2 to use either flag. See Versioning in the wire protocol reference.