9.4 KiB
Security
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 |
| 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.
[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_versiontakes"1.2"or"1.3". The older"TLS1.2"spelling from pre-restructure releases is rejected. There is nomax_versionand nocipher_suitesoption; 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 = trueand nocert_file/key_file client_auth = truewith noclient_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_versionthat 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.
# 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
[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
[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-01andedge-02may connect to this ingest port" - "the node label
edge-01may only be claimed by the holder of theedge-01certificate" - "this certificate may connect but only at this rate"
Two consequences follow.
- 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 claimedge-99, orrelay, and downstream consumers will attribute its entries accordingly. Settingtrust_node = falsereplaces the label with the remote address, which is coarse but not forgeable at the application layer. - 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.
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 (90–825 days) and automate renewal.
- Key files should be
0600and 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_verifyoutside a lab; it disables server verification entirely and makes the connection trivially interceptable. - Bind listeners to specific interfaces rather than
0.0.0.0where you can. - Use
trust_node = falseon 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:
[[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.