Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/deployment/launchd.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,40 @@ Both `SockType` and `SockFamily` must be defined for each socket. If the
family is omitted, launchd opens two sockets (IPv4 and IPv6) for each key,
which Ghostunnel does not currently support.

## UNIX Socket Activation

To restrict access to a specific local user (see
[Security]({{< ref "general.md#restricting-to-specific-local-users" >}})),
have launchd create a UNIX domain socket with the desired ownership and mode
instead of binding to TCP:

```xml
<key>Sockets</key>
<dict>
<key>Listener</key>
<dict>
<key>SockPathName</key>
<string>/var/run/ghostunnel.sock</string>
<key>SockPathMode</key>
<integer>384</integer>
<key>SockPathOwner</key>
<integer>0</integer>
<key>SockPathGroup</key>
<integer>0</integer>
<key>SockType</key>
<string>stream</string>
</dict>
</dict>
```

`SockPathMode` is a **decimal** integer in plist XML, not octal. `384` is
`0600` (owner read/write only); `416` is `0640`; `432` is `0660`. Combined
with `SockPathOwner`/`SockPathGroup`, this lets launchd enforce per-user
access at socket creation time, without any firewall rules.

The full list of `Sockets` keys is documented in `launchd.plist(5)`
(`man launchd.plist`).

## Installing

```bash
Expand Down
55 changes: 33 additions & 22 deletions docs/deployment/systemd.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,27 @@ The `FileDescriptorName` in `ghostunnel.socket` must match the name passed to
`--listen`. If multiple sockets are needed (e.g. for a status port), use the
name to distinguish them.

### UNIX Socket Variant

To restrict access to a specific local user (see
[Security]({{< ref "general.md#restricting-to-specific-local-users" >}})),
have systemd create a UNIX domain socket with the desired ownership and mode
instead of binding to TCP:

```ini
[Socket]
FileDescriptorName=ghostunnel
ListenStream=/run/ghostunnel.sock
SocketUser=root
SocketGroup=root
SocketMode=0600
```

systemd applies `SocketUser`/`SocketGroup`/`SocketMode` at socket creation
time, so only the intended user can `connect(2)` to it; no firewall rules
required. See [systemd.socket(5)][systemd-socket] for the full list of
options.

### Installing

```bash
Expand All @@ -162,10 +183,15 @@ the privileges it needs:

```ini
[Service]
# Run as a dedicated unprivileged user
# Dedicated unprivileged user. Use User=ghostunnel if you need persistent
# state or specific file ownership instead.
DynamicUser=yes

# Filesystem restrictions
# Filesystem: most of the system becomes read-only outside /dev, /proc, /sys.
# Ghostunnel only needs read access to certs, which still works from standard
# readable locations such as /etc. Note that ProtectHome=yes makes /home and
# /root inaccessible, so certs should not be stored there unless you relax
# ProtectHome or add an explicit exception with ReadOnlyPaths=.
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
Expand All @@ -176,11 +202,11 @@ ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectProc=invisible

# Network: only allow AF_INET/AF_INET6 (and AF_UNIX for syslog/notify)
# Network: only allow AF_INET/AF_INET6/AF_UNIX
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

# Capabilities: drop everything, Ghostunnel doesn't need any
# (use socket activation or listen on ports > 1024)
# Capabilities: drop everything. Add CAP_NET_BIND_SERVICE if binding to a
# privileged port (< 1024) without socket activation.
CapabilityBoundingSet=
NoNewPrivileges=yes

Expand All @@ -196,23 +222,8 @@ RemoveIPC=yes
UMask=0077
```

### Notes

* **`DynamicUser=yes`** allocates a transient user at runtime. If you need
persistent state or specific file ownership, use a static `User=ghostunnel`
instead.
* **`CapabilityBoundingSet=`** (empty) drops all capabilities. If you need to
bind to a privileged port (< 1024) without socket activation, add
`CAP_NET_BIND_SERVICE` instead.
* **`ProtectSystem=strict`** makes the entire filesystem read-only except
`/dev`, `/proc`, and `/sys`. Ghostunnel only needs to read certificate
files, so this is safe. If your certificates live outside the default
paths, no extra configuration is needed — they are already readable.
* These settings work alongside Ghostunnel's own Landlock sandboxing
(enabled by default on Linux). The two layers are complementary — systemd
restricts at the process level, Landlock restricts within the process.
* Run `systemd-analyze security ghostunnel.service` to audit the effective
security posture of your unit file.
Run `systemd-analyze security ghostunnel.service` to audit the effective
security posture of your unit file.

[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
Expand Down
86 changes: 40 additions & 46 deletions docs/security/general.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,29 @@
---
title: Landlock & TLS
title: General Security
description: Landlock sandboxing, TLS protocol settings, cipher suites, address restrictions.
weight: 10
---

Ghostunnel's TLS settings and Landlock sandboxing.
Ghostunnel's TLS settings, address restrictions, and Landlock sandboxing.

## TLS Configuration
## TLS Settings

Ghostunnel enforces a minimum TLS version of **TLS 1.2**. Earlier versions are
not supported. TLS 1.3 is supported and will be negotiated when both sides
support it.
Ghostunnel enforces a minimum TLS version of TLS 1.2, and TLS 1.3 is supported
and will be negotiated when both sides support it. Earlier versions of TLS are
not supported.

### Cipher Suites

The following cipher suites are enabled by default, in order of preference:

**AES-GCM:**
- `TLS_AES_128_GCM_SHA256` (TLS 1.3)
- `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`
- `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`
- `TLS_AES_256_GCM_SHA384` (TLS 1.3)
- `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`
- `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`

**ChaCha20-Poly1305:**
- `TLS_CHACHA20_POLY1305_SHA256` (TLS 1.3)
- `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`
- `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305`

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.

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 2>/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.

### Curve Preferences

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
and cannot be configured by the application. For TLS 1.2, the configured cipher
suites all use authenticated encryption (AEAD). Older CBC-mode ciphers are not
enabled.

### Client Authentication

In server mode, Ghostunnel requires and verifies client certificates by
default (`RequireAndVerifyClientCert`). This can be disabled with
`--disable-authentication`, in which case no client certificate is requested.
default. This can be disabled with `--disable-authentication`, in which case no
client certificate is requested.

The status port (`--status`) is optional and does not require client
certificates. It is typically consumed by monitoring systems that may not
Expand Down Expand Up @@ -96,6 +61,34 @@ To accept connections from remote hosts, pass `--unsafe-listen`. The listen
side of client mode accepts plaintext connections, so exposing it beyond
localhost risks unauthorized access to the proxied service.

### Restricting to specific local users

Binding to `localhost` (or `127.0.0.1` / `[::1]`) blocks the network, but on a
shared host any local user can still connect to the port. If you need to
restrict access to a specific UID (for example, only `root` may reach the
plaintext side of a tunnel), bind to a UNIX domain socket and use
filesystem permissions:

```bash
ghostunnel client \
--listen=unix:/var/run/ghostunnel/client.sock \
--target=backend.example.com:8443 \
...
```

Set the socket's owner and mode so only the intended user can `connect(2)` to
it. With socket activation, the service manager creates the socket and applies
the permissions for you; see [Systemd]({{< ref "systemd.md" >}}) and
[Launchd]({{< ref "launchd.md" >}}). Otherwise, ensure the socket's parent
directory is `chmod 0700` and `chown` it to the intended user, since the path
must be traversable to connect.

Firewall-based UID filtering exists but is fragile. On Linux, `iptables`
supports `-m owner --uid-owner` and `nftables` supports `skuid`. On macOS, `pf`
accepts `user =` rules, but Apple has formally stated that `pf` is not a stable
API; see [TN3165: Packet Filter is not API][tn3165]. Prefer UNIX socket
permissions, which are kernel-enforced via VFS and survive OS upgrades.

## Landlock sandboxing

*Available since v1.8.0. Enabled by default since v1.9.0.*
Expand Down Expand Up @@ -133,3 +126,4 @@ 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
[tn3165]: https://developer.apple.com/documentation/technotes/tn3165-packet-filter-is-not-api
Loading