diff --git a/README.md b/README.md index 7e5ad9ff52..c2bb42f8f6 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,13 @@ Getting Started =============== To get started and play around with Ghostunnel you will need X.509 client -and server certificates. If you don't already maintain a PKI, a good way to get -started is to use a package like [cloudflare/cfssl](https://github.com/cloudflare/cfssl). +and server certificates. If you already maintain a PKI, you can use your +existing certificates. Otherwise, you can use tools like +[mkcert](https://github.com/FiloSottile/mkcert) or +[cloudflare/cfssl](https://github.com/cloudflare/cfssl) to build one. -For testing and development purposes, you can generate test certificates using: +For quick testing and development, you can also generate throwaway test +certificates using the built-in generator: # Generate test certificates and keys go tool mage test:keys @@ -335,9 +338,11 @@ See [METRICS](docs/METRICS.md) for details. ### HSM/PKCS#11 support Ghostunnel has support for loading private keys from PKCS#11 modules, which -should work with any hardware security module that exposes a PKCS#11 interface. +should work with any hardware security module that exposes a PKCS#11 interface, +including YubiKeys (via the YKCS11 module). -See [HSM-PKCS11](docs/HSM-PKCS11.md) for details. +See [HSM-PKCS11](docs/HSM-PKCS11.md) for details, including a step-by-step +guide for using Ghostunnel with a YubiKey. ### Windows/macOS Keychain Support diff --git a/certstore/certstore_windows.go b/certstore/certstore_windows.go index cd92d0d0cf..d239e4060e 100644 --- a/certstore/certstore_windows.go +++ b/certstore/certstore_windows.go @@ -96,15 +96,18 @@ func openStore(logger *log.Logger) (*winStore, error) { // Additional stores to use to look for certificates in Identities(). // The identity we want to load might be in the "current service" or "local - // machine" stores, so we need to open those to check. - extraStores := map[string]C.DWORD{ - "CURRENT_SERVICE": C.CERT_SYSTEM_STORE_CURRENT_SERVICE, - "LOCAL_MACHINE": C.CERT_SYSTEM_STORE_LOCAL_MACHINE, - } - for friendlyName, storeIdent := range extraStores { - store := C.CertOpenStore(CERT_STORE_PROV_SYSTEM_W, 0, 0, storeIdent, storeName) + // machine" stores, so we need to open those to check. The order here is + // fixed: CURRENT_SERVICE is searched before LOCAL_MACHINE. + for _, extra := range []struct { + name string + ident C.DWORD + }{ + {"CURRENT_SERVICE", C.CERT_SYSTEM_STORE_CURRENT_SERVICE}, + {"LOCAL_MACHINE", C.CERT_SYSTEM_STORE_LOCAL_MACHINE}, + } { + store := C.CertOpenStore(CERT_STORE_PROV_SYSTEM_W, 0, 0, extra.ident, storeName) if store == nil { - logger.Printf("certstore: failed to open key store '%s', skipping", friendlyName) + logger.Printf("certstore: failed to open key store '%s', skipping", extra.name) continue } diff --git a/docs/ACCESS-FLAGS.md b/docs/ACCESS-FLAGS.md index 3b78fc0de6..743172cff2 100644 --- a/docs/ACCESS-FLAGS.md +++ b/docs/ACCESS-FLAGS.md @@ -135,11 +135,23 @@ but the backend doesn't require mutual authentication. ## Open Policy Agent -Ghostunnel has support for Open Policy Agent (OPA), both in server and client -mode. The policy bundle must be present on disk for Ghostunnel to use it and the -use of OPA is mutually exclusive with any other `allow` (or `verify`) flags. -Policy bundles can be reloaded at runtime much like certificates, with the -`--timed-reload` flag or via `SIGHUP`. +Ghostunnel has support for [Open Policy Agent][opa] (OPA), both in server and +client mode. The policy must be provided as an [OPA bundle][opa-bundles] on +disk and the use of OPA is mutually exclusive with any other `allow` (or +`verify`) flags. Policy bundles can be reloaded at runtime much like +certificates, with the `--timed-reload` flag or via `SIGHUP`. + +[opa]: https://www.openpolicyagent.org/ +[opa-bundles]: https://www.openpolicyagent.org/docs/latest/management-bundles/ + +To build a bundle from a `.rego` file, use the `opa build` command: + +```bash +opa build policy.rego -o bundle.tar.gz +``` + +See the [OPA bundle documentation][opa-bundles] for details on bundle +structure and manifest options. To use it in server mode, specify the `--allow-policy` and `--allow-query` flags. @@ -226,8 +238,8 @@ for more about the policy language. different process. * Older versions of Ghostunnel allowed specifying a Rego file rather than a bundle as an argument to the `--allow-policy` and `--verify-policy` flags. This - still works, but the policy will be treated as a V0 policy for compatibility - versions. It's recommended to specify a bundle so you can set the language + still works, but the policy will be treated as a V0 policy for backward + compatibility. It's recommended to specify a bundle so you can set the language version directly in the bundle manifest. * By standard OPA convention, we consider a policy to be "allowed" if the query is exactly one result with exactly one element that has the value `true`. diff --git a/docs/ACME.md b/docs/ACME.md index 940ad4d3bb..07bc1f9deb 100644 --- a/docs/ACME.md +++ b/docs/ACME.md @@ -5,11 +5,11 @@ weight: 30 --- In server mode, Ghostunnel can automatically obtain and renew a public TLS -certificate via the ACME protocol. This is powered by -[certmagic](https://github.com/caddyserver/certmagic), which handles -certificate storage, renewal, and OCSP stapling. +certificate via the [ACME][acme-rfc] protocol. This is powered by +[certmagic][certmagic], which handles certificate storage, renewal, and OCSP +stapling. -### Basic usage +## Basic usage To enable ACME, use the `--auto-acme-cert` flag with the FQDN to obtain a certificate for. You must also specify an email address with @@ -26,33 +26,58 @@ ghostunnel server \ --allow-cn client ``` -Ghostunnel defaults to using Let's Encrypt as the ACME CA. You can specify a -different ACME CA URL using `--auto-acme-ca`. To test against a non-production -CA (e.g. Let's Encrypt's staging environment), use `--auto-acme-testca` — when -set, the `--auto-acme-ca` flag is ignored. +Ghostunnel defaults to using [Let's Encrypt][letsencrypt] as the ACME CA. You +can specify a different ACME CA URL using `--auto-acme-ca`. To test against a +non-production CA (e.g. Let's Encrypt's staging environment), use +`--auto-acme-testca`. When set, the `--auto-acme-ca` flag is ignored. -### Requirements +## Requirements ACME is only supported in server mode. Ghostunnel must either be listening on a public interface on tcp/443, or have tcp/443 forwarded to it (e.g. via a systemd socket or iptables). Public DNS records must exist for the FQDN that resolve to the public listening interface IP. -Ghostunnel uses the TLS-ALPN-01 challenge type (HTTP-01 is disabled), so port -443 must be reachable. +Ghostunnel uses the [TLS-ALPN-01][tls-alpn-01] challenge type (HTTP-01 is +disabled), so port 443 must be reachable. -### Certificate storage and renewal +## Certificate storage and renewal -Certificates are stored locally by certmagic in its default storage directory -(typically `~/.local/share/certmagic` or the equivalent on your OS). Certmagic -automatically renews certificates before they expire — no manual intervention -or `--timed-reload` is needed for ACME certificates. +Certmagic stores certificates and account keys on disk. The default location +depends on your OS: + +| OS | Default path | +|----|-------------| +| Linux / macOS | `~/.local/share/certmagic` (or `$XDG_DATA_HOME/certmagic`) | +| Windows | `%USERPROFILE%\.local\share\certmagic` | + +Certmagic automatically renews certificates before they expire, so no manual +intervention or `--timed-reload` is needed for ACME certificates. If a valid certificate already exists locally, Ghostunnel loads it from cache on startup without contacting the CA. -### Startup retry behavior +## Revoking or force-renewing + +Certmagic handles renewal automatically, but if you need to force a renewal +(e.g. after a key compromise), delete the certificate and key files from the +certmagic storage directory and restart Ghostunnel. It will obtain a fresh +certificate on startup. + +To revoke a certificate with Let's Encrypt directly, use the +[certbot revoke][certbot-revoke] command or the ACME revocation endpoint +described in [RFC 8555 Section 7.6][acme-revoke]. + +[certbot-revoke]: https://eff-certbot.readthedocs.io/en/latest/using.html#revoking-certificates +[acme-revoke]: https://datatracker.ietf.org/doc/html/rfc8555#section-7.6 + +## Startup retry behavior On startup, Ghostunnel attempts to obtain the initial certificate up to 5 times with exponential backoff (starting at 5 seconds, capped at 2 minutes). If all attempts fail, Ghostunnel exits with an error. + +[acme-rfc]: https://datatracker.ietf.org/doc/html/rfc8555 +[letsencrypt]: https://letsencrypt.org/ +[tls-alpn-01]: https://datatracker.ietf.org/doc/html/rfc8737 +[certmagic]: https://pkg.go.dev/github.com/caddyserver/certmagic diff --git a/docs/CERTIFICATES.md b/docs/CERTIFICATES.md new file mode 100644 index 0000000000..d025acc67a --- /dev/null +++ b/docs/CERTIFICATES.md @@ -0,0 +1,175 @@ +--- +title: Certificate Formats +description: Supported certificate and key formats, how to prepare them, and how Ghostunnel selects the right loader. +weight: 12 +--- + +Ghostunnel supports several certificate and private key formats. The format +is auto-detected from the file extension or by inspecting the first few +bytes, so you don't need to specify it explicitly. + +## Formats at a glance + +| Format | Extensions | Flag | Notes | +|--------|-----------|------|-------| +| PEM (separate files) | `.pem`, `.crt` + `.pem` | `--cert` + `--key` | Most common; leaf cert must be first in chain | +| PEM (combined) | `.pem` | `--keystore` | Single file with cert chain and private key | +| PKCS#12 | `.p12`, `.pfx` | `--keystore` | Binary bundle; optional `--storepass` for password | +| JCEKS | `.jceks`, `.jks` | `--keystore` | Java keystore; requires `--storepass` | +| DER | `.der` | `--keystore` | Raw X.509 or PKCS#7; less common | + +These options are mutually exclusive with each other and with `--use-workload-api`, +`--keychain-identity`, and PKCS#11 flags. + +## PEM files (separate cert and key) + +Pass the certificate chain and private key as two separate PEM files: + +```bash +ghostunnel server \ + --cert server-chain.pem \ + --key server-key.pem \ + --listen localhost:8443 \ + --target localhost:8080 \ + --cacert cacert.pem \ + --allow-cn client +``` + +The certificate file must contain the **leaf certificate first**, followed by +any intermediate CA certificates: + +``` +-----BEGIN CERTIFICATE----- +(leaf / end-entity certificate) +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +(intermediate CA certificate) +-----END CERTIFICATE----- +``` + +The key file must contain a single PEM-encoded private key (RSA, ECDSA, +or Ed25519). + +## PEM keystore (combined file) + +A single PEM file containing both the certificate chain and private key can +be passed with `--keystore`. The private key can appear anywhere in the file, +but the leaf certificate must still come before any intermediates: + +```bash +ghostunnel server \ + --keystore server-combined.pem \ + --listen localhost:8443 \ + --target localhost:8080 \ + --cacert cacert.pem \ + --allow-cn client +``` + +To create a combined PEM file: + +```bash +cat server-cert.pem intermediate.pem server-key.pem > server-combined.pem +``` + +## PKCS#12 + +PKCS#12 (`.p12` / `.pfx`) bundles the certificate chain and private key into a +single password-protected binary file. This is also the format used when +importing into the macOS Keychain or Windows Certificate Store (see +[Keychain Support]({{< ref "KEYCHAIN.md" >}})). + +```bash +ghostunnel server \ + --keystore server.p12 \ + --storepass \ + --listen localhost:8443 \ + --target localhost:8080 \ + --cacert cacert.pem \ + --allow-cn client +``` + +To create a PKCS#12 file from PEM files: + +```bash +openssl pkcs12 -export \ + -in server-cert.pem \ + -inkey server-key.pem \ + -certfile intermediate.pem \ + -out server.p12 \ + -passout pass: +``` + +See the [openssl-pkcs12][openssl-pkcs12] man page for all options. + +[openssl-pkcs12]: https://docs.openssl.org/master/man1/openssl-pkcs12/ + +## JCEKS + +Ghostunnel can read Java keystores in JCEKS or JKS format. This is mainly +useful when migrating from a Java-based TLS terminator: + +```bash +ghostunnel server \ + --keystore server.jceks \ + --storepass \ + --listen localhost:8443 \ + --target localhost:8080 \ + --cacert cacert.pem \ + --allow-cn client +``` + +## CA bundle + +The `--cacert` flag accepts a PEM file containing one or more trusted CA +certificates. If omitted, Ghostunnel uses the system trust store. + +To build a CA bundle from individual certificates: + +```bash +cat root-ca.pem intermediate-ca.pem > cacert.pem +``` + +## Format auto-detection + +Ghostunnel detects the format in this order: + +1. **File extension**: `.pem`/`.crt` → PEM, `.p12`/`.pfx` → PKCS#12, + `.jceks`/`.jks` → JCEKS, `.der` → DER. +2. **Magic bytes**: if the extension is ambiguous, the first bytes of the file + are inspected (e.g. `-----BEGIN` → PEM, ASN.1 sequence → PKCS#12 or DER). + +In practice, just use the right file extension and Ghostunnel will do the +right thing. + +## Common operations + +### Inspect a PEM certificate + +```bash +openssl x509 -in server-cert.pem -noout -text +``` + +### Inspect a PKCS#12 file + +```bash +openssl pkcs12 -in server.p12 -info -nokeys +``` + +### Convert PKCS#12 to PEM + +```bash +# Extract the leaf certificate +openssl pkcs12 -in server.p12 -clcerts -nokeys -out server-cert.pem + +# Extract CA/intermediate certificates +openssl pkcs12 -in server.p12 -cacerts -nokeys -out ca-chain.pem + +# Extract private key +openssl pkcs12 -in server.p12 -nocerts -nodes -out server-key.pem +``` + +### Verify a certificate chain + +```bash +openssl verify -CAfile cacert.pem server-cert.pem +``` diff --git a/docs/DOCKER.md b/docs/DOCKER.md new file mode 100644 index 0000000000..e1d37f6f56 --- /dev/null +++ b/docs/DOCKER.md @@ -0,0 +1,60 @@ +--- +title: Docker Images +description: Available Docker image variants and tags for running Ghostunnel in containers. +weight: 85 +--- + +Docker images are published to [Docker Hub][hub] on each release. Three +variants are available: + +| Variant | Tag | Base | +|---------|-----|------| +| Alpine | `ghostunnel/ghostunnel:latest`, `ghostunnel/ghostunnel:v1.x.x` | Alpine Linux | +| Debian | `ghostunnel/ghostunnel:latest-debian`, `ghostunnel/ghostunnel:v1.x.x-debian` | Debian slim | +| Distroless | `ghostunnel/ghostunnel:latest-distroless`, `ghostunnel/ghostunnel:v1.x.x-distroless` | Google Distroless | + +The `latest` tags always point to the most recent release. + +## Pulling an image + +```bash +# Distroless (smallest, no shell) +docker pull ghostunnel/ghostunnel:latest-distroless + +# Alpine (includes shell, good for debugging) +docker pull ghostunnel/ghostunnel:latest + +# Debian (includes shell and package manager) +docker pull ghostunnel/ghostunnel:latest-debian +``` + +## Running in Docker + +Mount your certificate files into the container and pass flags as normal: + +```bash +docker run --rm \ + -v /path/to/certs:/certs:ro \ + -p 8443:8443 \ + ghostunnel/ghostunnel:latest-distroless \ + server \ + --listen 0.0.0.0:8443 \ + --target host.docker.internal:8080 \ + --cert /certs/server-cert.pem \ + --key /certs/server-key.pem \ + --cacert /certs/cacert.pem \ + --allow-cn client +``` + +Note the use of `0.0.0.0` for `--listen` (to bind all interfaces inside the +container) and `host.docker.internal` for `--target` (to reach services on +the Docker host). You may need `--unsafe-target` since `host.docker.internal` +is not localhost. + +## Building images from source + +```bash +go tool mage docker:build +``` + +[hub]: https://hub.docker.com/r/ghostunnel/ghostunnel diff --git a/docs/FLAGS.md b/docs/FLAGS.md index 217e842dbb..00eeb1e00e 100644 --- a/docs/FLAGS.md +++ b/docs/FLAGS.md @@ -13,6 +13,9 @@ These flags are available in both `server` and `client` modes. ### Certificate / Key +See [Certificate Formats]({{< ref "CERTIFICATES.md" >}}) for details on +supported file formats and chain ordering. + | Flag | Description | |------|-------------| | `--keystore PATH` | Path to keystore (combined PEM with cert/key, or PKCS12 keystore). | @@ -69,6 +72,9 @@ See [Metrics]({{< ref "METRICS.md" >}}). ### Status / Logging +See [Metrics & Profiling]({{< ref "METRICS.md" >}}) for details on the status port, +metrics endpoints, and profiling. + | Flag | Description | Availability | |------|-------------|--------------| | `--status ADDR` | Enable `/_status` and `/_metrics` on given HOST:PORT (or `unix:SOCKET`). | All platforms | @@ -80,6 +86,9 @@ See [Metrics]({{< ref "METRICS.md" >}}). ### Landlock +See [Security & TLS Configuration]({{< ref "SECURITY.md" >}}) for details on +Landlock sandboxing. + | Flag | Description | Availability | |------|-------------|--------------| | `--disable-landlock` | Disable the best-effort Landlock sandboxing. Landlock is automatically disabled when PKCS#11 is used. | Linux only | @@ -90,6 +99,9 @@ Flags specific to `ghostunnel server`. ### Required +See [Socket Activation]({{< ref "SOCKET-ACTIVATION.md" >}}) for `systemd:NAME` and +`launchd:NAME` addresses. + | Flag | Description | |------|-------------| | `--listen ADDR` | Address and port to listen on (`HOST:PORT`, `unix:PATH`, `systemd:NAME`, or `launchd:NAME`). | @@ -101,7 +113,7 @@ Flags specific to `ghostunnel server`. |------|-------------| | `--target-status URL` | Address to target for status checking downstream healthchecks. Defaults to TCP healthcheck if not passed. | | `--proxy-protocol` | Enable PROXY protocol v2 to signal connection info to backend. | -| `--unsafe-target` | Do not limit target to localhost, `127.0.0.1`, `[::1]`, or UNIX sockets. | +| `--unsafe-target` | Do not limit target to localhost, `127.0.0.1`, `[::1]`, or UNIX sockets. See [Security]({{< ref "SECURITY.md" >}}). | ### Access Control @@ -130,6 +142,8 @@ See [ACME Support]({{< ref "ACME.md" >}}). ### OPA Policy (Server) +See [Access Control Flags]({{< ref "ACCESS-FLAGS.md" >}}) for OPA/Rego policy details. + | Flag | Description | |------|-------------| | `--allow-policy BUNDLE` | Location of an OPA policy bundle. | @@ -141,6 +155,9 @@ Flags specific to `ghostunnel client`. ### Required +See [Socket Activation]({{< ref "SOCKET-ACTIVATION.md" >}}) for `systemd:NAME` and +`launchd:NAME` addresses. + | Flag | Description | |------|-------------| | `--listen ADDR` | Address and port to listen on (`HOST:PORT`, `unix:PATH`, `systemd:NAME`, or `launchd:NAME`). | @@ -150,7 +167,7 @@ Flags specific to `ghostunnel client`. | Flag | Description | |------|-------------| -| `--unsafe-listen` | Do not limit listen to localhost, `127.0.0.1`, `[::1]`, or UNIX sockets. | +| `--unsafe-listen` | Do not limit listen to localhost, `127.0.0.1`, `[::1]`, or UNIX sockets. See [Security]({{< ref "SECURITY.md" >}}). | | `--override-server-name NAME` | Override the server name used for hostname verification. | | `--proxy URL` | Connect to target over given proxy (HTTP CONNECT or SOCKS5). Must be a proxy URL. | | `--disable-authentication` | Disable client authentication, no certificate will be provided to the server. | @@ -168,6 +185,8 @@ See [Access Control Flags]({{< ref "ACCESS-FLAGS.md" >}}). ### OPA Policy (Client) +See [Access Control Flags]({{< ref "ACCESS-FLAGS.md" >}}) for OPA/Rego policy details. + | Flag | Description | |------|-------------| | `--verify-policy BUNDLE` | Location of an OPA policy bundle. | diff --git a/docs/GRACEFUL-SHUTDOWN.md b/docs/GRACEFUL-SHUTDOWN.md new file mode 100644 index 0000000000..70a6f23612 --- /dev/null +++ b/docs/GRACEFUL-SHUTDOWN.md @@ -0,0 +1,99 @@ +--- +title: Graceful Shutdown +description: How Ghostunnel handles shutdown signals, drains in-flight connections, and force-exits after a timeout. +weight: 87 +--- + +Ghostunnel supports graceful shutdown: when a shutdown is triggered, it stops +accepting new connections and waits for existing connections to drain before +exiting. If connections do not drain within the configured timeout, the process +force-exits. + +## Shutdown triggers + +There are three ways to initiate a graceful shutdown: + +### Signals (Unix) + +On Unix (Linux, macOS), sending `SIGTERM` or `SIGINT` to the Ghostunnel +process triggers a graceful shutdown: + +```bash +# Graceful shutdown via signal +kill -TERM +kill -INT # also sent by Ctrl+C +``` + +> **Note:** `SIGHUP` and `SIGUSR1` do *not* shut down the process. They +> trigger a reload of certificates and OPA policies instead. + +### Signals (Windows) + +On Windows, only the `Interrupt` signal (Ctrl+C) triggers shutdown. There are +no reload signals on Windows, but `--timed-reload` can be used to periodically +reload certificates and OPA policies on a fixed interval. + +### HTTP endpoint (`/_shutdown`) + +If `--enable-shutdown` is set (requires `--status`), you can trigger a +shutdown via HTTP POST: + +```bash +curl -X POST --cacert test-keys/cacert.pem https://localhost:6060/_shutdown +``` + +Any HTTP method other than POST returns 405 Method Not Allowed. + +## Shutdown sequence + +When a shutdown is triggered, the following happens in order: + +1. **Status transitions to "stopping"**: the `/_status` endpoint reflects + that the process is shutting down. +2. **Status HTTP server begins shutting down**: best-effort graceful shutdown + of the internal status listener. +3. **Listener closes**: Ghostunnel stops accepting new connections. +4. **In-flight connections continue**: existing connections are not + interrupted. Data continues to flow until both sides close normally. +5. **Force-exit timer starts**: a timer begins counting down from the + `--shutdown-timeout` value (default: 5 minutes). +6. **Process exits** when either: + - All in-flight connections have drained (exit code 0), or + - The shutdown timeout fires (exit code 1). + +## Flags + +| Flag | Default | Description | +|------|---------|-------------| +| `--shutdown-timeout` | `5m` | Maximum time to wait for in-flight connections to drain. If connections are still open after this duration, the process force-exits with code 1. | +| `--enable-shutdown` | `false` | Enable the `/_shutdown` HTTP endpoint on the status port. Requires `--status`. | +| `--status` | *(none)* | HOST:PORT (or `unix:SOCKET`) for the status listener. Required for `/_shutdown`. | + +See [Command-Line Flags]({{< ref "FLAGS.md" >}}) for the full flag reference. + +## Choosing a shutdown timeout + +The default timeout of 5 minutes is deliberately generous. Consider your +workload when tuning this value: + +- **Short-lived requests** (e.g. REST APIs): a lower timeout like `30s` or + `1m` is usually sufficient. +- **Long-lived connections** (e.g. streaming, WebSocket-like traffic): you may + need to increase the timeout or accept that some connections will be + force-closed. +- **Zero-downtime deployments**: coordinate the shutdown timeout with your + orchestrator's termination grace period (e.g. Kubernetes + `terminationGracePeriodSeconds`) to avoid the orchestrator killing the + process before Ghostunnel's own timeout fires. + +Other flags like `--connect-timeout` and `--conn-max-lifetime` also influence +connection behavior and may be relevant when tuning shutdown. See +[Command-Line Flags]({{< ref "FLAGS.md" >}}) for the full list. + +## Integration with systemd + +When running as a systemd service with `Type=notify-reload`, Ghostunnel +notifies systemd of its state transitions (ready, reloading, stopping). The +graceful shutdown sequence integrates naturally with systemd's service +lifecycle. See [Systemd Watchdog]({{< ref "WATCHDOG.md" >}}) for unit file +examples and configuration details. diff --git a/docs/HSM-PKCS11.md b/docs/HSM-PKCS11.md index e98e23aa26..a5711dff50 100644 --- a/docs/HSM-PKCS11.md +++ b/docs/HSM-PKCS11.md @@ -4,8 +4,9 @@ description: Load private keys from hardware security modules via the PKCS#11 in weight: 40 --- -Ghostunnel has support for loading private keys from PKCS#11 modules, which -should work with any hardware security module that exposes a PKCS#11 interface. +Ghostunnel has support for loading private keys from [PKCS#11][pkcs11-spec] +modules, which should work with any hardware security module that exposes a +PKCS#11 interface. An easy way to test the PKCS#11 interface for development purposes is with [SoftHSM][softhsm]. Note that CGO is required in order for PKCS#11 support to work. @@ -29,7 +30,7 @@ softhsm2-util --id 01 \ --pin 1234 ``` -To launch Ghostunnel with the SoftHSM-backed PKCS11 key: +To launch Ghostunnel with the SoftHSM-backed PKCS#11 key: ```bash ghostunnel server \ @@ -44,30 +45,152 @@ ghostunnel server \ ``` The `--pkcs11-module`, `--pkcs11-token-label` and `--pkcs11-pin` flags can be -used to select the private key to be used from the PKCS11 module. It's also possible -to use environment variables to set PKCS11 options instead of flags (via +used to select the private key to be used from the PKCS#11 module. It's also possible +to use environment variables to set PKCS#11 options instead of flags (via `PKCS11_MODULE`, `PKCS11_TOKEN_LABEL` and `PKCS11_PIN`), useful if you don't want to show the PIN on the command line. Note that `--cert` needs to point to the certificate chain that corresponds to the private key in the PKCS#11 module, with the leaf certificate being the -first certificate in the chain. Ghostunnel currently cannot read the -certificate chain directly from the module. +first certificate in the chain (see +[Certificate Formats]({{< ref "CERTIFICATES.md" >}})). Ghostunnel currently +cannot read the certificate chain directly from the module. -### Certificate hotswapping +## Using a YubiKey + +[YubiKey][yubikey] 4 and 5 series support the [PIV (FIPS 201)][piv] standard, +which exposes a PKCS#11 interface via the [YKCS11][ykcs11] module, so you +can use a YubiKey to hold Ghostunnel's private key in hardware. + +[yubikey]: https://www.yubico.com +[piv]: https://developers.yubico.com/PIV/ +[ykcs11]: https://developers.yubico.com/yubico-piv-tool/YKCS11/ + +### Prerequisites + +You'll need `yubico-piv-tool`, which ships the CLI and the `libykcs11` +PKCS#11 module: + +```bash +# macOS +brew install yubico-piv-tool + +# Debian/Ubuntu +apt install yubico-piv-tool ykcs11 +``` + +The module lives in different places depending on your platform: + +| Platform | Typical path | +|-----------------------|-------------------------------------------------| +| macOS (Apple Silicon) | `/opt/homebrew/lib/libykcs11.dylib` | +| macOS (Intel) | `/usr/local/lib/libykcs11.dylib` | +| Linux (x86_64) | `/usr/lib/x86_64-linux-gnu/libykcs11.so` or `/usr/local/lib/libykcs11.so` | + +### PIV slots + +YubiKey PIV has several key slots. For TLS with Ghostunnel, you'll +usually want slot **9a** (Authentication): + +| Slot | Purpose | Typical use | +|------|----------------------|--------------------------| +| 9a | Authentication | TLS client/server certs | +| 9c | Digital Signature | Code/document signing | +| 9d | Key Management | Encryption | +| 9e | Card Authentication | Physical access | + +### Generating a key and certificate + +Generate a key pair on the YubiKey itself (the private key never leaves +the device): + +```bash +# Generate an RSA 2048 key in slot 9a +yubico-piv-tool -s 9a -a generate -A RSA2048 -o public-key.pem + +# Create a certificate signing request (CSR) +yubico-piv-tool -s 9a -a verify-pin -a request-certificate \ + -S '/CN=my-server/' -i public-key.pem -o csr.pem +``` + +Sign the CSR with your CA, then import the signed certificate back: + +```bash +yubico-piv-tool -s 9a -a import-certificate -i server-cert.pem +``` + +### Exporting the certificate for Ghostunnel + +Ghostunnel reads the certificate chain from disk, not from the PKCS#11 +module, so you'll need to export it: + +```bash +yubico-piv-tool -s 9a -a read-certificate -o server-cert.pem +``` + +If your CA has an intermediate, concatenate them into a chain (leaf first): + +```bash +cat server-cert.pem intermediate.pem > chain.pem +``` + +### Launching Ghostunnel with a YubiKey + +```bash +ghostunnel server \ + --cert chain.pem \ + --pkcs11-module /opt/homebrew/lib/libykcs11.dylib \ + --pkcs11-token-label "YubiKey PIV #12345678" \ + --pkcs11-pin 123456 \ + --listen localhost:8443 \ + --target localhost:8080 \ + --cacert ca-cert.pem \ + --allow-cn client +``` + +The default PIV PIN is `123456`. Change it before doing anything real. To +keep the PIN off the command line, use the `PKCS11_PIN` environment variable +instead of `--pkcs11-pin`. + +To find the correct token label for your YubiKey: + +```bash +pkcs11-tool --module /opt/homebrew/lib/libykcs11.dylib -L +``` + +### Debugging + +If things aren't working, set `YKCS11_DBG` (values 1–9) for verbose output +from the YKCS11 module: + +```bash +YKCS11_DBG=1 ghostunnel server ... +``` + +`pkcs11-tool` is also handy for poking around on the YubiKey: + +```bash +# List available slots/tokens +pkcs11-tool --module /path/to/libykcs11.dylib -L + +# List objects (keys, certificates) on the token +pkcs11-tool --module /path/to/libykcs11.dylib -O +``` + +## Certificate hotswapping When using PKCS#11, certificate hotswapping (via `SIGHUP`/`SIGUSR1` or -`--timed-reload`) reloads only the certificate from disk. The private key in -the HSM is assumed to remain the same. This means the updated or reissued -certificate must still match the private key that was loaded from the HSM. +`--timed-reload`) reloads only the certificate from disk. The private key +in the HSM stays put, so the new certificate still needs to match the key +that was loaded from the HSM. Note that Landlock sandboxing is automatically disabled when PKCS#11 is used, as PKCS#11 modules are opaque shared libraries that may need access to arbitrary files and sockets. -### Inspecting PKCS#11 state +## Inspecting PKCS#11 state -If you need to inspect the state of a PKCS11 module/token, we recommend the +If you need to inspect the state of a PKCS#11 module/token, we recommend the [`pkcs11-tool`][pkcs11-tool] utility from OpenSC. For example, it can be used to list slots or read certificate(s) from a module: @@ -82,4 +205,5 @@ pkcs11-tool --module $MODULE -O -y cert pkcs11-tool --module $MODULE --label $LABEL --read-object -y cert ``` +[pkcs11-spec]: https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.1/pkcs11-spec-v3.1.html [pkcs11-tool]: https://github.com/OpenSC/OpenSC/wiki/SmartCardHSM#using-pkcs11-tool diff --git a/docs/KEYCHAIN.md b/docs/KEYCHAIN.md index 5d5bd2cd16..fddb775bea 100644 --- a/docs/KEYCHAIN.md +++ b/docs/KEYCHAIN.md @@ -4,29 +4,152 @@ description: Load certificates and private keys from the macOS Keychain or Windo weight: 50 --- -If you have identities stored in the macOS Keychain or Windows Certificate -Store, Ghostunnel can load certificates directly from them. This is useful -when you want private keys backed by the Secure Enclave on Touch ID MacBooks, -or when managing certificates through the OS is preferable to managing files -on disk. +Ghostunnel can load certificates and private keys directly from the macOS +Keychain or Windows Certificate Store. This lets you use Secure Enclave-backed +keys on Touch ID MacBooks, hardware-backed keys via CNG on Windows, or simply +manage certificates through the OS instead of as files on disk. -### Selecting a certificate +## Prerequisites: creating a PKCS#12 bundle + +Both macOS and Windows import certificates from [PKCS#12][openssl-pkcs12] +(`.p12` / `.pfx`) files. If you have a PEM certificate and key, bundle them +first: + +```bash +openssl pkcs12 -export \ + -in server-cert.pem \ + -inkey server-key.pem \ + -out server.p12 \ + -passout pass: +``` + +If you also need to include intermediate CA certificates in the bundle, add +`-certfile intermediate-ca.pem`. + +[openssl-pkcs12]: https://docs.openssl.org/master/man1/openssl-pkcs12/ + +## macOS + +### Importing into the Keychain + +**Using the CLI** (recommended for automation): + +```bash +security import server.p12 \ + -k ~/Library/Keychains/login.keychain-db \ + -f pkcs12 \ + -P \ + -A +``` + +The `-A` flag allows all applications to access the imported key without a +confirmation prompt. Omit it if you prefer per-application access control. + +You can also **double-click the `.p12` file** in Finder or use the +[Keychain Access][apple-keychain-access] app to import through the GUI. + +**Verify** the import succeeded: + +```bash +security find-identity -v +``` + +This lists all identities (certificate + private key pairs) in your keychain +search list. Look for your certificate's Common Name in the output. + +See also Apple's [Keychain Services documentation][apple-keychain-services] +and [TN3137: On Mac keychain APIs and implementations][apple-tn3137]. + +[apple-keychain-access]: https://support.apple.com/guide/keychain-access/add-certificates-to-a-keychain-kyca2431/mac +[apple-keychain-services]: https://developer.apple.com/documentation/security/keychain-services +[apple-tn3137]: https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains + +### Secure Enclave and hardware tokens + +On Touch ID MacBooks, private keys can live in the Secure Enclave. Pass +`--keychain-require-token` so Ghostunnel only loads keys backed by a hardware +token (e.g. the Secure Enclave or a smart card): + +```bash +ghostunnel server \ + --keychain-identity \ + --keychain-require-token \ + --listen localhost:8443 \ + --target localhost:8080 \ + --cacert cacert.pem \ + --allow-ou=client +``` + +This flag is only available on macOS and has no effect on Windows. + +See Apple's [Protecting keys with the Secure Enclave][apple-secure-enclave] +and the [Secure Enclave security overview][apple-se-overview] for more on +hardware-backed keys. + +[apple-secure-enclave]: https://developer.apple.com/documentation/security/protecting-keys-with-the-secure-enclave +[apple-se-overview]: https://support.apple.com/guide/security/sec59b0b31ff/web + +## Windows + +### Importing into the Certificate Store + +**Using certutil** (recommended for automation): + +```bash +certutil -f -p -user -importpfx MY server.p12 +``` + +This imports the certificate and private key into the current user's "MY" +(Personal) store. The `-user` flag targets the current user context. To import +into the Local Machine store instead, omit `-user` (and run as administrator). + +**Using PowerShell**: + +```powershell +Import-PfxCertificate -FilePath server.p12 ` + -CertStoreLocation Cert:\CurrentUser\My ` + -Password (ConvertTo-SecureString -String "" -AsPlainText -Force) +``` + +**Verify** the import: + +```powershell +Get-ChildItem Cert:\CurrentUser\My | Format-Table Subject, Thumbprint, NotAfter +``` + +**Which stores does Ghostunnel search?** When `--keychain-identity` is used +on Windows, Ghostunnel searches three stores in this order: + +1. **MY** (Current User), the personal certificate store +2. **CURRENT_SERVICE**, the current service account's certificates (if accessible) +3. **LOCAL_MACHINE**, machine-wide certificates (if accessible; may require elevation) + +Stores that fail to open are skipped rather than causing an error. + +See Microsoft's [certutil reference][ms-certutil], +[System Store Locations][ms-store-locations], and the +[Import-PfxCertificate][ms-import-pfx] cmdlet docs for more. + +[ms-certutil]: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/certutil +[ms-store-locations]: https://learn.microsoft.com/en-us/windows/win32/seccrypto/system-store-locations +[ms-import-pfx]: https://learn.microsoft.com/en-us/powershell/module/pki/import-pfxcertificate + +## Selecting a certificate Certificates from the keychain can be selected using one or both of the following flags: -* `--keychain-identity` — match by the certificate's Common Name (CN) or - serial number. Ghostunnel checks both fields and uses the first match. -* `--keychain-issuer` — match by the issuer's Common Name (CN). +* `--keychain-identity`: match by the certificate's Common Name (CN) or + serial number. Ghostunnel collects all certificates where either field matches. +* `--keychain-issuer`: match by the issuer's Common Name (CN). When both flags are specified, Ghostunnel selects certificates where both -attributes match (logical AND). +attributes match (logical AND). If multiple certificates match, the one with +the latest expiration date (NotAfter) is used. -On macOS, `--keychain-require-token` additionally requires the loaded -certificate to come from a physical hardware token (e.g. the Secure Enclave). -This flag is not available on Windows. +## Usage examples -### macOS example +### macOS Load an identity from the login keychain by subject name: @@ -48,7 +171,7 @@ ghostunnel client \ --cacert cacert.pem ``` -### Windows example +### Windows On Windows, `--keychain-identity` and `--keychain-issuer` work the same way but search the Windows Certificate Store (the "MY" store for the current user): @@ -61,10 +184,51 @@ ghostunnel client \ --cacert cacert.pem ``` -### Certificate reloading +## Certificate reloading Keychain certificates support reloading via `SIGHUP`/`SIGUSR1` or `--timed-reload`. On reload, Ghostunnel re-queries the keychain for a certificate matching the same identity/issuer criteria. If the certificate has been updated in the keychain (e.g. renewed), the new certificate will be used for subsequent connections. + +## Removing certificates + +**macOS**: remove an identity (certificate + private key) by Common Name: + +```bash +security delete-identity -c +``` + +Or use the Keychain Access app to find and delete the certificate in the GUI. + +**Windows**: remove via PowerShell: + +```powershell +Get-ChildItem Cert:\CurrentUser\My | + Where-Object { $_.Subject -match "CN=" } | + Remove-Item -DeleteKey +``` + +The `-DeleteKey` flag also removes the private key. You can alternatively +use `certutil -delstore MY `. + +## Troubleshooting + +**macOS: certificate not found** +- Check the keychain search list: `security list-keychains` +- Unlock the keychain if locked: `security unlock-keychain` +- List available identities: `security find-identity -v` +- Make sure the CN or serial matches what you passed to `--keychain-identity` + +**Windows: certificate not found** +- List certs in the store: `Get-ChildItem Cert:\CurrentUser\My` +- If using the Local Machine store, make sure Ghostunnel runs with sufficient permissions +- Make sure the CN or serial matches what you passed to `--keychain-identity` + +**Access denied / permission errors** +- **macOS**: the keychain may prompt for access. Use `-A` during import to allow all + apps, or grant access to Ghostunnel specifically in Keychain Access. +- **Windows**: the account running Ghostunnel needs read access to the private key. + You can manage private key permissions through the Certificates MMC snap-in + (right-click a certificate, then "All Tasks > Manage Private Keys"). diff --git a/docs/METRICS.md b/docs/METRICS.md index 1753b3f70f..2df67c97cf 100644 --- a/docs/METRICS.md +++ b/docs/METRICS.md @@ -63,14 +63,17 @@ information on profiling via pprof, see the [`runtime/pprof`][pprof] and [http-pprof]: https://pkg.go.dev/net/http/pprof [pprof-bug]: https://github.com/golang/go/issues/20939 -### Shutdown endpoint +## Shutdown endpoint If `--enable-shutdown` is set, a `/_shutdown` endpoint is available on the status port. Sending an HTTP POST request to this endpoint will trigger a graceful shutdown of the Ghostunnel process. Any other HTTP method returns 405 -Method Not Allowed. +Method Not Allowed. For details on what happens after shutdown is triggered, +including signal handling, connection draining, and the `--shutdown-timeout` +flag, see +[Graceful Shutdown]({{< ref "GRACEFUL-SHUTDOWN.md" >}}). -### Backend healthchecks +## Backend healthchecks The `/_status` endpoint includes a backend healthcheck. By default, Ghostunnel performs a TCP connection check against the `--target` address. You can override @@ -79,13 +82,13 @@ instead. Ghostunnel expects an HTTP 200 response. The `/_status` JSON response includes: -* `backend_ok` — boolean indicating if the backend check passed -* `backend_status` — string of `ok` or `critical` -* `backend_error` — string of error message if the check failed +* `backend_ok`: boolean indicating if the backend check passed +* `backend_status`: string of `ok` or `critical` +* `backend_error`: string of error message if the check failed If the backend check fails, the `/_status` endpoint returns HTTP 503. -### Metric names +## Metric names Ghostunnel exports the following base metrics: @@ -104,7 +107,7 @@ The `--metrics-prefix` flag (default: `ghostunnel`) is prepended to all metric names. How the prefix and metric names are formatted depends on the output format (see below). -### JSON format (`/_metrics/json`) +## JSON format (`/_metrics/json`) JSON output uses dot-separated names. Counters and gauges are emitted as a single value. Timers are expanded into count, min/max/mean, and percentile @@ -125,7 +128,7 @@ sub-metrics: Each metric is returned as a JSON object with `timestamp`, `metric`, `value`, and `hostname` fields. -### Prometheus format (`/_metrics/prometheus`) +## Prometheus format (`/_metrics/prometheus`) Prometheus output replaces dots, dashes, and other special characters with underscores to comply with Prometheus naming conventions. All metrics are @@ -149,17 +152,38 @@ statistical gauges, and a summary histogram: | `ghostunnel_conn_handshake_timer_bucket{le="..."}` | Histogram buckets (0.50, 0.95, 0.99, 0.999) | | `ghostunnel_conn_handshake_timer_count` | Histogram observation count | -### Metrics export +### Prometheus scrape config + +To scrape Ghostunnel metrics with Prometheus, add a job to your +`prometheus.yml`: + +```yaml +scrape_configs: + - job_name: ghostunnel + scheme: https + tls_config: + ca_file: /path/to/cacert.pem + cert_file: /path/to/client-cert.pem + key_file: /path/to/client-key.pem + metrics_path: /_metrics/prometheus + static_configs: + - targets: ['localhost:6060'] +``` + +If the status port uses HTTP (see below), set `scheme: http` and drop the +`tls_config` block. + +## Metrics export Metrics are always available via the status port endpoints (`/_metrics/json`, `/_metrics/prometheus`). Additionally, metrics can be pushed to external systems: -* `--metrics-graphite=ADDR` — push to a Graphite instance via raw TCP +* `--metrics-graphite=ADDR`: push to a Graphite instance via raw TCP (dot-separated names, same as JSON format) -* `--metrics-url=URL` — push via HTTP POST (JSON format) at the interval set by +* `--metrics-url=URL`: push via HTTP POST (JSON format) at the interval set by `--metrics-interval` (default: 30s) -### Exposing status port with HTTP instead of HTTPS +## Exposing status port with HTTP instead of HTTPS By default, Ghostunnel uses HTTPS for the status port. You can force it to use HTTP by prefixing the status address with "http://". diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md new file mode 100644 index 0000000000..97dcc890e7 --- /dev/null +++ b/docs/QUICKSTART.md @@ -0,0 +1,137 @@ +--- +title: Quick Start +description: Get Ghostunnel running with mTLS in 5 minutes using a self-signed CA. +weight: 5 +--- + +This guide walks through setting up a Ghostunnel server and client with mutual +TLS, using a self-signed CA for testing. + +## Install Ghostunnel + +```bash +# Homebrew +brew install ghostunnel + +# Or pull a Docker image (see Docker docs for all variants) +docker pull ghostunnel/ghostunnel:latest-distroless +``` + +Pre-built binaries are also available on the +[GitHub releases](https://github.com/ghostunnel/ghostunnel/releases) page. +See [Docker Images]({{< ref "DOCKER.md" >}}) for all available image variants. + +To build from source (requires [Go](https://go.dev/doc/install)): + +```bash +go tool mage go:build +``` + +## Generate test certificates + +If you already maintain a PKI, you can skip this step and use your existing +certificates. The steps below are for generating test certificates for +testing and development purposes only. + +You need a CA, a server certificate, and a client certificate. The rest of +this guide uses the paths from `test-keys/`, so adjust if you use a different +method. + +**From the Ghostunnel repo** (requires Go): + +```bash +go tool mage test:keys +``` + +This creates a `test-keys/` directory with everything you need: CA cert, +server cert+key, client cert+key, and PKCS#12 keystores. + +**Using [mkcert](https://github.com/FiloSottile/mkcert)**: + +```bash +mkcert -install +mkcert -cert-file test-keys/server-cert.pem -key-file test-keys/server-key.pem localhost 127.0.0.1 +mkcert -client -cert-file test-keys/client-cert.pem -key-file test-keys/client-key.pem localhost +``` + +Note: mkcert sets SANs, not CNs, so use `--allow-dns localhost` instead of +`--allow-cn client` when authorizing clients. The CA cert is at +`$(mkcert -CAROOT)/rootCA.pem`, copy it to `test-keys/cacert.pem` to match +the paths below. + +**Using [cfssl](https://github.com/cloudflare/cfssl)**: cfssl is a full-featured +PKI toolkit that can generate CAs and sign certificates. See the +[cfssl documentation](https://github.com/cloudflare/cfssl#readme) for usage. + +**Using OpenSSL** manually: see the [openssl-req](https://docs.openssl.org/master/man1/openssl-req/) +and [openssl-x509](https://docs.openssl.org/master/man1/openssl-x509/) docs +for creating CAs and signing certificates. + +## Start a backend service + +Ghostunnel is protocol-agnostic and works with any TCP-based protocol, not +just HTTP. For this demo we'll use a simple HTTP server as the backend: + +```bash +python3 -m http.server 8080 & +``` + +## Run Ghostunnel server + +In a new terminal, start a server that listens for TLS on port 8443 and +forwards plaintext to the backend on port 8080. Only clients with CN=client +are allowed: + +```bash +ghostunnel server \ + --listen localhost:8443 \ + --target localhost:8080 \ + --cert test-keys/server-cert.pem \ + --key test-keys/server-key.pem \ + --cacert test-keys/cacert.pem \ + --allow-cn client +``` + +## Run Ghostunnel client + +In another terminal, start a client that listens for plaintext on port 8081 +and connects to the server over TLS: + +```bash +ghostunnel client \ + --listen localhost:8081 \ + --target localhost:8443 \ + --cert test-keys/client-cert.pem \ + --key test-keys/client-key.pem \ + --cacert test-keys/cacert.pem +``` + +## Test the tunnel + +In a third terminal, send a request through the tunnel: + +```bash +curl http://localhost:8081 +``` + +You should see the directory listing from the Python HTTP server. The +connection between client and server is encrypted with mTLS, even though +curl speaks plain HTTP. + +
+ +![Tunnel diagram](/tunnel-diagram.svg) + +The Ghostunnel client accepted a plaintext connection from curl, wrapped it +in TLS with the client certificate, and forwarded it to the Ghostunnel +server. The server verified the client cert (CN=client), unwrapped TLS, and +forwarded the plaintext request to the backend. + +## Next steps + +- [Command-Line Flags]({{< ref "FLAGS.md" >}}): full flag reference +- [Certificate Formats]({{< ref "CERTIFICATES.md" >}}): PEM, PKCS#12, JCEKS, and chain ordering +- [Access Control Flags]({{< ref "ACCESS-FLAGS.md" >}}): control who can connect (CN, OU, DNS/URI SAN, OPA) +- [ACME Support]({{< ref "ACME.md" >}}): automatic certificates from Let's Encrypt +- [Metrics & Profiling]({{< ref "METRICS.md" >}}): status port, Prometheus metrics, pprof +- [Socket Activation]({{< ref "SOCKET-ACTIVATION.md" >}}) and [Systemd Watchdog]({{< ref "WATCHDOG.md" >}}): run Ghostunnel as a service diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 99f4799487..da12ba9bbb 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -33,8 +33,17 @@ All suites use authenticated encryption (AEAD). CBC-mode ciphers are not enabled. ECDSA suites are listed before RSA to prefer ECDSA when both certificate types are available. -In TLS 1.3, cipher suite selection is handled by Go's `crypto/tls` and cannot -be configured by the application. The TLS 1.3 suites listed above are always +To check which cipher suite and protocol version were negotiated for a +connection: + +```bash +openssl s_client -connect localhost:8443 \ + -cert client-cert.pem -key client-key.pem -CAfile cacert.pem \ + /dev/null | grep -E 'Protocol|Cipher' +``` + +In TLS 1.3, cipher suite selection is handled by Go's [`crypto/tls`][crypto-tls] +and cannot be configured by the application. The TLS 1.3 suites listed above are always available when TLS 1.3 is negotiated. The configurable cipher suite list only affects TLS 1.2 connections. @@ -42,8 +51,8 @@ affects TLS 1.2 connections. In server mode, key exchange prefers the following elliptic curves: -1. **X25519** — fast, constant-time, widely supported -2. **P-256 (secp256r1)** — hardware-accelerated on most platforms +1. **X25519**: fast, constant-time, widely supported +2. **P-256 (secp256r1)**: hardware-accelerated on most platforms ### Client authentication @@ -89,9 +98,9 @@ localhost risks unauthorized access to the proxied service. ## Landlock sandboxing -On Linux, Ghostunnel uses [Landlock](https://landlock.io) to restrict its own -process privileges after startup. Landlock is a kernel-level access control -mechanism that limits which files and network ports a process can access. +On Linux, Ghostunnel uses [Landlock][landlock] to restrict its own process +privileges after startup. Landlock is a kernel-level access control mechanism +that limits which files and network ports a process can access. ### How it works @@ -117,3 +126,6 @@ Landlock can be disabled with `--disable-landlock` if it causes issues with your deployment. This is not recommended. Landlock is also automatically disabled when PKCS#11 is in use, since PKCS#11 modules are opaque shared libraries that may require access to arbitrary files and sockets. + +[crypto-tls]: https://pkg.go.dev/crypto/tls +[landlock]: https://docs.kernel.org/userspace-api/landlock.html diff --git a/docs/SOCKET-ACTIVATION.md b/docs/SOCKET-ACTIVATION.md index d26933fe2a..c64b57c698 100644 --- a/docs/SOCKET-ACTIVATION.md +++ b/docs/SOCKET-ACTIVATION.md @@ -12,7 +12,10 @@ your systemd/launchd configuration. Note that socket activation is not available on Windows. -### launchd +## launchd + +See Apple's [Creating Launch Daemons and Agents][launchd-guide] for background +on launchd plists. A launchd plist to launch Ghostunnel in server mode on :8081, listening for status connections on :8082, and forwarding connections to :8083 @@ -76,7 +79,26 @@ defined for each socket. If for example the family were to be left out, launchd would open two sockets (IPv4 and IPv6) for the given key (like `Listener`) and pass them to Ghostunnel which is not currently supported. -### systemd +To install and enable: + +```bash +# Copy the plist into place +sudo cp com.square.ghostunnel.plist /Library/LaunchDaemons/ + +# Load and start +sudo launchctl load /Library/LaunchDaemons/com.square.ghostunnel.plist + +# Stop and unload +sudo launchctl unload /Library/LaunchDaemons/com.square.ghostunnel.plist +``` + +Use `~/Library/LaunchAgents/` instead of `/Library/LaunchDaemons/` if running +as a user agent rather than a system daemon. + +## systemd + +See the [`systemd.socket`][systemd-socket] man page for the full socket unit +reference. A systemd unit for a `ghostunnel.socket` for listening on `*:8443` could look like this: @@ -115,5 +137,22 @@ Note that the `FileDescriptorName` in `ghostunnel.socket` matches the name passe `--listen`. If multiple sockets are needed, e.g. for a status port, the name can be used to distinguish the listening and status sockets. +To install and enable: + +```bash +# Copy unit files into place +sudo cp ghostunnel.socket ghostunnel.service /etc/systemd/system/ + +# Reload, enable, and start the socket +sudo systemctl daemon-reload +sudo systemctl enable --now ghostunnel.socket +``` + +systemd will start `ghostunnel.service` on demand when a connection arrives +on the socket. + Ghostunnel also supports systemd notify and watchdog functionality. See [WATCHDOG]({{< ref "WATCHDOG.md" >}}) for details on configuring `Type=notify-reload` services. + +[launchd-guide]: https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html +[systemd-socket]: https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html diff --git a/docs/SPIFFE-WORKLOAD-API.md b/docs/SPIFFE-WORKLOAD-API.md index 3ff07b60b5..9eaa01bc5d 100644 --- a/docs/SPIFFE-WORKLOAD-API.md +++ b/docs/SPIFFE-WORKLOAD-API.md @@ -38,10 +38,11 @@ ghostunnel server \ --allow-uri spiffe://domain.test/frontend ``` -### Authorization +## Authorization -The identity of the peer, i.e. the [SPIFFE ID](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md), is embedded as a URI SAN on the -X509-SVID. Accordingly, the existing `--verify-uri` and `--allow-uri` +The identity of the peer, i.e. the +[SPIFFE ID](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md), +is embedded as a URI SAN on the X509-SVID. Accordingly, the existing `--verify-uri` and `--allow-uri` flags can be used to authorize the peer: As a server: @@ -64,7 +65,7 @@ ghostunnel client \ --verify-uri spiffe://domain.test/backend ``` -### Trust bundle updates +## Trust bundle updates When using the Workload API, Ghostunnel automatically watches for updates to both the X.509 identity (certificate and key) and the trusted root CA @@ -72,8 +73,11 @@ bundle. When the SPIFFE provider (e.g. SPIRE) rotates certificates or updates the trust bundle, Ghostunnel picks up the changes without requiring a manual reload or restart. -### Demo +## Demo See the [end-to-end demo](https://github.com/ghostunnel/ghostunnel/tree/master/docs/spiffe-workload-api-demo) for an example using Ghostunnel with SPIFFE Workload API support backed by -[SPIRE](https://spiffe.io/spire/). +[SPIRE](https://spiffe.io/spire/). The [SPIRE getting started guide][spire-getting-started] covers setting up +SPIRE from scratch on Linux/macOS. + +[spire-getting-started]: https://spiffe.io/docs/latest/try/getting-started-linux-macos-x/ diff --git a/docs/WATCHDOG.md b/docs/WATCHDOG.md index 3a93a961ff..6bd0ddfe91 100644 --- a/docs/WATCHDOG.md +++ b/docs/WATCHDOG.md @@ -4,13 +4,13 @@ description: Integrate with the systemd watchdog timer for automatic restart on weight: 85 --- -Ghostunnel supports systemd's notify and watchdog functionality on Linux. This -allows systemd to know when Ghostunnel is ready and to automatically restart it -if it becomes unresponsive. +Ghostunnel supports systemd's [notify][sd-notify] and watchdog functionality on +Linux. This allows systemd to know when Ghostunnel is ready and to automatically +restart it if it becomes unresponsive. -### How it works +## How it works -When running as a `Type=notify-reload` service: +When running as a [`Type=notify-reload`][systemd-service] service: * **Notify**: Ghostunnel signals readiness to systemd after it has successfully loaded certificates and started listening. Systemd will not consider the @@ -23,7 +23,7 @@ When running as a `Type=notify-reload` service: `SIGHUP` to the process, which triggers a certificate reload (same as sending `SIGHUP` manually). -### Example unit file +## Example unit file ```ini [Unit] @@ -45,7 +45,7 @@ Restart=always WantedBy=default.target ``` -### Notes +## Notes * `Type=notify-reload` requires systemd v253 or later. If you are on an older version, use `Type=notify` instead (reload via `systemctl reload` will not @@ -58,3 +58,6 @@ WantedBy=default.target native mechanisms. * For socket activation with systemd, see [Socket Activation]({{< ref "SOCKET-ACTIVATION.md" >}}). + +[sd-notify]: https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html +[systemd-service]: https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html diff --git a/docs/tunnel-diagram.dot b/docs/tunnel-diagram.dot new file mode 100644 index 0000000000..3d1883afe5 --- /dev/null +++ b/docs/tunnel-diagram.dot @@ -0,0 +1,15 @@ +digraph tunnel { + rankdir=LR; + bgcolor="transparent"; + node [fontname="Helvetica" fontsize=12 style=filled]; + edge [fontname="Helvetica" fontsize=10]; + + curl [label="curl\n:8081" shape=box fillcolor="#e8e8e8" color="#999999"]; + client [label="ghostunnel\nclient" shape=box fillcolor="#4a90d9" fontcolor=white color="#3a7bc8"]; + server [label="ghostunnel\nserver" shape=box fillcolor="#4a90d9" fontcolor=white color="#3a7bc8"]; + backend [label="backend\n:8080" shape=box fillcolor="#e8e8e8" color="#999999"]; + + curl -> client [label=" plaintext " color="#999999" fontcolor="#666666"]; + client -> server [label=" mTLS " color="#d94a4a" fontcolor="#d94a4a" penwidth=2]; + server -> backend [label=" plaintext " color="#999999" fontcolor="#666666"]; +} diff --git a/website/content/_index.md b/website/content/_index.md index fd6e51fd66..67106ab8b1 100644 --- a/website/content/_index.md +++ b/website/content/_index.md @@ -34,55 +34,11 @@ socket. Ghostunnel also supports UNIX domain sockets, PROXY protocol v2, systemd/launchd socket activation, and more. See the [documentation](docs/) for details. -## Install +## Getting Started -Pre-built binaries for Linux, macOS, and Windows are available under [Releases](/releases). - -Via Homebrew: - -```bash -brew install ghostunnel -``` - -Via Docker (see [Docker Hub](https://hub.docker.com/r/ghostunnel/ghostunnel) for all -available tags): - -```bash -docker pull ghostunnel/ghostunnel:latest-distroless # Distroless (recommended) -docker pull ghostunnel/ghostunnel:latest-alpine # Alpine -docker pull ghostunnel/ghostunnel:latest-debian # Debian -``` - -Compile from source (replace `VERSION` with a [release tag](https://github.com/ghostunnel/ghostunnel/releases)): - -```bash -go install github.com/ghostunnel/ghostunnel@VERSION -``` - -## Usage - -Start a Ghostunnel in server mode to proxy TLS connections to a backend: - -```bash -ghostunnel server \ - --listen :8443 \ - --target localhost:8080 \ - --keystore server-keystore.p12 \ - --cacert cacert.pem \ - --allow-cn client -``` - -Start a Ghostunnel in client mode to wrap connections in TLS: - -```bash -ghostunnel client \ - --listen localhost:8080 \ - --target example.com:8443 \ - --keystore client-combined.pem \ - --cacert cacert.pem -``` - -See [Docs](/docs) for more in-depth usage information. +See the [Quick Start](/docs/quickstart/) guide for installation, generating +test certificates, and running your first tunnel. The full documentation is +available under [Docs](/docs/). ## Supported Platforms diff --git a/website/static/tunnel-diagram.svg b/website/static/tunnel-diagram.svg new file mode 100644 index 0000000000..0e261b08e2 --- /dev/null +++ b/website/static/tunnel-diagram.svg @@ -0,0 +1,61 @@ + + + + + + +tunnel + + +curl + +curl +:8081 + + + +client + +ghostunnel +client + + + +curl->client + + +  plaintext   + + + +server + +ghostunnel +server + + + +client->server + + +  mTLS   + + + +backend + +backend +:8080 + + + +server->backend + + +  plaintext   + + +