# 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](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 (90–825 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).