ghostunnel

ghostunnel is a simple TLS proxy with mutual-authentication support, written in Go and maintained at ghostunnel/ghostunnel. Its job is to wrap plaintext applications in TLS — either terminating TLS in front of an insecure backend, or giving an insecure local client a TLS-speaking front end — without modifying the application itself.

It runs in two modes:

  • Server mode — accepts TLS connections and proxies them to an insecure backend (TCP host/port or UNIX socket). Enforces mutual TLS by requiring valid client certificates, with fine-grained access control on certificate fields (--allow-cn, --allow-ou, --allow-dns, --allow-uri) or declarative authorization via Open Policy Agent policies.
  • Client mode — accepts insecure local connections (TCP or UNIX socket) and proxies them to a TLS-secured upstream, presenting a client certificate. Target verification is hostname-based by default; --override-server-name overrides the name used for verification when the server’s certificate CN/SAN doesn’t match the dial address (common when tunneling to an IP or through an SSH forward).

Key properties from the project’s docs:

  • Certificate sources — PEM (--cert/--key or combined), PKCS#12 (--keystore), ACME (Let’s Encrypt, server mode), PKCS#11 HSMs (including YubiKey), macOS/Windows keychains, and the SPIFFE Workload API.
  • Certificate hotswapping — reload cert/key/CA on SIGHUP/SIGUSR1 or a --timed-reload interval, enabling short-lived certificates without restarts. SO_REUSEPORT allows a replacement instance to bind before the old one exits.
  • Secure by default — listeners and targets are restricted to localhost/UNIX sockets unless --unsafe-listen/--unsafe-target is passed; on Linux, Landlock sandboxing is enabled by default.
  • Metrics — optional status port with JSON and Prometheus endpoints, plus pprof profiling.

Usage pattern

A typical client-mode deployment that fronts a plaintext app with mTLS:

# Server side: terminate TLS in front of a plaintext backend on :8080.
ghostunnel server \
    --listen localhost:8443 \
    --target localhost:8080 \
    --keystore server-keystore.p12 \
    --cacert cacert.pem \
    --allow-cn client
 
# Client side: app talks plaintext to localhost:8000; ghostunnel
# wraps it in mutually-authenticated TLS to the upstream.
ghostunnel client \
    --listen localhost:8000 \
    --target upstream.example.com:8443 \
    --keystore client-keystore.p12 \
    --cacert cacert.pem

Point the application at http://localhost:8000 and ghostunnel handles the handshake, client-certificate presentation, and encryption. When the upstream is reached through a jump host, a plain ssh -L forward can bring the target to a local port first, with ghostunnel layering mTLS on top.

Testing with openssl s_client -cert ... -key ... -CAfile ... is the standard way to verify the mTLS handshake from the outside; see openssl-s-client-command-injection and pull-ssl-certificates-external-server for related OpenSSL client workflows.

Assessment relevance

On engagements, ghostunnel (or its cousin stunnel) shows up as the TLS shim in front of internal services — databases, message queues, admin panels. Finding the client keystore plus its CA bundle on a compromised host means you can stand up your own client-mode ghostunnel and reach the mTLS-protected backend as a legitimate peer, bypassing the perimeter entirely. The --allow-* flags on the server side also tell you exactly which certificate fields the target trusts (e.g. only CN), which shapes what a forged or mis-issued certificate needs to contain.

Sources

See also