v0.31.0 refinements
This commit is contained in:
1 parent
bfcc15613a
commit
6bd5e12a60
52 files changed
+1948
-683
No files matched your search
+1
-1
@@ -90,7 +90,7 @@ stdout:
|
||||
|
||||
```bash
|
||||
lw < app.log > copy.log
|
||||
tail -F app.log | lw --filter include,patterns=ERROR,patterns=WARN
|
||||
tail -F app.log | lw --filter include:patterns=ERROR,patterns=WARN
|
||||
```
|
||||
|
||||
As a service, a file holds the pipelines:
|
||||
|
||||
+3
-2
@@ -255,8 +255,9 @@ is what their certificates say, regardless of the `node` each edge configured.
|
||||
`test/mtls-chain-test.sh` builds exactly this shape against a throwaway PKI.
|
||||
|
||||
To use passwords instead of client certificates, replace both `auth` blocks:
|
||||
`type = "scram"` with `username` and `password_file` on the edge, and with a
|
||||
`credentials_file` on the relay. `client_auth` and the edge's certificate become
|
||||
`type = "scram"` with `username` and `password_file` on the edge, and on the
|
||||
relay a `credentials_file`, or one `username` with its `password_file`.
|
||||
`client_auth` and the edge's certificate become
|
||||
optional, and entries are labelled with the username. See
|
||||
[Password Authentication](security.md#password-authentication-scram).
|
||||
|
||||
|
||||
+91
-66
@@ -3,7 +3,7 @@
|
||||
```
|
||||
lw [options] [< input]
|
||||
lw -c|--config FILE [options]
|
||||
lw -p|--preset NAME[,KEY=VALUE...] [options]
|
||||
lw -p|--preset NAME[:KEY=VALUE,...] [options]
|
||||
lw -t|--check [options]
|
||||
lw --dump [options]
|
||||
lw -T|--tui [options]
|
||||
@@ -45,7 +45,7 @@ first argument.
|
||||
tail`, `lw -t FILE`) are usage errors, named on stderr.
|
||||
- lw's multiword options join words with `-` (`--rate-limit`,
|
||||
`--password-file`); configuration keys, spec keys and preset keys keep
|
||||
TOML's `_` and `.` (`--status_reporter`, `--sink http,tls.cert_file=F`,
|
||||
TOML's `_` and `.` (`--status_reporter`, `--sink http:tls.cert_file=F`,
|
||||
`lw preset edge --password_file F`).
|
||||
- The commands also take a long option after one dash (`-user NAME`).
|
||||
|
||||
@@ -121,14 +121,15 @@ pipelines and the built-in default; every other key keeps its precedence.
|
||||
- stdin has one reader, so the whole configuration holds at most one console
|
||||
source: `pipeline "b": a console source already reads stdin in pipeline "a"`
|
||||
|
||||
A SPEC is a comma-separated list:
|
||||
A SPEC is `TYPE:key=value,...`, or `key=value,...` without a TYPE:
|
||||
|
||||
- source, sink, filter and format specs start with a TYPE
|
||||
- source, sink, filter and format specs start with a TYPE, which ends at the
|
||||
first `:`; a TYPE alone (`--sink null`, `--format txt`) takes the defaults
|
||||
- the plugin type: [Sources](sources.md), [Sinks](sinks.md)
|
||||
- `include` or `exclude`: [Filters](filters.md)
|
||||
- `json`, `txt` or `raw`: [Formatters](formatters.md)
|
||||
- rate-limit and heartbeat specs have no TYPE
|
||||
- the rest are `key=value` pairs with the TOML keys of the plugin's `config`
|
||||
- the pairs are comma-separated, with the TOML keys of the plugin's `config`
|
||||
table or of the [flow stage](configuration.md#flow-stages)
|
||||
- a dotted key reaches a nested table: `tls.cert_file=...`, `auth.type=scram`
|
||||
- a repeated key makes a list: `patterns=ERROR,patterns=WARN`
|
||||
@@ -136,7 +137,8 @@ A SPEC is a comma-separated list:
|
||||
is one regex, `auth.allow=a,auth.allow=b` two entries
|
||||
- values convert to the option's type: `port=8080`, `raw=true`
|
||||
- `\` escapes `,`, `=` and `\` in a value; any other backslash stays, so regex
|
||||
escapes such as `\d` pass unchanged
|
||||
escapes such as `\d` pass unchanged; a `:` in a value needs no escape
|
||||
(`host=::1`, `listen=127.0.0.1:8080`)
|
||||
- `id=NAME` names a source or sink; the default is its TYPE, then `TYPE_2`,
|
||||
`TYPE_3`, ...
|
||||
- naming a stage turns it on: `--rate-limit` defaults `policy` to `drop`,
|
||||
@@ -145,8 +147,9 @@ A SPEC is a comma-separated list:
|
||||
Errors name the flag and the offending part, and nothing starts:
|
||||
|
||||
```
|
||||
--sink http,port: missing "="
|
||||
--rate-limit rate=100,polcy=drop: unknown key "polcy"
|
||||
--sink http:port: missing "="
|
||||
--rate-limit entries_per_second=100,polcy=drop: unknown key "polcy"
|
||||
--source file,directory=/x: TYPE ends at ':', as in file:directory=/x
|
||||
```
|
||||
|
||||
A misspelled plugin key fails when the plugin is built, before any listener
|
||||
@@ -155,35 +158,39 @@ opens: `failed to create sink http: ... unknown key "tls.enabeld"`.
|
||||
```bash
|
||||
# tail a directory, serve it over SSE on every interface; a browser at
|
||||
# http://HOST:8080/ shows it
|
||||
lw --source 'file,directory=/var/log/app,pattern=*.log' \
|
||||
--sink http,host=0.0.0.0,port=8080
|
||||
lw --source 'file:directory=/var/log/app,pattern=*.log' \
|
||||
--sink http:host=0.0.0.0,port=8080
|
||||
|
||||
# errors and warnings as JSON, over TLS with SCRAM logins
|
||||
lw --source file,directory=/var/log/app \
|
||||
--filter include,patterns=ERROR,patterns=WARN --format json \
|
||||
--sink "http,port=8443,auth.type=scram,auth.credentials_file=/etc/logwisp/users.toml,\
|
||||
lw --source file:directory=/var/log/app \
|
||||
--filter include:patterns=ERROR,patterns=WARN --format json \
|
||||
--sink "http:port=8443,auth.type=scram,auth.credentials_file=/etc/logwisp/users.toml,\
|
||||
tls.enabled=true,tls.cert_file=/etc/logwisp/server.crt,tls.key_file=/etc/logwisp/server.key"
|
||||
|
||||
# two pipelines: app writes stdout, relay a file
|
||||
lw --pipeline app --source file,directory=/var/log/app \
|
||||
--pipeline relay --source tcp_chain,port=9000 \
|
||||
--sink file,directory=/var/log/relay,name=relay
|
||||
lw --pipeline app --source file:directory=/var/log/app \
|
||||
--pipeline relay --source tcp_chain:port=9000 \
|
||||
--sink file:directory=/var/log/relay,name=relay
|
||||
```
|
||||
|
||||
Pipelines over stdin and stdout are under [Usage Patterns](#usage-patterns).
|
||||
|
||||
## Presets
|
||||
|
||||
`--preset NAME,key=value,...` takes the [SPEC](#pipelines) syntax. A list
|
||||
key (`hosts`, `proxy`, `allow`, `deny`) takes several values, repeated
|
||||
(`hosts=a,hosts=b`) or `,`-separated; any other key takes one.
|
||||
`--preset NAME:key=value,...` takes the [SPEC](#pipelines) syntax. A list
|
||||
key (`hosts`, `proxy`, `allow`, `deny`) takes several values: repeated
|
||||
(`hosts=a,hosts=b`) or `\,`-separated in a SPEC, `,`-separated in `lw preset`
|
||||
flags and the TUI; any other key takes one.
|
||||
`lw preset NAME [--key value ...]` prints the pipeline a preset expands to,
|
||||
ready for a configuration file; its flags are the keys, spelled as in the SPEC
|
||||
(`--password_file`), and `-u` is `--user`;
|
||||
`lw preset NAME -h` lists its keys and defaults, `lw preset` the presets. An
|
||||
unknown key fails and lists the valid ones. A `path` is a file, a directory
|
||||
(its files) or a glob; a preset that takes one reads stdin without it, and
|
||||
`from` (`end`, or `start`) is where reading a file starts.
|
||||
`from` (`end`, or `start`) is where reading a file starts. `user` names one
|
||||
SCRAM user, an edge's login or a listener's only user in place of `users`;
|
||||
`password_file` holds its password and defaults to `/dev/tty`, which asks
|
||||
([password sources](security.md#one-user-without-a-credentials-file)).
|
||||
|
||||
- `pipe`: stdin to stdout, the built-in default
|
||||
- `format` (`raw`)
|
||||
@@ -196,41 +203,49 @@ unknown key fails and lists the valid ones. A `path` is a file, a directory
|
||||
- `tls`: `off` (default), `self` (a self-signed certificate made at
|
||||
startup), `issuer` (one signed by `issuer_cert` and `issuer_key`), or
|
||||
`files` (`cert` and `key`); `hosts` adds names to a made certificate
|
||||
- without `users` the viewer needs no login, over plain http too
|
||||
- without `user` or `users` the viewer needs no login, over plain http too
|
||||
- `allow` and `deny`: addresses or CIDRs, the sink's
|
||||
[acl](security.md#the-acl-block)
|
||||
- `users`: a credentials file; readers then log in with SCRAM, and browsers
|
||||
need `proxy` and `viewer=true`: the login page and viewer behind the
|
||||
TLS-terminating proxies `proxy` names, since a browser cannot bind its
|
||||
login to the TLS channel
|
||||
- `users` (a credentials file) or `user`: readers then log in with SCRAM,
|
||||
which needs `tls` or `proxy`; browsers need `proxy` and `viewer=true`: the
|
||||
login page and viewer behind the TLS-terminating proxies `proxy` names,
|
||||
since a browser cannot bind its login to the TLS channel
|
||||
- `edge`: forward to an aggregator over TLS (`tcp_chain`, or `http_chain`
|
||||
with `transport=http`)
|
||||
- `to` (required), `path`, `from`, `transport` (`tcp`), `node`
|
||||
- verify the aggregator by `ca` (a CA file; default: system roots) or `pin`
|
||||
(its `tls.pin_sha256`), and `server_name`
|
||||
- authenticate by `user` and `password_file` (SCRAM), or a client `cert` and
|
||||
`key` (mTLS): without either the preset refuses to run
|
||||
- authenticate by `user` (SCRAM), or a client `cert` and `key` (mTLS):
|
||||
without either the preset refuses to run
|
||||
- `aggregator`: receive from edges over TLS, to stdout or files
|
||||
- `listen` (`0.0.0.0:9000`), `transport` (`tcp`), `format` (`json`)
|
||||
- `tls`: `self` (default), `issuer` or `files`, as for `serve`; never `off`
|
||||
- authenticate by `users` (SCRAM) or `client_ca` (mTLS): one is required
|
||||
- authenticate by `users` or `user` (SCRAM), or `client_ca` (mTLS): one is
|
||||
required
|
||||
- `allow` and `deny`, as for `serve`
|
||||
- `out`: a directory for `aggregate*.log` files; default stdout
|
||||
|
||||
```bash
|
||||
# follow a directory, level names in color on a terminal
|
||||
lw --preset tail,path=/var/log/app
|
||||
lw --preset tail:path=/var/log/app
|
||||
|
||||
# serve it to a browser at http://127.0.0.1:8080/
|
||||
lw --preset serve,path=/var/log/app
|
||||
lw --preset serve:path=/var/log/app
|
||||
|
||||
# serve it over HTTPS, self-signed, to SCRAM users; keep the result as a file
|
||||
lw --preset serve,path=/var/log/app,tls=self,users=/etc/logwisp/users.toml --dump > serve.toml
|
||||
lw --preset serve:path=/var/log/app,tls=self,users=/etc/logwisp/users.toml --dump > serve.toml
|
||||
|
||||
# an aggregator with a certificate made at startup, and an edge pinning it
|
||||
lw --preset aggregator,users=/etc/logwisp/users.toml,out=/var/log/edges
|
||||
lw --preset edge,path=/var/log/app,to=agg.example.org:9000,pin=sha256//BASE64,\
|
||||
lw --preset aggregator:users=/etc/logwisp/users.toml,out=/var/log/edges
|
||||
lw --preset edge:path=/var/log/app,to=agg.example.org:9000,pin=sha256//BASE64,\
|
||||
user=edge-01,password_file=/etc/logwisp/edge-01.pass
|
||||
|
||||
# a temporary pipe, nothing on disk: Enter at the receiver's prompt draws the
|
||||
# password, typed at the sender's; or both read it from a descriptor
|
||||
lw -p aggregator:listen=0.0.0.0:9000,user=pipe,format=raw,allow=SENDER/32
|
||||
cmd | lw -p edge:to=RECEIVER:9000,pin=sha256//BASE64,user=pipe
|
||||
cmd | lw -p edge:to=RECEIVER:9000,pin=sha256//BASE64,user=pipe,\
|
||||
password_file=/dev/fd/3 3< <(pass show lw/pipe)
|
||||
```
|
||||
|
||||
A self-signed aggregator logs its pin at startup (`pin_sha256`, a warning, so
|
||||
@@ -278,9 +293,9 @@ A container needs no configuration file:
|
||||
```bash
|
||||
docker run --rm -p 8080:8080 -v /var/log/app:/logs:ro \
|
||||
-e LOGWISP_PIPELINE=app \
|
||||
-e LOGWISP_SOURCE='file,directory=/logs,pattern=*.log' \
|
||||
-e LOGWISP_FILTER='exclude,patterns=DEBUG' \
|
||||
-e LOGWISP_SINK='http,host=0.0.0.0,port=8080' \
|
||||
-e LOGWISP_SOURCE='file:directory=/logs,pattern=*.log' \
|
||||
-e LOGWISP_FILTER='exclude:patterns=DEBUG' \
|
||||
-e LOGWISP_SINK='http:host=0.0.0.0,port=8080' \
|
||||
-e LOGWISP_SINK_1=console \
|
||||
-e LOGWISP_LOGGING_LEVEL=info \
|
||||
logwisp
|
||||
@@ -382,13 +397,13 @@ sink gives its clients or its downstream its `write_timeout_ms`
|
||||
lw < app.log > copy.log
|
||||
|
||||
# errors and warnings only
|
||||
tail -F app.log | lw --filter include,patterns=ERROR,patterns=WARN
|
||||
tail -F app.log | lw --filter include:patterns=ERROR,patterns=WARN
|
||||
|
||||
# stdin as a live SSE stream; the http and tcp sinks bind 0.0.0.0 by default
|
||||
journalctl -f | lw --sink http,host=127.0.0.1,port=8080
|
||||
journalctl -f | lw --sink http:host=127.0.0.1,port=8080
|
||||
|
||||
# a directory's files to one file, through stdout
|
||||
lw --source file,directory=/var/log/app,pattern='*.log' > all.log
|
||||
lw --source file:directory=/var/log/app,pattern='*.log' > all.log
|
||||
```
|
||||
|
||||
A console sink never drops: when its reader is slow, the pipeline waits, and
|
||||
@@ -411,7 +426,7 @@ prints `configuration ok: N pipeline(s)` and exits 0, or the error and exits 1
|
||||
|
||||
```bash
|
||||
lw --check -c /etc/logwisp/logwisp.toml
|
||||
lw --check --source file,directory=/var/log/app --sink http,port=8080
|
||||
lw --check --source file:directory=/var/log/app --sink http:port=8080
|
||||
```
|
||||
|
||||
**Production**
|
||||
@@ -443,21 +458,30 @@ WebAssembly.
|
||||
pipeline flags, `LOGWISP_SOURCE`, a discovered file); with none but the
|
||||
built-in pipe, the preset menu (or `empty`). A pasted `lw` command line
|
||||
replaces the pipelines, in the preset menu too.
|
||||
- Screen: SOURCES, FLOW and SINKS side by side from 64 columns, stacked
|
||||
below; each source and sink a tinted bar with its type, whether it listens
|
||||
or dials, and a summary; the four flow stages in their order in a dashed
|
||||
box, the ones off dimmed. The inspector, the chosen part's options, sits
|
||||
below the canvas, and beside it from 110 columns.
|
||||
- Keys: arrows or `hjkl` move; Enter inspects, then edits a value (empty
|
||||
returns it to its default); Tab sets it and goes to the next; Esc back; `a` adds a source,
|
||||
sink or filter; `d` deletes, or returns a value to its default; Space turns
|
||||
a flow stage or a switch on or off; `J`/`K` move a filter; `[` `]` switch
|
||||
pipelines; `p` presets; `c` shows the first problem; `o` output; `?` keys;
|
||||
`q` quits.
|
||||
- Output, `o`: run the pipelines with the other options given (so `--check`
|
||||
and `--dump` apply to them, and reloads keep them), or print one of the
|
||||
command line, the environment variables (one pipeline) and the file's
|
||||
pipelines on standard output once the screen closes.
|
||||
- Screen: the pipeline bar on top; SOURCES, FLOW and SINKS side by side
|
||||
from 64 columns, stacked below; each source and sink a tinted bar with its
|
||||
type, whether it listens or dials, and a summary; the four flow stages in
|
||||
their order in a dashed box, the ones off dimmed. The inspector, the
|
||||
chosen part's options with radio buttons for few choices, sits below the
|
||||
canvas, and beside it from 110 columns. Narrow panes stack labels, wrap
|
||||
text and scroll dialogs; the status line wraps to three rows.
|
||||
- Keys: arrows or `hjkl` move; Enter inspects a part, opening on its first
|
||||
required key, then edits a value (empty returns it to its default), opens
|
||||
a list's choices or adds on an add row; in a value, Enter sets it, and Tab
|
||||
or Up/Down set it and move. Left/Right step a choice or set a switch;
|
||||
Space flips it; Esc goes back. `a` adds a source, sink or filter; `c`
|
||||
changes a source's or sink's type in its place; `d` deletes, or returns a
|
||||
value to its default; Space turns a flow stage on or off; `J`/`K` move a
|
||||
filter; `[` `]` switch pipelines; `p` presets; `!` shows the first
|
||||
problem; `r` runs; `o` output; `?` keys; `q` quits.
|
||||
- Pipelines: Up from a column's top row reaches the bar, where `[` `]` or
|
||||
Left/Right switch, `a` adds one (empty or from a preset), Enter renames and
|
||||
`d` deletes, asking first when it has parts.
|
||||
- Output, `o`, each picked by its letter: `r` runs the pipelines with the
|
||||
other options given (so `--check` and `--dump` apply to them, and reloads
|
||||
keep them), or `c`, `e` and `f` print the command line, the environment
|
||||
variables (one pipeline) or the file's pipelines on standard output once
|
||||
the screen closes.
|
||||
- The screen draws on the controlling terminal: `lw -T > pipelines.toml` and
|
||||
`cmd | lw -T` keep standard input and output as data. lw never writes the
|
||||
file it starts from, but the shell empties a redirect's target before lw
|
||||
@@ -476,9 +500,9 @@ them as a viewer. See
|
||||
```
|
||||
lw auth add-user --credentials FILE -u NAME [--password-file FILE] [--generate]
|
||||
lw auth remove-user --credentials FILE -u NAME
|
||||
lw auth token --url https://HOST:PORT[/PATH] -u NAME --password-file FILE
|
||||
lw auth token --url https://HOST:PORT[/PATH] -u NAME [--password-file FILE]
|
||||
[--unbound] [TLS flags]
|
||||
lw auth stream --addr HOST:PORT -u NAME --password-file FILE [TLS flags]
|
||||
lw auth stream --addr HOST:PORT -u NAME [--password-file FILE] [TLS flags]
|
||||
```
|
||||
|
||||
`-u` is short for `--user`.
|
||||
@@ -496,7 +520,7 @@ success, `1` on failure and `2` on a usage error.
|
||||
**`add-user`** creates the credentials file, with a fresh `decoy_key`, when it
|
||||
does not exist or is empty (create it empty first to choose its owner). The
|
||||
password comes from `--password-file` when that file exists (at least 8 bytes;
|
||||
one trailing line break is trimmed). Otherwise a random
|
||||
one trailing line break is trimmed; `/dev/tty` asks). Otherwise a random
|
||||
26-character password (130 bits) is generated and written to `--password-file`,
|
||||
or printed once to stdout when there is none. Replacing an existing user's
|
||||
password needs an existing `--password-file` or `--generate`, so a mistyped path
|
||||
@@ -512,11 +536,13 @@ Neither touches a running LogWisp: send `SIGHUP`, since `auto_reload` does not
|
||||
watch the credentials file.
|
||||
|
||||
**`token`** and **`stream`** build the same TLS and SCRAM client as a chain
|
||||
sink. TLS flags: `--ca-file` (default: system roots) or `--pin-sha256` (the
|
||||
`tls.pin_sha256` a self-signed listener logs), `--server-name` (default: the
|
||||
host), and `--cert-file` / `--key-file` for a listener with `tls.client_auth`.
|
||||
There is no flag to skip verification: an unverified server could relay the
|
||||
login. Redirects are not followed. `--unbound` logs in to an `http` sink behind
|
||||
sink. `--password-file` defaults to `/dev/tty`: the password is asked without
|
||||
echo; a file, `/dev/fd/N` or a pipe is read as by a
|
||||
[plugin](security.md#one-user-without-a-credentials-file). TLS flags:
|
||||
`--ca-file` (default: system roots) or `--pin-sha256` (the `tls.pin_sha256` a
|
||||
self-signed listener logs), `--server-name` (default: the host), and
|
||||
`--cert-file` / `--key-file` for a listener with `tls.client_auth`. There is no
|
||||
flag to skip verification: an unverified server could relay the login. Redirects are not followed. `--unbound` logs in to an `http` sink behind
|
||||
a TLS-terminating proxy (`auth.trusted_proxies`), still pinning the proxy's
|
||||
certificate across the two requests; only then may `--url` carry the path the
|
||||
proxy mounts LogWisp at. `stream` exits `0` on `SIGINT` or `SIGTERM`, and `1`
|
||||
@@ -549,9 +575,8 @@ curl --cacert ca.crt -H @<(printf 'Authorization: Bearer %s\n' \
|
||||
"$(lw auth token --url https://HOST:PORT --user viewer \
|
||||
--password-file viewer.pass --ca-file ca.crt)") https://HOST:PORT/status
|
||||
|
||||
# follow a tcp sink
|
||||
lw auth stream --addr HOST:PORT --user viewer --password-file viewer.pass \
|
||||
--ca-file ca.crt
|
||||
# follow a tcp sink, asked for the password
|
||||
lw auth stream --addr HOST:PORT --user viewer --pin-sha256 sha256//BASE64
|
||||
```
|
||||
|
||||
## `lw tls`
|
||||
|
||||
@@ -111,8 +111,8 @@ name = "app" # required, unique across pipelines
|
||||
|
||||
# --- flow: everything between sources and sinks ---
|
||||
[pipelines.flow.rate_limit]
|
||||
rate = 1000.0
|
||||
burst = 2000.0
|
||||
entries_per_second = 1000.0
|
||||
burst_entries = 2000.0
|
||||
policy = "drop"
|
||||
max_entry_size_bytes = 65536
|
||||
|
||||
@@ -168,19 +168,19 @@ Every stage is optional:
|
||||
|
||||
Options, each as type and default:
|
||||
|
||||
- `rate` (float, `0`): entries per second; `<= 0` disables the limiter
|
||||
entirely.
|
||||
- `burst` (float, `rate`): token bucket capacity.
|
||||
- `entries_per_second` (float, `0`): `<= 0` disables the limiter entirely.
|
||||
- `burst_entries` (float, `entries_per_second`): token bucket capacity.
|
||||
- `policy` (string, `pass`): `pass` allows everything through, `drop` discards
|
||||
over-limit entries.
|
||||
- `max_entry_size_bytes` (int, `0`): per-entry byte cap; `0` = unlimited.
|
||||
|
||||
Two behaviours are easy to trip over:
|
||||
|
||||
- The limiter is constructed only when `rate > 0`. With `rate = 0`,
|
||||
- The limiter is constructed only when `entries_per_second > 0`. With `0`,
|
||||
`max_entry_size_bytes` is never enforced.
|
||||
- `policy = "pass"` short-circuits the whole check, including the size cap.
|
||||
To enforce a size cap you need `rate > 0` **and** `policy = "drop"`.
|
||||
To enforce a size cap you need `entries_per_second > 0` **and**
|
||||
`policy = "drop"`.
|
||||
|
||||
#### Heartbeat
|
||||
|
||||
@@ -258,7 +258,7 @@ Startup validation is intentionally split.
|
||||
"pipelines[0].plugin_sinks[0].confg"`. A misspelled table path would otherwise
|
||||
drop the whole table it heads. A top-level `config_file` is ignored
|
||||
- pipeline [specs](cli.md#pipelines): syntax, and unknown flow-stage keys such
|
||||
as `--rate-limit rate=1,polcy=drop`
|
||||
as `--rate-limit entries_per_second=1,polcy=drop`
|
||||
- at least one pipeline
|
||||
- unique, non-empty pipeline names
|
||||
- at least one source and one sink per pipeline
|
||||
|
||||
+1
-1
@@ -317,7 +317,7 @@ docker run -d --name logwisp \
|
||||
|
||||
```bash
|
||||
-e LOGWISP_PIPELINE=relay \
|
||||
-e LOGWISP_SOURCE=tcp_chain,port=9000,tls.enabled=true,tls.cert_file=/run/secrets/tls.crt,tls.key_file=/run/secrets/tls.key,auth.type=scram,auth.credentials_file=/run/secrets/users.toml \
|
||||
-e LOGWISP_SOURCE=tcp_chain:port=9000,tls.enabled=true,tls.cert_file=/run/secrets/tls.crt,tls.key_file=/run/secrets/tls.key,auth.type=scram,auth.credentials_file=/run/secrets/users.toml \
|
||||
-e LOGWISP_SINK=console
|
||||
```
|
||||
|
||||
|
||||
@@ -342,17 +342,20 @@ Set the pipeline's formatter, rate limit and heartbeat.
|
||||
.Pp
|
||||
A source, sink, filter or format
|
||||
.Ar spec
|
||||
is a type followed by comma-separated
|
||||
is a type, then a colon and comma-separated
|
||||
.Ar key Ns = Ns Ar value
|
||||
pairs; a rate limit or heartbeat
|
||||
pairs; the first
|
||||
.Ql \&:
|
||||
ends the type, and a type alone takes the defaults.
|
||||
A rate limit or heartbeat
|
||||
.Ar spec
|
||||
has the pairs only:
|
||||
.Bd -literal -offset indent
|
||||
\-\-source file,directory=/var/log/app,pattern=*.log
|
||||
\-\-sink http,host=127.0.0.1,port=8080,id=web
|
||||
\-\-filter include,patterns=ERROR,patterns=WARN
|
||||
\-\-source file:directory=/var/log/app,pattern=*.log
|
||||
\-\-sink http:host=127.0.0.1,port=8080,id=web
|
||||
\-\-filter include:patterns=ERROR,patterns=WARN
|
||||
\-\-format json
|
||||
\-\-rate\-limit rate=100,burst=200,policy=drop
|
||||
\-\-rate\-limit entries_per_second=100,burst_entries=200,policy=drop
|
||||
.Ed
|
||||
.Pp
|
||||
Keys are dotted paths into the plugin's options, such as
|
||||
@@ -366,6 +369,8 @@ and
|
||||
inside a value; quote such a
|
||||
.Ar spec
|
||||
so the shell keeps the backslash.
|
||||
A colon in a value needs no escape, as in
|
||||
.Cm host=::1 .
|
||||
.Cm id= Ns Ar name
|
||||
names a source or sink; the default is its type, then
|
||||
.Ar type Ns _2 ,
|
||||
@@ -377,7 +382,7 @@ ignore the pipeline variables in
|
||||
.Sx ENVIRONMENT .
|
||||
.Ss Presets
|
||||
A preset builds a common pipeline from a few keys:
|
||||
.Fl \-preset Ar name Ns Op , Ns Ar key Ns = Ns Ar value ... ,
|
||||
.Fl \-preset Ar name Ns Op : Ns Ar key Ns = Ns Ar value ... ,
|
||||
in the syntax of the other specifications;
|
||||
.Nm
|
||||
.Cm preset Ar name Op Fl \- Ns Ar key Ar value ...
|
||||
@@ -392,6 +397,16 @@ A
|
||||
.Ar path
|
||||
is a file, a directory, whose files are followed, or a glob; without one, a
|
||||
preset that takes it reads standard input.
|
||||
.Cm user
|
||||
names one SCRAM user, an edge's login or a listener's only user in place of
|
||||
.Cm users ;
|
||||
.Cm password_file
|
||||
holds its password, read once at startup when it is
|
||||
.Pa /dev/fd/ Ns Ar N
|
||||
or a pipe, and defaults to
|
||||
.Pa /dev/tty ,
|
||||
which asks at startup; on a listener, Enter alone generates a password and
|
||||
shows it there.
|
||||
.Bl -tag -width Ds
|
||||
.It Cm pipe Op Cm format
|
||||
Standard input to standard output, the pipeline
|
||||
@@ -400,7 +415,7 @@ runs without a configuration.
|
||||
.It Cm tail path Op Cm from , format
|
||||
Follow files to standard output, like
|
||||
.Ql tail \-F .
|
||||
.It Cm serve Oo Cm path , listen , format , tls , users , proxy , viewer , allow , deny Oc
|
||||
.It Cm serve Oo Cm path , listen , format , tls , users , user , password_file , proxy , viewer , allow , deny Oc
|
||||
Serve a live HTTP stream on
|
||||
.Cm listen
|
||||
.Pq default Li 127.0.0.1:8080 ,
|
||||
@@ -416,9 +431,13 @@ or
|
||||
.Cm files
|
||||
.Pq Cm cert No and Cm key ;
|
||||
.Cm users
|
||||
names a credentials file, so readers log in with SCRAM.
|
||||
A browser at the root gets a live viewer; with
|
||||
.Cm users ,
|
||||
names a credentials file, or
|
||||
.Cm user
|
||||
one user, so readers log in with SCRAM, which needs
|
||||
.Cm tls
|
||||
or
|
||||
.Cm proxy .
|
||||
A browser at the root gets a live viewer; with a user,
|
||||
only behind a TLS-terminating proxy named by
|
||||
.Cm proxy ,
|
||||
with
|
||||
@@ -437,14 +456,12 @@ or, with
|
||||
.Cm transport=http ,
|
||||
.Cm http_chain .
|
||||
It refuses to run without credentials:
|
||||
.Cm user
|
||||
and
|
||||
.Cm password_file ,
|
||||
.Cm user ,
|
||||
or a client
|
||||
.Cm cert
|
||||
and
|
||||
.Cm key .
|
||||
.It Cm aggregator Oo Cm listen , transport , tls , users , client_ca , out , format , allow , deny Oc
|
||||
.It Cm aggregator Oo Cm listen , transport , tls , users , user , password_file , client_ca , out , format , allow , deny Oc
|
||||
Receive from edges on
|
||||
.Cm listen
|
||||
.Pq default Li 0.0.0.0:9000
|
||||
@@ -452,7 +469,9 @@ over TLS, never off
|
||||
.Pq default Cm tls=self ,
|
||||
authenticated by the
|
||||
.Cm users
|
||||
credentials file or the client certificates
|
||||
credentials file, the one
|
||||
.Cm user ,
|
||||
or the client certificates
|
||||
.Cm client_ca
|
||||
vouches for, and write to standard output or to files in
|
||||
.Cm out ;
|
||||
@@ -534,7 +553,7 @@ one keeps its owner and mode.
|
||||
.It Cm remove-user Fl \-credentials Ar file Fl u Ar name
|
||||
Remove a user.
|
||||
The last user cannot be removed.
|
||||
.It Cm token Fl \-url Ar https://host:port Fl u Ar name Fl \-password\-file Ar file Oo Ar tls-flags Oc Op Fl \-unbound
|
||||
.It Cm token Fl \-url Ar https://host:port Fl u Ar name Oo Fl \-password\-file Ar file Oc Oo Ar tls-flags Oc Op Fl \-unbound
|
||||
Log in to an
|
||||
.Cm http
|
||||
sink or
|
||||
@@ -544,12 +563,21 @@ source and print a bearer token.
|
||||
logs in without channel binding, to an
|
||||
.Cm http
|
||||
sink behind a TLS-terminating proxy; the URL may then carry the proxy's path.
|
||||
.It Cm stream Fl \-addr Ar host:port Fl u Ar name Fl \-password\-file Ar file Op Ar tls-flags
|
||||
.It Cm stream Fl \-addr Ar host:port Fl u Ar name Oo Fl \-password\-file Ar file Oc Op Ar tls-flags
|
||||
Log in to a
|
||||
.Cm tcp
|
||||
sink and copy its stream to standard output until interrupted.
|
||||
.El
|
||||
.Pp
|
||||
For
|
||||
.Cm token
|
||||
and
|
||||
.Cm stream ,
|
||||
.Fl \-password\-file
|
||||
defaults to
|
||||
.Pa /dev/tty ,
|
||||
where the password is asked without echo.
|
||||
.Pp
|
||||
The
|
||||
.Ar tls-flags
|
||||
are
|
||||
@@ -627,7 +655,8 @@ is looked for.
|
||||
.Pp
|
||||
Secrets never go in the environment: TLS keys, credentials files and
|
||||
password files are named by path
|
||||
.Pq Cm tls.key_file , auth.credentials_file , auth.password_file .
|
||||
.Pq Cm tls.key_file , auth.credentials_file , auth.password_file ,
|
||||
or asked on the terminal.
|
||||
.Sh FILES
|
||||
Without
|
||||
.Fl c
|
||||
@@ -728,7 +757,7 @@ $ lw < app.log > copy.log
|
||||
.Pp
|
||||
Follow a log and keep only errors and warnings:
|
||||
.Bd -literal -offset indent
|
||||
$ tail \-F app.log | lw \-\-filter include,patterns=ERROR,patterns=WARN
|
||||
$ tail \-F app.log | lw \-\-filter include:patterns=ERROR,patterns=WARN
|
||||
.Ed
|
||||
.Pp
|
||||
Prefix each line with the time, the level found in it and the source:
|
||||
@@ -743,7 +772,7 @@ sink listens on every address by default, and read it from another terminal
|
||||
as server-sent events; a client gets the lines that arrive while it is
|
||||
connected:
|
||||
.Bd -literal -offset indent
|
||||
$ journalctl \-f | lw \-\-sink http,host=127.0.0.1,port=8080
|
||||
$ journalctl \-f | lw \-\-sink http:host=127.0.0.1,port=8080
|
||||
$ curl \-N http://127.0.0.1:8080/stream
|
||||
.Ed
|
||||
.Pp
|
||||
@@ -753,19 +782,19 @@ file in a directory, as
|
||||
.Ql tail \-F
|
||||
does, and write the new lines unparsed to one file:
|
||||
.Bd -literal -offset indent
|
||||
$ lw \-\-source 'file,directory=/var/log/app,pattern=*.log,raw=true' \e
|
||||
$ lw \-\-source 'file:directory=/var/log/app,pattern=*.log,raw=true' \e
|
||||
> all.log
|
||||
.Ed
|
||||
.Pp
|
||||
Follow every file in a directory, with the level names in color on a
|
||||
terminal:
|
||||
.Bd -literal -offset indent
|
||||
$ lw \-\-preset tail,path=/var/log/app
|
||||
$ lw \-\-preset tail:path=/var/log/app
|
||||
.Ed
|
||||
.Pp
|
||||
Print the configuration a set of options makes, to keep as a file:
|
||||
.Bd -literal -offset indent
|
||||
$ lw \-\-preset serve,path=/var/log/app,tls=self \-\-dump > serve.toml
|
||||
$ lw \-\-preset serve:path=/var/log/app,tls=self \-\-dump > serve.toml
|
||||
.Ed
|
||||
.Pp
|
||||
Ship a directory from an edge to an aggregator: on the aggregator, create
|
||||
@@ -777,13 +806,22 @@ and the password file to the edge; then run both:
|
||||
# lw tls cert \-\-ca-dir /etc/logwisp/pki \-\-name agg.example.org \-\-server
|
||||
# lw auth add-user \-\-credentials /etc/logwisp/users.toml \e
|
||||
\-\-user edge-01 \-\-password-file /root/edge-01.pass
|
||||
agg# lw \-\-preset aggregator,tls=files,users=/etc/logwisp/users.toml,\e
|
||||
agg# lw \-\-preset aggregator:tls=files,users=/etc/logwisp/users.toml,\e
|
||||
cert=/etc/logwisp/pki/agg.example.org.crt,\e
|
||||
key=/etc/logwisp/pki/agg.example.org.key,out=/var/log/edges
|
||||
edge# lw \-\-preset edge,path=/var/log/app,to=agg.example.org:9000,\e
|
||||
edge# lw \-\-preset edge:path=/var/log/app,to=agg.example.org:9000,\e
|
||||
ca=/etc/logwisp/pki/ca.crt,user=edge-01,password_file=/root/edge-01.pass
|
||||
.Ed
|
||||
.Pp
|
||||
A temporary pipe with nothing on disk: the receiver makes a self-signed
|
||||
certificate, logs its pin, and generates the password when Enter alone
|
||||
answers its prompt; the sender pins the certificate and is asked for the
|
||||
password:
|
||||
.Bd -literal -offset indent
|
||||
recv$ lw \-p aggregator:listen=0.0.0.0:9000,user=pipe,format=raw
|
||||
send$ cmd | lw \-p edge:to=RECEIVER:9000,pin=sha256//BASE64,user=pipe
|
||||
.Ed
|
||||
.Pp
|
||||
Write the annotated configuration to the file
|
||||
.Nm
|
||||
reads without
|
||||
|
||||
+6
-6
@@ -7,7 +7,7 @@ Running, monitoring, and maintaining LogWisp.
|
||||
```bash
|
||||
# a filter: stdin to stdout, exits 0 at the end of input
|
||||
lw < app.log > copy.log
|
||||
tail -F app.log | lw --filter include,patterns=ERROR
|
||||
tail -F app.log | lw --filter include:patterns=ERROR
|
||||
|
||||
# a service: the file's pipelines, until SIGINT or SIGTERM
|
||||
lw -c /etc/logwisp/logwisp.toml
|
||||
@@ -249,16 +249,16 @@ client_buffer_size = 1024
|
||||
|
||||
```toml
|
||||
[pipelines.flow.rate_limit]
|
||||
rate = 1000.0
|
||||
burst = 2000.0
|
||||
entries_per_second = 1000.0
|
||||
burst_entries = 2000.0
|
||||
policy = "drop"
|
||||
max_entry_size_bytes = 65536
|
||||
```
|
||||
|
||||
Two behaviours to keep in mind: the limiter does not exist at all when
|
||||
`rate <= 0`, and `policy = "pass"` short-circuits the size cap as well as the
|
||||
rate check. Enforcing `max_entry_size_bytes` therefore requires `rate > 0` and
|
||||
`policy = "drop"`.
|
||||
`entries_per_second <= 0`, and `policy = "pass"` short-circuits the size cap as
|
||||
well as the rate check. Enforcing `max_entry_size_bytes` therefore requires
|
||||
`entries_per_second > 0` and `policy = "drop"`.
|
||||
|
||||
### Formatting
|
||||
|
||||
|
||||
+33
-14
@@ -15,8 +15,10 @@ next to the existing certificate method, without weakening what mTLS gives.
|
||||
|
||||
- Password authentication for chain links (`tcp_chain`, `http_chain`: sources
|
||||
verify, sinks present) and for viewers of the `tcp` and `http` sinks.
|
||||
- No password or password-equivalent on the wire, no KDF work on listeners, and
|
||||
no credential relay through a TLS-terminating MITM.
|
||||
- No password or password-equivalent on the wire, no KDF work per login on
|
||||
listeners, and no credential relay through a TLS-terminating MITM.
|
||||
- A temporary pipe needs no file: one listener user from a descriptor, a pipe
|
||||
or the terminal, with a certificate made at startup.
|
||||
- Default stays `auth.type = "none"`; existing configurations are unaffected.
|
||||
- Config in TOML, credential management and viewer clients in a `lw auth`
|
||||
CLI.
|
||||
@@ -66,7 +68,7 @@ curl by the CLI is unpinned.
|
||||
# listener (tcp_chain/http_chain source, tcp/http sink)
|
||||
[pipelines.plugin_sources.config.auth]
|
||||
type = "scram"
|
||||
credentials_file = "/etc/logwisp/users.toml"
|
||||
credentials_file = "/etc/logwisp/users.toml" # or username + password_file
|
||||
token_lifetime_ms = 900000 # http sink and http_chain source only
|
||||
node_binding = "force" # chain sources; binds to the username
|
||||
|
||||
@@ -81,12 +83,20 @@ Validation at construction:
|
||||
|
||||
- `scram` requires TLS, except on an `http` sink in proxy mode; a dialer also
|
||||
forbids `insecure_skip_verify`.
|
||||
- Listeners require `credentials_file` and reject `allow`/`allow_patterns` (the
|
||||
credentials file is the allow list), `username` and `password_file`. Use one
|
||||
file per listener when listeners admit different users.
|
||||
- Dialers require `username` + `password_file` (1–1024 bytes after trimming one
|
||||
line break), and reject `credentials_file`, `token_lifetime_ms`, `allow*` and
|
||||
`identity` (server pinning stays an mTLS feature).
|
||||
- Listeners require `credentials_file`, or `username` and `password_file`
|
||||
(one user, its verifier derived in memory at construction), not both, and
|
||||
reject `allow`/`allow_patterns`: the users are the allow list. Use one file
|
||||
per listener when listeners admit different users.
|
||||
- Dialers require `username` + `password_file`, and reject `credentials_file`,
|
||||
`token_lifetime_ms`, `allow*` and `identity` (server pinning stays an mTLS
|
||||
feature).
|
||||
- A password is 1–1024 bytes after trimming one line break, 8 or more on a
|
||||
listener. A regular file is read at every construction; `/dev/fd/N` (the
|
||||
inherited descriptor, then closed), a pipe, a FIFO or another device once, at
|
||||
startup, and kept for reloads; a terminal is asked once, at startup, without
|
||||
echo, and on a listener Enter alone generates a password shown only there.
|
||||
The open file decides, not stat: FreeBSD stats `/dev/stdin` as a device
|
||||
whatever it holds. No terminal fails closed.
|
||||
- `identity` on a listener requires `tls.client_auth` and **binds the certificate
|
||||
to the user**: the certificate's identity field must equal the SCRAM username,
|
||||
so a peer needs its own certificate and its own password (PostgreSQL's
|
||||
@@ -207,8 +217,8 @@ closed.
|
||||
```
|
||||
lw auth add-user --credentials F --user U [--password-file P] [--generate]
|
||||
lw auth remove-user --credentials F --user U
|
||||
lw auth token --url https://host:port[/path] --user U --password-file P [--unbound] [TLS flags]
|
||||
lw auth stream --addr host:port --user U --password-file P [TLS flags]
|
||||
lw auth token --url https://host:port[/path] --user U [--password-file P] [--unbound] [TLS flags]
|
||||
lw auth stream --addr host:port --user U [--password-file P] [TLS flags]
|
||||
```
|
||||
|
||||
- `add-user` takes the password from `--password-file` when it exists (at least
|
||||
@@ -224,7 +234,7 @@ lw auth stream --addr host:port --user U --password-file P [TLS flags]
|
||||
- `token` prints a bearer token for curl (`-H @<(...)` keeps it out of argv);
|
||||
`stream` authenticates to a `tcp` sink and copies the stream to stdout until
|
||||
interrupted. `--url` takes a path only with `--unbound`, and redirects are not
|
||||
followed.
|
||||
followed. `--password-file` defaults to `/dev/tty`, which asks.
|
||||
- TLS flags: `--ca-file` or `--pin-sha256`, `--server-name`, `--cert-file`,
|
||||
`--key-file`. There is no insecure flag.
|
||||
- Exit status: 0 success, 1 failure, 2 usage error.
|
||||
@@ -340,7 +350,11 @@ a challenge below its Argon2 floor; the HTTP exchange on a TLS test server
|
||||
(token accepted, garbage and foreign tokens `401`, forged final yields no token,
|
||||
a different certificate after the challenge refused before the proof is sent,
|
||||
renewal ahead of expiry); `Authorize` failing closed under SCRAM; password
|
||||
trimming; refused hellos freeing their slot; unbound logins pinned too. Proxy
|
||||
trimming; a one-user listener admitting its password, refusing another and an
|
||||
unknown user alike; a descriptor's password surviving a reload, closed and
|
||||
refused to a second user; a descriptor lw opened itself never read; refused
|
||||
hellos freeing their slot; unbound logins pinned too. In `tlsx`, terminals,
|
||||
pipes and descriptors never warned as world-readable. Proxy
|
||||
mode: trusted peers, https and the client hop; throttling on the forwarded
|
||||
client and IPv6 /64; an unbound browser login with the cookie's attributes, the
|
||||
cookie opening endpoints (beside Basic auth) until logout revokes it, cookie
|
||||
@@ -371,7 +385,12 @@ viewer's own form, not a loop, its token kept out of storage and the URL; a
|
||||
profile blocking every cookie signing in on the viewer, its proofs asking for
|
||||
no cookie, streaming with a bearer, sign-out revoking the token, and a reload's
|
||||
`401` asking again; `token --unbound` and curl through the proxy; direct peers
|
||||
and plaintext-forwarded requests `403`. The existing scripts keep passing.
|
||||
and plaintext-forwarded requests `403`. `test/preset-test.sh --auto` (ports
|
||||
15897-15898): an aggregator and an edge given one password on `/dev/fd/3`
|
||||
deliver, another password is refused, both survive `SIGHUP` and deliver again,
|
||||
and the password is in no file, argv or environment; under `script(1)` a
|
||||
listener's generated password shows on its terminal and not in its log, and
|
||||
an edge and `lw auth token` log in with it. The existing scripts keep passing.
|
||||
|
||||
## Not Implemented
|
||||
|
||||
|
||||
+68
-9
@@ -159,8 +159,8 @@ allow_patterns = [] # mtls
|
||||
node_binding = "force" # chain sources only
|
||||
credentials_file = "" # scram listeners
|
||||
token_lifetime_ms = 0 # scram, HTTP listeners; 10 s to 24 h, 0 = 15 minutes
|
||||
username = "" # scram dialers
|
||||
password_file = "" # scram dialers
|
||||
username = "" # scram dialers, or a listener's one user
|
||||
password_file = "" # scram, with username
|
||||
trusted_proxies = [] # scram http sink behind a TLS-terminating proxy
|
||||
```
|
||||
|
||||
@@ -181,7 +181,9 @@ Options by where they apply, each with its default:
|
||||
them yourself.
|
||||
- `scram` listeners
|
||||
- `credentials_file`: verifiers written by
|
||||
[`lw auth add-user`](cli.md#lw-auth).
|
||||
[`lw auth add-user`](cli.md#lw-auth); or, in its place, `username` and
|
||||
`password_file`, see
|
||||
[One user without a credentials file](#one-user-without-a-credentials-file).
|
||||
- `token_lifetime_ms` (15 minutes): bearer token lifetime, 10 s to 24 h; the
|
||||
`http` sink and `http_chain` source only.
|
||||
- `trusted_proxies` (`[]`): addresses or CIDRs of the reverse proxies that
|
||||
@@ -190,7 +192,7 @@ Options by where they apply, each with its default:
|
||||
- `scram` dialers
|
||||
- `username`: user to log in as.
|
||||
- `password_file`: file holding the password; one trailing line break is
|
||||
trimmed.
|
||||
trimmed. `/dev/fd/N`, a pipe or a terminal are read once, as on listeners.
|
||||
|
||||
**Roles by plugin**, and what the block decides:
|
||||
|
||||
@@ -306,9 +308,10 @@ Misconfiguration fails at plugin construction, before the pipeline starts:
|
||||
type forgotten
|
||||
- a key of the other method: `allow` or `allow_patterns` under `scram`, a
|
||||
`scram` key under `mtls`
|
||||
- a `scram` listener without a loadable `credentials_file` (see
|
||||
[Credentials file](#credentials-file)), with `username` or `password_file`,
|
||||
with a `token_lifetime_ms` outside 10 s to 24 h or on a TCP listener, or with
|
||||
- a `scram` listener with neither a loadable `credentials_file` (see
|
||||
[Credentials file](#credentials-file)) nor `username` and `password_file`,
|
||||
or with both, with only one of `username` and `password_file`, with a
|
||||
`token_lifetime_ms` outside 10 s to 24 h or on a TCP listener, or with
|
||||
`identity` but no `tls.client_auth`
|
||||
- a `scram` dialer without `username` and `password_file`, with
|
||||
`credentials_file`, `token_lifetime_ms`, `trusted_proxies` or `identity`
|
||||
@@ -435,8 +438,8 @@ when it is constructed, so startup and every reload repeat them:
|
||||
- an `allow_patterns` entry not anchored at both ends of every alternative
|
||||
(`^a|b$` admits `a…` and `…b`)
|
||||
- a `key_file`, `issuer_key_file`, `credentials_file` or `password_file` every
|
||||
local user can read
|
||||
(once per path per process)
|
||||
local user can read (once per path per process); terminals, pipes and
|
||||
descriptors hold nothing at rest and are not reported
|
||||
- an `acl` entry with host bits set (`10.0.0.1/8`), naming the network it
|
||||
matches; an `acl.allow` entry of a whole family (`0.0.0.0/0`, `::/0`); a
|
||||
wildcard listener whose `acl` has `deny` but no `allow`; an `acl.proxy_from`
|
||||
@@ -477,6 +480,18 @@ lw tls cert --ca-dir /etc/logwisp/pki --name agg.example.org --server
|
||||
lw tls cert --ca-dir /etc/logwisp/pki --name edge-01 --client
|
||||
```
|
||||
|
||||
A temporary pipe needs no file at all: a self-signed certificate and
|
||||
[one user](#one-user-without-a-credentials-file) whose password is typed, or
|
||||
generated, at startup. Both last until lw exits.
|
||||
|
||||
```bash
|
||||
# receiver: Enter at the prompt generates the password; copy it, and the
|
||||
# pin_sha256 of the WARN line (-q would hide it)
|
||||
lw -p aggregator:listen=0.0.0.0:9000,tls=self,user=pipe,format=raw,allow=SENDER/32
|
||||
# sender: asked for the password
|
||||
cmd | lw -p edge:to=RECEIVER:9000,pin=sha256//PIN,user=pipe
|
||||
```
|
||||
|
||||
## Enabling mTLS
|
||||
|
||||
### 1. Generate a CA and certificates
|
||||
@@ -702,6 +717,50 @@ only when the plugin is built and `auto_reload` does not watch it: send
|
||||
`SIGHUP` after every change. Use one file per listener when listeners admit
|
||||
different users.
|
||||
|
||||
### One user without a credentials file
|
||||
|
||||
`username` and `password_file` on a listener replace `credentials_file`: one
|
||||
user whose verifier is derived in memory when the plugin is built (one
|
||||
Argon2id at startup and each reload, never per login), with a decoy key drawn
|
||||
once per process. Nothing is written. It suits a temporary pipe or a single
|
||||
edge. `password_file` is read by what it names, on either side:
|
||||
|
||||
- A regular file: at every build, so `SIGHUP` rotates it. A systemd
|
||||
`LoadCredential=` file sits on tmpfs.
|
||||
- `/dev/fd/N`, a pipe, a FIFO or another device: once, at startup, then kept
|
||||
in memory for reloads; restart lw to change it. A reload naming a new one
|
||||
fails and the running pipelines stay: a console source may be reading the
|
||||
same input, and the read would hold the reload and its signals.
|
||||
- `/dev/fd/N` is the descriptor lw was given (`3< <(pass show lw/pipe)`),
|
||||
read and closed; it needs no fdescfs on FreeBSD. A descriptor lw opened
|
||||
itself is refused.
|
||||
- One such source holds one user's password. `/dev/stdin` would take the
|
||||
console source's input.
|
||||
- A terminal: `/dev/tty`, the presets' default with `user`, asked once, at
|
||||
startup, without echo. lw asks whatever file is a terminal once opened, so
|
||||
`/dev/stdin` holding a pipe is read on FreeBSD too, where stat calls it a
|
||||
device.
|
||||
- On a listener, Enter alone generates a 130-bit password and shows it once
|
||||
on that terminal, never in the log or on stderr: logs persist, are
|
||||
forwarded, and stderr is often a file.
|
||||
- With no terminal (systemd, cron) the plugin fails. `lw ... &` stops on
|
||||
`SIGTTIN` at the prompt.
|
||||
|
||||
Trade-offs:
|
||||
|
||||
- The password lives as long as the process, as dialers' passwords do:
|
||||
`tcp_chain` logs in again on every reconnect and `http_chain` at every token
|
||||
renewal, so it cannot be single-use.
|
||||
- A generated password stays in terminal scrollback, tmux or screen logs, and
|
||||
the clipboard that carries it. A typed one may be as short as 8 bytes; on an
|
||||
exposed listener prefer the generated one and `acl.allow`.
|
||||
- It never goes in argv or the environment: `/proc/PID/cmdline` is
|
||||
world-readable, and `--dump` keeps only the path.
|
||||
- Each build draws a new salt, which, like a rotation, shows a prober that the
|
||||
name exists.
|
||||
- Older bash and zsh back a here-string (`<<<`) with a temporary file; prefer
|
||||
`3< <(...)`.
|
||||
|
||||
### Rollout, rotation and revocation
|
||||
|
||||
- **Rollout.** Turning `scram` on cuts off every dialer of that listener that
|
||||
|
||||
+6
-5
@@ -173,11 +173,12 @@ Options, each as type and default:
|
||||
- `replay_lines` (int, `0`; `1000` from the `serve` preset): the last entries
|
||||
a new stream gets after its `connected` event, before the live ones, so a
|
||||
browser opened late sees a backlog; `0` = none.
|
||||
- `login_page` (bool, `false`): `scram` in proxy mode only (an error
|
||||
elsewhere): the browser login page at `/auth/login`.
|
||||
- `viewer_page` (bool, `false`): `scram` in proxy mode only (an error
|
||||
elsewhere): the live viewer at `/auth/view`; needs `login_page`. Without
|
||||
auth, or under `mtls`, the viewer is always served.
|
||||
- `login_page` (bool, `false`): only for `scram` behind
|
||||
`auth.trusted_proxies`, where browsers log in (an error elsewhere): the
|
||||
login page at `/auth/login`.
|
||||
- `viewer_page` (bool, `false`): the same, the live viewer at `/auth/view`
|
||||
after a login; needs `login_page`. Without auth, or under `mtls`, the
|
||||
viewer is always at `/`, with neither key.
|
||||
- `tls` (table): listener TLS; see [Security](security.md).
|
||||
- `auth` (table): client authentication (`mtls` or `scram`); see
|
||||
[Security](security.md#the-auth-block).
|
||||
|
||||
+2
-1
@@ -227,7 +227,8 @@ Options, each as type and default:
|
||||
[Security](security.md#the-acl-block).
|
||||
|
||||
For passwords instead of certificates, the `auth` block takes `type = "scram"`
|
||||
and a `credentials_file`, and `client_auth` becomes optional; see
|
||||
and a `credentials_file`, or one `username` with its `password_file`, and
|
||||
`client_auth` becomes optional; see
|
||||
[Password Authentication](security.md#password-authentication-scram).
|
||||
|
||||
**Behaviour**
|
||||
|
||||
+6
-2
@@ -102,7 +102,9 @@ What exists:
|
||||
auth and acl tables, sources, sinks and presets, as one JSON document.
|
||||
- `github.com/lixenwraith/terminal` (pinned) and its `tui` package: regions,
|
||||
layout, boxes, List, Tree, TabBar, StatusBar, TextField, Form, Modal,
|
||||
ConfirmDialog, scrolling, mouse. `github.com/lixenwraith/color`: RGB,
|
||||
ConfirmDialog, scrolling, mouse; themed Radio, Field, TextInput,
|
||||
OptionList and menus, Frame, Rule, Wires and Window, which `lw --tui`
|
||||
draws with. `github.com/lixenwraith/color`: RGB,
|
||||
blending, RGBTo256, RGBTo16.
|
||||
|
||||
Steps:
|
||||
@@ -112,7 +114,9 @@ Steps:
|
||||
4. `lixenwraith/terminal` v0.2.0: done: 16 and no color, the terminal's
|
||||
default colors, `/dev/tty`, bracketed paste, themed form controls. Still
|
||||
optional, in `color`: export the 16 xterm reference shades (RGBTo16 maps
|
||||
the site's hues badly, so the 16-color tier is chosen by hand).
|
||||
the site's hues badly, so the 16-color tier is chosen by hand). v0.3.0:
|
||||
radio buttons, narrow form fields and option lists, menus, frames, rules,
|
||||
wires and windows, moved from `lw --tui`.
|
||||
5. `lw --tui`: done ([doc/cli.md](cli.md#lw---tui)).
|
||||
6. WebAssembly: done. `make wasm` builds `bin/lwconf.wasm`, version stamped,
|
||||
beside the toolchain's `wasm_exec.js`, and prints both checksums: about
|
||||
|
||||
Reference in new issue
Block a user