v0.22.0 several enhancements and fixes for security, network, doc, test, packaging
Test / test (push) Canceled after 0s
Test / e2e (push) Canceled after 0s

This commit is contained in:
lixen committed 2026-10-03 14:52:16 -04:00
1 parent 9ab8c356d2
commit 71d1b82f83
102 files changed
+7102 -2178

No files matched your search

+37 -32
View File
@@ -65,14 +65,13 @@ back to authenticated peers.
The authenticated identity is a single string derived from the peer's verified
leaf certificate. Because `tls.RequireAndVerifyClientCert` has already validated
the chain, signature, and validity window by the time we look, extraction is
pure field selection — `tlsx.PeerIdentity`.
pure field selection — `tlsx.PeerIdentity`. The `identity` modes:
| `identity` mode | Source | Notes |
|-----------------|--------|-------|
| `cn` (default) | `Subject.CommonName` | Matches the existing `tls_peer_cn` metadata |
| `san_dns` | first `DNSNames` entry | Preferred for host identities |
| `san_uri` | first `URIs` entry | SPIFFE-style IDs |
| `san_email` | first `EmailAddresses` entry | Operator identities |
- `cn` (default): `Subject.CommonName`, matching the existing `tls_peer_cn`
metadata.
- `san_dns`: the first `DNSNames` entry; preferred for host identities.
- `san_uri`: the first `URIs` entry; SPIFFE-style IDs.
- `san_email`: the first `EmailAddresses` entry; operator identities.
An empty identity is a rejection, not an empty match: a certificate with no
usable identity field cannot satisfy any policy.
@@ -96,13 +95,14 @@ allow_patterns = ["^edge-\\d{2}$"] # RE2, anchored by the author
node_binding = "force" # none | assert | force
```
| Option | Type | Default | Meaning |
|--------|------|---------|---------|
| `type` | string | `none` | `none` preserves pre-auth behaviour exactly; `mtls` enables the policy |
| `identity` | string | `cn` | Which certificate field is the identity |
| `allow` | []string | `[]` | Exact identity matches |
| `allow_patterns` | []string | `[]` | RE2 patterns matched against the identity |
| `node_binding` | string | `force` when `type = "mtls"` | Chain sources only; see below |
- `type`, string, default `none`: `none` preserves pre-auth behaviour
exactly; `mtls` enables the policy.
- `identity`, string, default `cn`: which certificate field is the identity.
- `allow`, []string, default `[]`: exact identity matches.
- `allow_patterns`, []string, default `[]`: RE2 patterns matched against the
identity.
- `node_binding`, string, default `force` when `type = "mtls"`: chain sources
only; see below.
Empty `allow` **and** empty `allow_patterns` under `type = "mtls"` means "any
identity the CA vouches for" — that is, the pre-auth behaviour, but with the
@@ -111,13 +111,15 @@ default rather than a silent deny-all, and the plugin logs a WARN at startup
saying so.
`node_binding` applies only to the chain sources, where a `node` label is
declared. Setting it on any other plugin is a configuration error.
declared. Setting it to anything but `none` on any other plugin is a
configuration error. Its values:
| Value | Connection label | Per-entry `node` field |
|-------|------------------|------------------------|
| `none` | `trust_node` governs, as before | `trust_node` governs |
| `assert` | Must equal the identity; a mismatch or an omission is rejected | `trust_node` governs |
| `force` | The declared label is ignored and the identity is used | Overwritten with the identity |
- `none`: `trust_node` governs the connection label, as before, and the
per-entry `node` field.
- `assert`: the connection label must equal the identity, and a mismatch or an
omission is rejected; `trust_node` governs the per-entry `node` field.
- `force`: the declared label is ignored and the identity is used; every
per-entry `node` field is overwritten with the identity.
The split between `assert` and `force` is what makes both worth having:
@@ -131,8 +133,9 @@ The split between `assert` and `force` is what makes both worth having:
it forwards keep the origin labels stamped at the first hop, so multi-hop
attribution survives.
`node_binding` overrides `trust_node`; when binding is active the constructor
logs that `trust_node` is being ignored.
`node_binding` overrides `trust_node` for the connection label. Under `force`
the constructor logs that `trust_node` is ignored; under `assert`, that it still
governs the per-entry labels.
Dialer-side plugins (`tcp_chain` and `http_chain` sinks) accept the same block
to pin the *server's* identity beyond hostname verification. There
@@ -161,7 +164,8 @@ One small package, mirroring `internal/tlsx`, is the single seam between the
against the sibling TLS config and returns `(nil, nil)` when auth is disabled;
every method tolerates a nil receiver, so call sites never branch on config.
Listeners admit only through `Admit` (TCP) and `AuthorizeRequest` (HTTP); the
current API, including SCRAM, is in [scram-auth-plan.md](scram-auth-plan.md#the-authz-seam).
current API, including SCRAM, is in
[scram-auth-plan.md](scram-auth-plan.md#the-authz-seam).
### Enforcement Points
@@ -169,18 +173,19 @@ current API, including SCRAM, is in [scram-auth-plan.md](scram-auth-plan.md#the-
Handshake → `Admit` (authorize, then read hello) → `ResolveNode` → create
session. Under `mtls` the certificate is checked before the hello is read, so
an unauthorized peer never gets a preamble parsed on its behalf. `chain.DecodeEntry` is then
called with `auth.TrustsEntryNode(trust_node)` rather than `trust_node` itself.
an unauthorized peer never gets a preamble parsed on its behalf.
`chain.DecodeEntry` is then called with `auth.TrustsEntryNode(trust_node)`
rather than `trust_node` itself.
**`http_chain` source** (`internal/source/httpchain/httpchain.go`, `handleIngest`)
Per request, through `AuthorizeRequest`, before the body is read — an unauthorized sender does
not get to stream `max_body_bytes` into the process. Rejection is `403`,
distinct from the `400` used for protocol errors, so a sender can tell "you are
not allowed" from "your batch was malformed". `ResolveNode` then governs the
`X-Logwisp-Node` header exactly as it governs the TCP hello. The session cache
key includes the identity, so two peers sharing a remote address never share a
session.
Per request, through `AuthorizeRequest`, before the body is read — an
unauthorized sender does not get to stream `max_body_bytes` into the process.
Rejection is `403`, distinct from the `400` used for protocol errors, so a
sender can tell "you are not allowed" from "your batch was malformed".
`ResolveNode` then governs the `X-Logwisp-Node` header exactly as it governs the
TCP hello. The session cache key includes the identity, so two peers sharing a
remote address never share a session.
**`tcp` sink** (`internal/sink/tcp/tcp.go`, `handleConn`)