access-flags.md
July 18, 2026 · View on GitHub
Ghostunnel uses mutual TLS for authentication and access control. Both the client and server present a certificate that the other party verifies.
Server mode
Several flags restrict which clients can connect, based on fields in the client certificate.
When multiple certificate-field flags (--allow-cn, --allow-ou,
--allow-dns, --allow-uri) are specified, they are OR'd together: a client is
allowed if at least one flag matches. --allow-policy/--allow-query may also
be combined with these flags and is OR'd together with them. The remaining flags,
--allow-all and --disable-authentication, are each mutually exclusive with
the rest.
--allow-all
Allow all clients with a valid certificate, regardless of the certificate subject. Mutually exclusive with other access control flags.
--allow-cn
Allow clients with the given common name (CN). Can be repeated. Performs an exact string match.
--allow-ou
Allow clients with the given organizational unit (OU). Can be repeated. Performs an exact string match.
--allow-dns
Allow clients with the given DNS subject alternative name (DNS SAN). Can be repeated. Matches the DNS SAN value on the certificate; no DNS lookups are performed.
--allow-uri
Allow clients with the given URI subject alternative name (URI SAN). Can be
repeated. Supports * and ** wildcards.
For example, setting --allow-uri=spiffe://ghostunnel/* would allow clients
with spiffe://ghostunnel/client1 or spiffe://ghostunnel/client2 URI SANs (as
well as other values). See documentation for the wildcard package
for more information.
--allow-spki-pin(available since v1.11.1)
Allow clients whose leaf certificate's public key matches the given SPKI pin,
of the form <algo>:<base64-digest>, where <algo> is one of sha256,
sha384, or sha512, and <base64-digest> is the base64 encoding of the
hash of the DER-encoded SubjectPublicKeyInfo (hash construction per
RFC 7469 §2.4). Can be repeated to allow multiple keys (e.g. a
current and a backup key for rotation).
This is out-of-band key pinning in the style of RFC 7858 §4.2: the
client is authenticated by the pin alone, so the certificate chain, validity
period, and hostname are not verified and a pinned key need not chain to a
trusted CA. Mutually exclusive with other access control flags and with
--use-workload-api.
When --allow-spki-pin is set, --cacert has no effect on client
verification: the client is authenticated by the pin alone, and ghostunnel does
not advertise the CA pool in the TLS handshake (so a strict client is not
prompted to withhold a pinned certificate that does not chain to it).
Prefer an OPA policy if the client is already PKIX-valid.
--allow-spki-pinis for clients whose certificate is not otherwise trusted (self-signed or out-of-band-distributed keys). If clients already present a certificate that chains to--cacertand you only want to additionally restrict to a specific key, keep normal chain validation and pin via--allow-policyoverinput.certificate.RawSubjectPublicKeyInfo(its SHA-256 is the SPKI pin). See the Open Policy Agent section below.
--allow-policyand--allow-query(OPA bundle support since v1.9.0)
Allow clients where a Rego policy evaluates to true with the given query.
For more information, see the Open Policy Agent section below.
--disable-authentication
Disable client authentication entirely, no client certificate is required. Anyone can connect. Mutually exclusive with other access control flags.
Passing Client Identity to Backends
Ghostunnel verifies client certificates before forwarding connections, but
backends may also need to know the client's identity for their own access
control, logging, or auditing. Use --proxy-protocol-mode=tls-full (available
since v1.10.0) to forward the client certificate (CN, full DER-encoded cert) to
the backend via [PROXY protocol v2]({{< ref "proxy-protocol.md" >}}) TLV
extensions.
Client mode
In client mode, additional flags can verify properties of the server
certificate. Standard hostname verification always runs regardless of which
flags are set; by default it checks the hostname from --target against the
server certificate's DNS/IP SANs, but --override-server-name (see below) can
redirect verification to a different name (e.g. when dialing by IP, or when
the cert's SAN doesn't match the dialed host). Two flags are exceptions that
replace hostname verification entirely rather than adding to it: the
[SPIFFE Workload API]({{< ref "spiffe-workload-api.md" >}}) (when
--use-workload-api is set, hostname verification is replaced by SPIFFE
authentication — peers are verified as presenting a valid X509-SVID, so use
--verify-uri to pin the expected SPIFFE ID), and --verify-spki-pin (see below,
which authenticates the server by its SPKI pin alone).
When multiple verification flags are specified, they are OR'd together: a connection is allowed if at least one flag matches (and hostname verification passes).
--override-server-name
Override the server name used for hostname verification and SNI, instead of
using the hostname from --target. Useful when dialing by IP, when the
backend's certificate SAN does not match the dial address, or to pin SNI to a
specific virtual host. Ignored when --use-workload-api is in effect. See the
ServerName field on tls.Config.
--verify-cn
Verify the common name (CN) of the server certificate, in addition to hostname verification. Can be repeated. Performs an exact string match.
--verify-ou
Verify the organizational unit (OU) of the server certificate, in addition to hostname verification. Can be repeated. Performs an exact string match.
--verify-dns
Verify that a DNS subject alternative name (DNS SAN) is present on the server certificate, in addition to hostname verification. Can be repeated.
--verify-uri
Verify that a URI subject alternative name (URI SAN) is present on the server
certificate, in addition to hostname verification. Supports * and **
wildcards.
For example, setting --verify-uri=spiffe://ghostunnel/* would allow servers
with spiffe://ghostunnel/server1 or spiffe://ghostunnel/server2 URI SANs (as
well as other values). See documentation for the wildcard package
for more information.
--verify-spki-pin(available since v1.11.1)
Verify the server's leaf certificate's public key against the given SPKI pin,
of the form <algo>:<base64-digest>, where <algo> is one of sha256,
sha384, or sha512, and <base64-digest> is the base64 encoding of the
hash of the DER-encoded SubjectPublicKeyInfo (hash construction per
RFC 7469 §2.4). Can be repeated to accept multiple keys (e.g. a
current and a backup key for rotation).
Unlike the other verification flags, this is out-of-band key pinning in the
style of RFC 7858 §4.2: the server is authenticated by the pin
alone, so standard hostname verification, chain validation, and validity-period
checks do not run. Mutually exclusive with the other verification flags and
with --use-workload-api. It may, however, be combined with
--disable-authentication: on the client that flag only suppresses sending a
client certificate, so the pair yields a deployment where ghostunnel presents no
certificate of its own and authenticates the server by pin alone — the typical
DNS-over-TLS (DoT) style setup that motivates SPKI pinning.
Prefer an OPA policy if the server is already PKIX-valid.
--verify-spki-pinis for servers whose certificate is not otherwise trusted (self-signed or out-of-band-distributed keys). If the server already presents a certificate that chains to--cacertand passes hostname verification, and you only want to additionally restrict to a specific key, keep normal validation and pin via--verify-policyoverinput.certificate.RawSubjectPublicKeyInfo(its SHA-256 is the SPKI pin). See the Open Policy Agent section below.
--verify-policyand--verify-query(OPA bundle support since v1.9.0)
Verify that a Rego policy evaluates to true with the given query.
For more information, see the Open Policy Agent section below.
--disable-authentication
Disable client authentication, no certificate is sent to the server. Useful when the backend does not require mutual TLS.
Open Policy Agent
Available since v1.7.0, OPA bundle support available since v1.9.0.
Ghostunnel supports Open Policy Agent (OPA) in both server and client
mode. The policy must be an OPA bundle on disk. In server mode,
--allow-policy/--allow-query may be combined with the other access control
flags and are OR'd together with them; likewise in client mode,
--verify-policy/--verify-query may be combined with other verification flags.
In both cases we recommend expressing all access control logic in the policy
itself. Policy bundles reload at runtime via --timed-reload or SIGHUP, just
like certificates.
To build a bundle from a .rego file, use the opa build command:
opa build policy.rego -o bundle.tar.gz
See the OPA bundle documentation for details on bundle structure and manifest options.
To use it in server mode, specify the --allow-policy and --allow-query flags.
Example:
ghostunnel server [...] --allow-policy=bundle.tar.gz --allow-query=data.policy.allow
To use it in client mode, specify the --verify-policy and --verify-query flags.
Example:
ghostunnel client [...] --verify-policy=bundle.tar.gz --verify-query=data.policy.allow
Inside your policy, the peer's X.509 certificate is available as
input.certificate. The example below checks whether the client certificate
contains an allowed common name or SPIFFE ID.
You can use the Rego Playground to test and
develop policies. See the documentation for x509.Certificate
for the structure of the input.certificate variable.
Example (Playground):
package policy
import input
default allow := false
allowed_common_names = [
"client1",
"client2",
]
allowed_spiffe_ids = [
"example.com/client1",
"example.com/client1/*",
"example.com/client2",
"example.com/client2/*",
]
allow if {
# Allow if common name matches a pattern in allowed_common_names
some common_name in allowed_common_names
glob.match(common_name, [], input.certificate.Subject.CommonName)
}
allow if {
# Allow if one of the URI SANs matches a pattern in allowed_spiffe_ids
some uri in input.certificate.URIs
some spiffe_id in allowed_spiffe_ids
# Basic sanity checks for the URI SAN before we compare
uri.Scheme == "spiffe"
# User, query, fragment, etc. should not be set in the URI SAN
not uri.User
not uri.Opaque
not uri.RawQuery
not uri.Fragment
not uri.RawFragment
# Match host/path against the pattern
glob.match(spiffe_id, [".", "/"], sprintf("%s%s", [uri.Host, uri.Path]))
}
The corresponding query for this policy is data.policy.allow.
See x509.Certificate for all available fields, and the Rego documentation for the policy language reference.
Notes
- Policy bundles must be local files. There is no built-in support for loading from a remote OPA server. Fetch and store bundles locally using a separate process.
- Passing a raw
.regofile instead of a bundle to--allow-policyor--verify-policystill works for backward compatibility (treated as V0). Using a bundle is recommended so you can set the Rego language version in the bundle manifest. - By OPA convention, a policy is considered "allowed" if the query produces
exactly one result with a single expression whose value is
true, and no variable bindings. - Policy evaluation timeout is the same as the connection timeout. If a policy takes more time to execute than the specified connection timeout, the connection will fail.