v0.16.1 doc update

This commit is contained in:
2026-08-29 16:37:20 -04:00
parent 296b351883
commit 5fbd5c71cf
19 changed files with 3066 additions and 1323 deletions
+239 -1
View File
@@ -1,4 +1,242 @@
# Security
*Note: Security features like mTLS and IP-Based Access Control are currently under redesign and act as placeholders in the new architecture. Future versions will reintroduce full security capabilities.*
This page covers LogWisp's transport security: what it protects, how to
configure it, and — equally important — what it does not yet do.
## Current State
| Capability | Status |
|------------|--------|
| TLS 1.2 / 1.3 on all network sources and sinks | Implemented |
| Server certificate verification by dialers | Implemented |
| Mutual TLS (client certificate required and verified) | Implemented at the transport layer |
| Peer identity (certificate CN) recorded per session | Implemented |
| Authorization from peer identity (CN allow-lists, node binding) | **Not implemented** — see [mtls-auth-plan.md](mtls-auth-plan.md) |
| Password, token, or SCRAM authentication | **Removed**; not currently available |
| IP allow/deny lists, per-IP connection or request limits | **Not implemented** |
| Authentication on the `http` sink's stream and status endpoints | **Not implemented** |
Earlier releases carried basic-auth, bearer-token, and SCRAM authentication.
Those were removed during the move to the plugin/flow architecture and the
switch to standard-library networking. Only certificate-based transport
security survived that transition.
## The TLS Block
One option shape serves both roles, so the configuration reads the same
wherever it appears. Which keys matter depends on whether the plugin listens or
dials.
```toml
[pipelines.plugin_sources.config.tls] # or plugin_sinks.config.tls
enabled = false
cert_file = ""
key_file = ""
client_auth = false
client_ca_file = ""
ca_file = ""
server_name = ""
insecure_skip_verify = false
min_version = "1.3"
```
| Option | Role | Default | Description |
|--------|------|---------|-------------|
| `enabled` | both | `false` | Master switch; when false the whole block is ignored |
| `cert_file` | both | — | Local certificate. **Required** for listeners; optional client identity for dialers |
| `key_file` | both | — | Private key for `cert_file`. Must be set together with it |
| `client_auth` | listener | `false` | Require and verify a client certificate (mTLS) |
| `client_ca_file` | listener | — | CA bundle used to verify client certificates. **Required** when `client_auth` is true |
| `ca_file` | dialer | system store | CA bundle used to verify the server certificate |
| `server_name` | dialer | the configured `host` | SNI and certificate name to verify against |
| `insecure_skip_verify` | dialer | `false` | Disable server verification |
| `min_version` | both | `"1.3"` | `"1.2"` or `"1.3"` |
**Roles by plugin:**
| Plugin | Role | Keys that apply |
|--------|------|-----------------|
| `tcp` sink, `http` sink | Listener | `cert_file`, `key_file`, `client_auth`, `client_ca_file`, `min_version` |
| `tcp_chain` source, `http_chain` source | Listener | same as above |
| `tcp_chain` sink, `http_chain` sink | Dialer | `ca_file`, `server_name`, `insecure_skip_verify`, `cert_file`, `key_file`, `min_version` |
> `min_version` takes `"1.2"` or `"1.3"`. The older `"TLS1.2"` spelling from
> pre-restructure releases is rejected. There is no `max_version` and no
> `cipher_suites` option; TLS 1.3 suites are not configurable in Go, and the
> 1.2 defaults are the standard library's.
### Validation
Misconfiguration fails at plugin construction, before the pipeline starts:
- a listener with `enabled = true` and no `cert_file`/`key_file`
- `client_auth = true` with no `client_ca_file`
- a dialer with only one of `cert_file` / `key_file`
- a certificate or key that will not load, or a CA file containing no
certificates
- a `min_version` that is neither `"1.2"` nor `"1.3"`
## Enabling mTLS
### 1. Generate a CA and certificates
LogWisp no longer ships a certificate-generation subcommand; the `logwisp tls`
command was removed with the rest of the CLI restructure. Use `openssl`,
`cfssl`, `step-cli`, or your existing PKI.
```bash
# CA
openssl req -x509 -newkey rsa:4096 -nodes -days 3650 \
-keyout ca.key -out ca.crt -subj "/CN=LogWisp CA"
# Relay (server) certificate — SAN must match how clients address it
openssl req -newkey rsa:2048 -nodes -keyout relay.key -out relay.csr \
-subj "/CN=relay.internal"
openssl x509 -req -in relay.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out relay.crt -days 825 \
-extfile <(printf "subjectAltName=DNS:relay.internal\nextendedKeyUsage=serverAuth")
# Edge (client) certificate — CN identifies the node
openssl req -newkey rsa:2048 -nodes -keyout edge-01.key -out edge-01.csr \
-subj "/CN=edge-01"
openssl x509 -req -in edge-01.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out edge-01.crt -days 825 \
-extfile <(printf "extendedKeyUsage=clientAuth")
```
The server certificate's SAN must cover the address clients dial. Dialers seed
`ServerName` from the configured `host`, so an IP literal in `host` requires an
IP SAN, and a DNS name requires a DNS SAN. Override with `server_name` when the
dialed address and the certificate name legitimately differ.
### 2. Configure the listener
```toml
[pipelines.plugin_sources.config.tls]
enabled = true
cert_file = "/etc/logwisp/tls/relay.crt"
key_file = "/etc/logwisp/tls/relay.key"
client_auth = true
client_ca_file = "/etc/logwisp/tls/ca.crt"
min_version = "1.3"
```
### 3. Configure the dialer
```toml
[pipelines.plugin_sinks.config.tls]
enabled = true
ca_file = "/etc/logwisp/tls/ca.crt"
cert_file = "/etc/logwisp/tls/edge-01.crt"
key_file = "/etc/logwisp/tls/edge-01.key"
min_version = "1.3"
```
### 4. Verify
Startup logs report both flags:
```
INFO msg="TCP chain source initialized" ... tls=true mtls=true
INFO msg="TCP chain sink initialized" ... tls=true mtls=true
```
A client that presents no certificate is refused during the handshake:
```
WARN msg="TLS handshake failed" component=tcp_chain_source
remote_addr=127.0.0.1:53840 error="tls: client didn't provide a certificate"
```
Handshake failures are counted in the `tls_handshake_errors` statistic on the
`tcp` sink and the `tcp_chain` source.
## What mTLS Currently Buys You
With `client_auth = true`, the transport enforces:
- the peer holds a certificate chaining to `client_ca_file`
- the certificate is within its validity window and not structurally broken
- the peer holds the matching private key
That is a real membership check: an attacker without a CA-issued certificate
cannot connect at all.
## What It Does Not Buy You
**Any** valid certificate from the configured CA is accepted. LogWisp extracts
the peer's Common Name into session metadata (`tls_peer_cn`) but never consults
it, so within one CA there is no way to express:
- "only `edge-01` and `edge-02` may connect to this ingest port"
- "the node label `edge-01` may only be claimed by the holder of the `edge-01`
certificate"
- "this certificate may connect but only at this rate"
Two consequences follow.
1. **A compromised edge can impersonate any other edge.** With
`trust_node = true` (the default) a peer declares its own node label. Any
certificate holder can claim `edge-99`, or `relay`, and downstream consumers
will attribute its entries accordingly. Setting `trust_node = false` replaces
the label with the remote address, which is coarse but not forgeable at the
application layer.
2. **Revocation is CA-wide.** With no CRL or OCSP checking and no per-identity
allow-list, withdrawing one node's access means re-issuing the CA or rotating
the CA bundle for every peer.
Closing both gaps is the subject of the
[mTLS authentication plan](mtls-auth-plan.md).
## Unauthenticated Surfaces
These endpoints have no access control at all. Bind them to a trusted interface
or front them with an authenticating proxy.
| Surface | Exposure |
|---------|----------|
| `http` sink `stream_path` | Full log stream, with `Access-Control-Allow-Origin: *`, so any browser origin can read it |
| `http` sink `status_path` | Host, port, TLS flag, uptime, client counts, throughput counters |
| `tcp` sink | Full log stream to any client that connects |
`max_connections` bounds concurrency on all three but does not distinguish
callers.
## Operational Guidance
**Certificates**
- Use a dedicated CA for LogWisp so its trust decisions stay independent.
- Keep leaf lifetimes short (90825 days) and automate renewal.
- Key files should be `0600` and owned by the service account.
- Rotation requires a reload (`SIGHUP`), because certificates are loaded once at
plugin construction; there is no on-disk watch for certificate files.
- Check expiry: `openssl x509 -in relay.crt -noout -enddate`.
**Deployment**
- Prefer `min_version = "1.3"`. Drop to `"1.2"` only for a peer that genuinely
cannot do 1.3.
- Never enable `insecure_skip_verify` outside a lab; it disables server
verification entirely and makes the connection trivially interceptable.
- Bind listeners to specific interfaces rather than `0.0.0.0` where you can.
- Use `trust_node = false` on any ingest port reachable from a network you do
not fully control.
- Run LogWisp as an unprivileged user with write access only to its own log and
configuration directories.
**Log content**
Logs routinely contain secrets that were never meant to leave the host. Filters
are the available tool:
```toml
[[pipelines.flow.filters]]
type = "exclude"
patterns = ["password", "api[_-]?key", "authorization", "bearer ", "secret"]
```
Choose a sanitizer policy that matches the sink — `json` for JSON output,
`txt` for files and consoles — so control characters in log data cannot break
framing or inject terminal escapes downstream. See
[Formatters](formatters.md).