docs: fix genuine passive-voice hits in docs/swarm
Read all 94 write-good.Passive hits across docs/swarm/ (ca.md, README.md, secrets.md, services.md, sso.md, ui.md) in context. 44 are genuine catches with a nameable, usually already-established actor (swarm-controller, authelia, swarmctl, the controller, the gateway, this module, hyperhive itself, or 'the operator' for manual actions) — rewritten to active. 50 are legitimate passives or false catches, left alone: predicate-adjective state descriptions (is expected/misconfigured/ broken), negative-capability idioms (no X is needed/placed, can't be Yed/listed/fetched), config-state conditionals (whenever/when X is enabled/configured/set), requirement-list labels (is required), 'is tracked as' idiom, backward-looking changelog facts with no actor (was removed/verified/introduced), ambiguous-actor statements left conservatively alone (agents are created and destroyed — could be hive-c0re or swarm-controller, doc doesn't say), and a couple of deliberately-parallel idiom pairs. Several sibling-inconsistency fixes: a passive clause sitting next to an already-active sibling describing the same fact/mechanism (ca.md's two-bullet consumer list, README's 4-item WireGuard-mesh bullet list, README's controller-registers-hooks paragraph, sso.md's followed-a-302 sentence). Verified via vale on the whole directory, diffed against main's exact baseline (not just the Passive count): write-good.Passive 94 -> 50 exactly, every other category unchanged (1 pre-existing Microsoft.Contractions error at services... at secrets.md:182, 8 TooWordy, 1 Microsoft.We, 1 Microsoft.FirstPerson — same counts, same locations).
This commit is contained in:
parent
e9d59f3d10
commit
fab2a0dedc
6 changed files with 79 additions and 78 deletions
|
|
@ -41,7 +41,7 @@ services.hyperhive = {
|
||||||
```
|
```
|
||||||
|
|
||||||
`swarm.domain` and `hiveName` are **required** whenever hyperhive is
|
`swarm.domain` and `hiveName` are **required** whenever hyperhive is
|
||||||
enabled; eval fails with a hint naming each. Neither is defaulted,
|
enabled; eval fails with a hint naming each. Neither defaults,
|
||||||
because a guessed value here is a wrong hostname that evaluates cleanly
|
because a guessed value here is a wrong hostname that evaluates cleanly
|
||||||
and deploys — an eval failure asking the operator to write the address
|
and deploys — an eval failure asking the operator to write the address
|
||||||
down is the cheaper outcome. **Upgrading past this release means setting
|
down is the cheaper outcome. **Upgrading past this release means setting
|
||||||
|
|
@ -55,9 +55,9 @@ the other hives read — `swarm.hives.edge.domain = "edge.elsewhere.example";`.
|
||||||
|
|
||||||
Setting `services.hyperhive.domain` directly still works and still wins,
|
Setting `services.hyperhive.domain` directly still works and still wins,
|
||||||
with a **deprecation warning**. The reason it's deprecated isn't tidiness:
|
with a **deprecation warning**. The reason it's deprecated isn't tidiness:
|
||||||
that option is local to one host, the directory is copied to every host,
|
that option is local to one host, and the operator copies the directory
|
||||||
so a value written only there leaves every peer pointing somewhere else
|
to every host, so a value written only there leaves every peer pointing
|
||||||
with nothing detecting the disagreement.
|
somewhere else with nothing detecting the disagreement.
|
||||||
|
|
||||||
⚠️ **Upgrading:** a hive that has been running on `swarm.domain` +
|
⚠️ **Upgrading:** a hive that has been running on `swarm.domain` +
|
||||||
`hiveName` alone now needs its own directory entry —
|
`hiveName` alone now needs its own directory entry —
|
||||||
|
|
@ -81,8 +81,8 @@ and `qualify()` / `qualified_label()` semantics.
|
||||||
## Swarm CA
|
## Swarm CA
|
||||||
|
|
||||||
A hive's internal TLS chains to a **swarm root CA**, so a peer that
|
A hive's internal TLS chains to a **swarm root CA**, so a peer that
|
||||||
trusts the root validates every hive in the swarm rather than being
|
trusts the root validates every hive in the swarm rather than pinning
|
||||||
pinned to each one by hand. Provisioning modes, what to hand a peer
|
to each one by hand. Provisioning modes, what to hand a peer
|
||||||
(`trust-bundle.pem`, never `ca.pem`), the name constraints on a hive
|
(`trust-bundle.pem`, never `ca.pem`), the name constraints on a hive
|
||||||
CA, and how an existing hive adopts the hierarchy: [`ca.md`](ca.md).
|
CA, and how an existing hive adopts the hierarchy: [`ca.md`](ca.md).
|
||||||
|
|
||||||
|
|
@ -107,8 +107,8 @@ which of the three topologies makes it the operator's job to place:
|
||||||
## Swarm UI
|
## Swarm UI
|
||||||
|
|
||||||
The operator-only web surface on the swarm apex, why reaching it needs
|
The operator-only web surface on the swarm apex, why reaching it needs
|
||||||
the `admins` group rather than just a session, and the four sites a
|
the `admins` group rather than just a session, and the four sites you
|
||||||
swarm service name has to be wired into: [`ui.md`](ui.md).
|
wire a swarm service name into: [`ui.md`](ui.md).
|
||||||
|
|
||||||
## The swarm's hive directory
|
## The swarm's hive directory
|
||||||
|
|
||||||
|
|
@ -249,15 +249,15 @@ services.hyperhive = {
|
||||||
|
|
||||||
### What the mesh does
|
### What the mesh does
|
||||||
|
|
||||||
- `networking.wireguard.interfaces.wg-hive` is configured on the host
|
- hyperhive configures `networking.wireguard.interfaces.wg-hive` on the
|
||||||
(not inside agent containers; containers reach peers via the host's
|
host (not inside agent containers; containers reach peers via the
|
||||||
routing table).
|
host's routing table).
|
||||||
- UDP port 51820 (or `listenPort`) is opened on the host firewall.
|
- It opens UDP port 51820 (or `listenPort`) on the host firewall.
|
||||||
- `swarm-wireguard.nix` reads each entry's `wireguardAddress` directly
|
- `swarm-wireguard.nix` reads each entry's `wireguardAddress` directly
|
||||||
from `services.hyperhive.swarm.peerHives` to build `wg-hive`'s
|
from `services.hyperhive.swarm.peerHives` to build `wg-hive`'s
|
||||||
`allowedIPs`, so intra-swarm traffic can route over the mesh address
|
`allowedIPs`, so intra-swarm traffic can route over the mesh address
|
||||||
rather than the public domain.
|
rather than the public domain.
|
||||||
- `persistentKeepalive = 25` is set by default; override or null to
|
- It sets `persistentKeepalive = 25` by default; override or null to
|
||||||
disable (not needed when both sides have public IPs and no NAT).
|
disable (not needed when both sides have public IPs and no NAT).
|
||||||
|
|
||||||
### NAT / one-sided endpoints
|
### NAT / one-sided endpoints
|
||||||
|
|
@ -310,7 +310,7 @@ below. A hive without them reads
|
||||||
| `fresh` | nothing — reported within `staleAfterSeconds` |
|
| `fresh` | nothing — reported within `staleAfterSeconds` |
|
||||||
| `stale` | the hive stopped reporting. Its last payload is still shown, so check `age_seconds` and the payload for what it managed to say |
|
| `stale` | the hive stopped reporting. Its last payload is still shown, so check `age_seconds` and the payload for what it managed to say |
|
||||||
| `never_reported` | this hive has never reported at all — normally a deployment that hasn't happened, not an outage |
|
| `never_reported` | this hive has never reported at all — normally a deployment that hasn't happened, not an outage |
|
||||||
| `unknown` | something is publishing under a name that's not in `swarm.hives` — a typo in the roster, or a hive that was removed and is still running |
|
| `unknown` | something is publishing under a name that's not in `swarm.hives` — a typo in the roster, or a hive removed from the roster but still running |
|
||||||
|
|
||||||
Every row also carries `last_seen_unix` and `age_seconds` if you want to
|
Every row also carries `last_seen_unix` and `age_seconds` if you want to
|
||||||
apply your own threshold. The timestamp is the one the queue recorded on
|
apply your own threshold. The timestamp is the one the queue recorded on
|
||||||
|
|
@ -382,10 +382,11 @@ controller's — and **both are expected; don't delete either.** Removing
|
||||||
a hive's stops it acting on config PRs; removing the controller's just
|
a hive's stops it acting on config PRs; removing the controller's just
|
||||||
gets recreated on its next start.
|
gets recreated on its next start.
|
||||||
|
|
||||||
Nothing to configure. The hooks are registered only when this host also
|
Nothing to configure. The controller registers the hooks only when this
|
||||||
serves the swarm UI vhost — that's what publishes the endpoint, and a
|
host also serves the swarm UI vhost — that's what publishes the
|
||||||
hook the forge can't reach would collect failed deliveries while
|
endpoint, and a hook the forge can't reach would collect failed
|
||||||
looking healthy. The HMAC secret is generated on first start and kept
|
deliveries while looking healthy. The controller generates the HMAC
|
||||||
|
secret on first start and keeps it
|
||||||
(see [`docs/agent-lifecycle/persistence.md`](../agent-lifecycle/persistence.md)).
|
(see [`docs/agent-lifecycle/persistence.md`](../agent-lifecycle/persistence.md)).
|
||||||
|
|
||||||
To check it's working, push to `internal/knowledge` and look for
|
To check it's working, push to `internal/knowledge` and look for
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
A hive's internal TLS chains to a **swarm root CA**: the root signs each
|
A hive's internal TLS chains to a **swarm root CA**: the root signs each
|
||||||
hive's own CA, and that hive CA signs the gateway leaf. A peer that
|
hive's own CA, and that hive CA signs the gateway leaf. A peer that
|
||||||
trusts the root once validates every hive in the swarm, present and
|
trusts the root once validates every hive in the swarm, present and
|
||||||
future, instead of being pinned to each one by hand.
|
future, instead of pinning to each one by hand.
|
||||||
|
|
||||||
That's the whole point of the hierarchy — it turns per-peer trust from
|
That's the whole point of the hierarchy — it turns per-peer trust from
|
||||||
O(n²) hand-pinning into one anchor per swarm.
|
O(n²) hand-pinning into one anchor per swarm.
|
||||||
|
|
@ -55,8 +55,8 @@ able to issue, but not an operator-installed sub-CA in
|
||||||
by any other means. If you have arranged one, the warning is expected
|
by any other means. If you have arranged one, the warning is expected
|
||||||
and can be ignored. Otherwise either install the sub-CA, or pin the
|
and can be ignored. Otherwise either install the sub-CA, or pin the
|
||||||
names back under the hive domain — a supported migration, since the
|
names back under the hive domain — a supported migration, since the
|
||||||
sub-CA is constrained to the _configured_ names and the swarm root
|
sub-CA carries a name constraint on the _configured_ names and the
|
||||||
carries no name constraints at all.
|
swarm root carries no name constraints at all.
|
||||||
|
|
||||||
## Constraints on the material
|
## Constraints on the material
|
||||||
|
|
||||||
|
|
@ -89,9 +89,9 @@ host:
|
||||||
scp /var/lib/swarm-ca/root.pem <peer-host>:/var/lib/swarm-ca/root.pem
|
scp /var/lib/swarm-ca/root.pem <peer-host>:/var/lib/swarm-ca/root.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
That's the point of the hierarchy: the root is installed **once per
|
That's the point of the hierarchy: the operator installs the root
|
||||||
swarm host**, not once per peer, so a hive joining later needs no edit on
|
**once per swarm host**, not once per peer, so a hive joining later
|
||||||
the hives already running. A hive whose cert already chains to a public
|
needs no edit on the hives already running. A hive whose cert already chains to a public
|
||||||
CA has nothing to install — `peer-config` says so instead.
|
CA has nothing to install — `peer-config` says so instead.
|
||||||
|
|
||||||
Handing a peer this hive's own `ca.pem` would not work even as a
|
Handing a peer this hive's own `ca.pem` would not work even as a
|
||||||
|
|
@ -114,8 +114,8 @@ two cases.**
|
||||||
|
|
||||||
Adoption happens by itself, once. `hive-tls-ca.service` notices that
|
Adoption happens by itself, once. `hive-tls-ca.service` notices that
|
||||||
`ca.pem` doesn't chain to the root, keeps the old certificate as
|
`ca.pem` doesn't chain to the root, keeps the old certificate as
|
||||||
`ca-previous.pem`, and re-issues under the root; the next leaf is signed
|
`ca-previous.pem`, and re-issues under the root; the new CA signs the
|
||||||
by the new CA.
|
next leaf.
|
||||||
|
|
||||||
It's safe to automate here precisely because this is the
|
It's safe to automate here precisely because this is the
|
||||||
all-on-one-host shape: every consumer is on this box, so "when will they
|
all-on-one-host shape: every consumer is on this box, so "when will they
|
||||||
|
|
@ -142,7 +142,7 @@ systemctl restart hive-tls-ca.service
|
||||||
```
|
```
|
||||||
|
|
||||||
Failing rather than warning is deliberate: a hive whose CA doesn't
|
Failing rather than warning is deliberate: a hive whose CA doesn't
|
||||||
chain to the root it has been given is misconfigured, and a warning in a
|
chain to the root it received is misconfigured, and a warning in a
|
||||||
build log isn't something anyone reads twice.
|
build log isn't something anyone reads twice.
|
||||||
|
|
||||||
To keep the current CA on purpose — a hive that deliberately stays
|
To keep the current CA on purpose — a hive that deliberately stays
|
||||||
|
|
@ -162,12 +162,12 @@ beside the key under `swarm.ca.stateDir` — and that has a cost worth
|
||||||
naming, because it's subtle and it bites at a distance:
|
naming, because it's subtle and it bites at a distance:
|
||||||
|
|
||||||
> **Nothing whose trust store is assembled at build time can reference
|
> **Nothing whose trust store is assembled at build time can reference
|
||||||
> the swarm root.** `security.pki.certificateFiles` is read inside the
|
> the swarm root.** The derivation reads `security.pki.certificateFiles`
|
||||||
> derivation; the root doesn't exist there.
|
> inside itself; the root doesn't exist there.
|
||||||
|
|
||||||
Two consumers, and only one of them is fine:
|
Two consumers, and only one of them is fine:
|
||||||
|
|
||||||
- **Agents are covered.** `hive-tls.nix` folds the root into this hive's
|
- **`hive-tls.nix` covers agents.** It folds the root into this hive's
|
||||||
`trust-bundle.pem`, hive-c0re receives that path as
|
`trust-bundle.pem`, hive-c0re receives that path as
|
||||||
`HIVE_TLS_CA_PATH`, and the meta-flake renderer embeds that one file
|
`HIVE_TLS_CA_PATH`, and the meta-flake renderer embeds that one file
|
||||||
next to every agent's flake. The bundle is the runtime-to-build-time
|
next to every agent's flake. The bundle is the runtime-to-build-time
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ Two rules run through all of it.
|
||||||
|
|
||||||
**Private key material and access tokens are paths, never values.** Every option
|
**Private key material and access tokens are paths, never values.** Every option
|
||||||
carrying one takes a file path (`*File`), because a literal written into a nix
|
carrying one takes a file path (`*File`), because a literal written into a nix
|
||||||
expression is rendered into the nix store — world-readable and permanent. No
|
expression ends up in the nix store — world-readable and permanent. No
|
||||||
option in this tree accepts one inline, and adding one would be a leak rather
|
option in this tree accepts one inline, and adding one would be a leak rather
|
||||||
than a convenience.
|
than a convenience.
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ container that starts after it.
|
||||||
|
|
||||||
## Topologies, by who places secrets
|
## Topologies, by who places secrets
|
||||||
|
|
||||||
Every row below is read against one of these. This is a different cut
|
Read every row below against one of these. This is a different cut
|
||||||
from the [deployment shapes](services.md#deployment-shapes) --- those
|
from the [deployment shapes](services.md#deployment-shapes) --- those
|
||||||
say _where services run_, these say _who is responsible for a secret
|
say _where services run_, these say _who is responsible for a secret
|
||||||
file being there_ --- the two lists don't line up one-to-one, and
|
file being there_ --- the two lists don't line up one-to-one, and
|
||||||
|
|
@ -58,7 +58,7 @@ neither is a renaming of the other.
|
||||||
| the secret store's own contents | openbao, on first `bao operator init` — **an operator action, not a unit** | inside the `swarm-bao` container, at its own `/var/lib/openbao`, kept across rebuilds by `ephemeral = false`. ⚠️ **Not a host path**: `nixos-container destroy swarm-bao` takes the raft data with it, so back up the container's tree, not `/var/lib/`. Only the store's TLS material (`/var/lib/swarm-bao-tls`) and its PKCS11 token (`/var/lib/swarm-bao-token`) are host-level | n/a — there is one store; a hive elsewhere is a _client_ of it and holds none of this |
|
| the secret store's own contents | openbao, on first `bao operator init` — **an operator action, not a unit** | inside the `swarm-bao` container, at its own `/var/lib/openbao`, kept across rebuilds by `ephemeral = false`. ⚠️ **Not a host path**: `nixos-container destroy swarm-bao` takes the raft data with it, so back up the container's tree, not `/var/lib/`. Only the store's TLS material (`/var/lib/swarm-bao-tls`) and its PKCS11 token (`/var/lib/swarm-bao-token`) are host-level | n/a — there is one store; a hive elsewhere is a _client_ of it and holds none of this |
|
||||||
| the secret store's unseal material | the HSM/TPM under `deploy.bao.seal = "pkcs11"`; openbao itself under `"shamir"` | in the token; or held by whoever ran `bao operator init`, which is what `"shamir"` means and why it's stated rather than inferred | n/a — only the host running the store seals anything |
|
| the secret store's unseal material | the HSM/TPM under `deploy.bao.seal = "pkcs11"`; openbao itself under `"shamir"` | in the token; or held by whoever ran `bao operator init`, which is what `"shamir"` means and why it's stated rather than inferred | n/a — only the host running the store seals anything |
|
||||||
|
|
||||||
The three keys authelia mints for itself are generated in-container precisely
|
Authelia mints the three keys for itself, in-container, precisely
|
||||||
because nothing outside that container ever reads them. **That's the test worth
|
because nothing outside that container ever reads them. **That's the test worth
|
||||||
applying to any secret added here** — and the client secret's plaintext half is
|
applying to any secret added here** — and the client secret's plaintext half is
|
||||||
the one row that fails it, which is the entire reason a delivery step exists.
|
the one row that fails it, which is the entire reason a delivery step exists.
|
||||||
|
|
@ -71,8 +71,8 @@ an all-local swarm it reads authelia's file where it lies and no second copy is
|
||||||
made. On any other topology it's an ordinary "operator provides the file"
|
made. On any other topology it's an ordinary "operator provides the file"
|
||||||
case — see `services.hyperhive.otel.clientSecretFile`.
|
case — see `services.hyperhive.otel.clientSecretFile`.
|
||||||
|
|
||||||
The **swarm's** collector does need one. It runs in a container, so its copy is
|
The **swarm's** collector does need one. It runs in a container, so
|
||||||
placed by `swarm-otel-oidc-secret.service` and lands at
|
`swarm-otel-oidc-secret.service` places its copy, landing at
|
||||||
`/var/lib/swarm-otel-oidc/<client-id>.secret` — the same shape as the forge and
|
`/var/lib/swarm-otel-oidc/<client-id>.secret` — the same shape as the forge and
|
||||||
homeserver rows below, and for the same reason: the container that mints the
|
homeserver rows below, and for the same reason: the container that mints the
|
||||||
secret isn't the container that reads it.
|
secret isn't the container that reads it.
|
||||||
|
|
@ -120,7 +120,7 @@ refuses every client rather than degrading — and a refusal reaches the client
|
||||||
a timeout, so the symptom is every consumer hanging with nothing logged.
|
a timeout, so the symptom is every consumer hanging with nothing logged.
|
||||||
|
|
||||||
One consequence of the generated path worth knowing before you debug it: with
|
One consequence of the generated path worth knowing before you debug it: with
|
||||||
`autoGenerateCallout` set, the queue's config is assembled at boot rather than at
|
`autoGenerateCallout` set, the queue assembles its config at boot rather than at
|
||||||
build time, so a malformed one surfaces when the container starts instead of
|
build time, so a malformed one surfaces when the container starts instead of
|
||||||
when the system builds. The server names the offending file and refuses to run.
|
when the system builds. The server names the offending file and refuses to run.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ sections below don't repeat it, so a service that stops deriving is a
|
||||||
visible difference rather than one more paragraph saying the same thing.
|
visible difference rather than one more paragraph saying the same thing.
|
||||||
|
|
||||||
`singleHostSwarm` is the all-on-one-box switch above it: it defaults
|
`singleHostSwarm` is the all-on-one-box switch above it: it defaults
|
||||||
both `deploy.allSwarmServices` and `swarm.ca.autoConfigure` (the swarm CA
|
both `deploy.allSwarmServices` and `swarm.ca.autoConfigure` (this host
|
||||||
is generated here). Each derived toggle can still be set on its own,
|
generates the swarm CA here). Each derived toggle can still be set on its own,
|
||||||
which wins, so "all local except X" needs no further option.
|
which wins, so "all local except X" needs no further option.
|
||||||
|
|
||||||
**Both default to off**, and that's deliberate: a host can't tell
|
**Both default to off**, and that's deliberate: a host can't tell
|
||||||
|
|
@ -45,8 +45,8 @@ the shapes worth naming are the ones they produce:
|
||||||
- **Fully spread out.** One container / VM / machine per service,
|
- **Fully spread out.** One container / VM / machine per service,
|
||||||
somewhere.
|
somewhere.
|
||||||
|
|
||||||
**These are a set, not a ladder with a correct top, and in-between
|
**These are a set, not a ladder with a correct top, and hyperhive
|
||||||
shapes are supported.** Each derived toggle can be set on its own (see
|
supports in-between shapes.** Each derived toggle can be set on its own (see
|
||||||
above), which is what makes "all local except X" a configuration rather
|
above), which is what makes "all local except X" a configuration rather
|
||||||
than an unsupported edge case. Nothing in hyperhive prescribes a
|
than an unsupported edge case. Nothing in hyperhive prescribes a
|
||||||
deployment model, so a doc that treats one shape as the real one and
|
deployment model, so a doc that treats one shape as the real one and
|
||||||
|
|
@ -69,21 +69,21 @@ provider, differentiated by roles and claims rather than by mechanism —
|
||||||
there is one IdP and one auth path.
|
there is one IdP and one auth path.
|
||||||
|
|
||||||
- **`deploy.authelia`** — run the container here.
|
- **`deploy.authelia`** — run the container here.
|
||||||
- **`swarm.authelia.url`** — where clients are sent to authenticate.
|
- **`swarm.authelia.url`** — where clients go to authenticate.
|
||||||
Present on **every** hive, defaulting to this host's own instance only
|
Present on **every** hive, defaulting to this host's own instance only
|
||||||
when this module is the thing running it; otherwise `null`, and a hive
|
when this module is the thing running it; otherwise `null`, and a hive
|
||||||
joining someone else's swarm sets it explicitly. Null means "no SSO
|
joining someone else's swarm sets it explicitly. Null means "no SSO
|
||||||
configured", and consumers say so rather than guessing an address.
|
configured", and consumers say so rather than guessing an address.
|
||||||
|
|
||||||
The users database is written by swarm-controller, not by hand: agents
|
swarm-controller writes the users database, not by hand: agents
|
||||||
are created and destroyed continuously, so the subject set is dynamic.
|
are created and destroyed continuously, so the subject set is dynamic.
|
||||||
This module only guarantees the file exists and parses, so authelia
|
This module only guarantees the file exists and parses, so authelia
|
||||||
starts with nobody in it rather than failing to start — a provider with
|
starts with nobody in it rather than failing to start — a provider with
|
||||||
no subjects yet is the correct state before anything has provisioned
|
no subjects yet is the correct state before anything has provisioned
|
||||||
them. Session and storage keys are generated in the container on first
|
them. Authelia generates session and storage keys in the container on
|
||||||
boot and never rotated automatically; replacing one invalidates data
|
first boot and never rotates them automatically; replacing one
|
||||||
already written (sessions, the encrypted store), so that's an operator
|
invalidates data already written (sessions, the encrypted store), so
|
||||||
action.
|
that's an operator action.
|
||||||
|
|
||||||
Storage is local sqlite and the notifier writes to a file. Both are
|
Storage is local sqlite and the notifier writes to a file. Both are
|
||||||
small-deployment choices, and the scope is the justification: redis
|
small-deployment choices, and the scope is the justification: redis
|
||||||
|
|
@ -91,13 +91,13 @@ buys shared session state across replicas and there is one instance;
|
||||||
SMTP exists to mail humans, and provisioning here is programmatic.
|
SMTP exists to mail humans, and provisioning here is programmatic.
|
||||||
|
|
||||||
See [`sso.md`](sso.md) for bootstrapping the first user and the OIDC
|
See [`sso.md`](sso.md) for bootstrapping the first user and the OIDC
|
||||||
relying-party flow, and [`secrets.md`](secrets.md) for where each of
|
relying-party flow, and [`secrets.md`](secrets.md) for where authelia
|
||||||
authelia's keys is generated and read.
|
generates and reads each of its keys.
|
||||||
|
|
||||||
### Metrics (VictoriaMetrics + Grafana)
|
### Metrics (VictoriaMetrics + Grafana)
|
||||||
|
|
||||||
The swarm's telemetry lands in one VictoriaMetrics and is read through
|
The swarm's telemetry lands in one VictoriaMetrics, and one Grafana
|
||||||
one Grafana, in two containers at `metrics.<swarm-domain>` and
|
reads it, in two containers at `metrics.<swarm-domain>` and
|
||||||
`grafana.<swarm-domain>`. Two containers rather than one so Grafana can
|
`grafana.<swarm-domain>`. Two containers rather than one so Grafana can
|
||||||
be restarted or broken without taking the time-series database with it.
|
be restarted or broken without taking the time-series database with it.
|
||||||
|
|
||||||
|
|
@ -119,8 +119,8 @@ See `retentionPeriod` below before leaving it at its default.
|
||||||
| `deploy.grafana.datasourceUrl` | Only if you front VictoriaMetrics with something else. It defaults to the store on this host, which is the only thing it can reach. |
|
| `deploy.grafana.datasourceUrl` | Only if you front VictoriaMetrics with something else. It defaults to the store on this host, which is the only thing it can reach. |
|
||||||
|
|
||||||
**Logging in.** Grafana is behind swarm SSO, so the accounts are the
|
**Logging in.** Grafana is behind swarm SSO, so the accounts are the
|
||||||
authelia ones — there is no separate Grafana password, and the local
|
authelia ones — there is no separate Grafana password, and this module
|
||||||
login form is switched off whenever SSO is configured. If you enable
|
switches off the local login form whenever SSO is configured. If you enable
|
||||||
Grafana on a host with no authelia, the form stays on and Grafana's
|
Grafana on a host with no authelia, the form stays on and Grafana's
|
||||||
default `admin`/`admin` applies; change it before exposing that host.
|
default `admin`/`admin` applies; change it before exposing that host.
|
||||||
|
|
||||||
|
|
@ -178,9 +178,9 @@ gateway either way.
|
||||||
| `swarm.otel.domain` | Only to rename it — the default already resolves correctly for every hive in the swarm. |
|
| `swarm.otel.domain` | Only to rename it — the default already resolves correctly for every hive in the swarm. |
|
||||||
| `swarm.otel.port` | Only if something else on the services host already claims `4319`. |
|
| `swarm.otel.port` | Only if something else on the services host already claims `4319`. |
|
||||||
|
|
||||||
With neither `otel.endpoint` nor the store enabled, this collector is
|
With neither `otel.endpoint` nor the store enabled, this module refuses
|
||||||
refused at eval — a tier that receives samples and drops them looks
|
the collector at eval — a tier that receives samples and drops them
|
||||||
healthy while losing data.
|
looks healthy while losing data.
|
||||||
|
|
||||||
Agent-side configuration, and what a hive's own collector does, are in
|
Agent-side configuration, and what a hive's own collector does, are in
|
||||||
[`../scheduler/observability.md`](../scheduler/observability.md).
|
[`../scheduler/observability.md`](../scheduler/observability.md).
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ provider** every protected vhost checks (`auth_request`), and — once any
|
||||||
client is declared — an **OIDC provider** issuing tokens to relying
|
client is declared — an **OIDC provider** issuing tokens to relying
|
||||||
parties: the forge and the matrix homeserver.
|
parties: the forge and the matrix homeserver.
|
||||||
|
|
||||||
The second role is derived rather than switched:
|
The second role derives rather than switches:
|
||||||
`services.hyperhive.swarm.authelia.oidc.clients` being non-empty turns it
|
`services.hyperhive.swarm.authelia.oidc.clients` being non-empty turns it
|
||||||
on. authelia refuses to start with a provider that has no clients, so a
|
on. authelia refuses to start with a provider that has no clients, so a
|
||||||
separate `enable` would be a second fact free to disagree with the first.
|
separate `enable` would be a second fact free to disagree with the first.
|
||||||
|
|
@ -18,10 +18,10 @@ name all follow `deploy.authelia`, so there is nothing to turn on
|
||||||
separately. (Details, including why a client hive must not declare that
|
separately. (Details, including why a client hive must not declare that
|
||||||
vhost: [`../networking/gateway.md`](../networking/gateway.md).)
|
vhost: [`../networking/gateway.md`](../networking/gateway.md).)
|
||||||
|
|
||||||
**Authelia doesn't start until at least one user exists.** The user
|
**Authelia doesn't start until at least one user exists.** This module
|
||||||
store is generated empty — deliberately, since seeding a default account
|
generates the user store empty — deliberately, since seeding a default
|
||||||
would put a credential in a config file — but authelia validates it at
|
account would put a credential in a config file — but authelia validates
|
||||||
startup and treats "no users" as fatal:
|
it at startup and treats "no users" as fatal:
|
||||||
|
|
||||||
```
|
```
|
||||||
error reading the authentication database: could not validate the schema:
|
error reading the authentication database: could not validate the schema:
|
||||||
|
|
@ -47,8 +47,8 @@ password: <generated>
|
||||||
this password is stored nowhere — record it now
|
this password is stored nowhere — record it now
|
||||||
```
|
```
|
||||||
|
|
||||||
The password is generated, hashed, and printed once; only the hash is
|
`swarmctl` generates, hashes, and prints the password once, keeping only
|
||||||
kept. `swarmctl` reads and writes authelia's `users.yml` directly — it's
|
the hash. It reads and writes authelia's `users.yml` directly — it's
|
||||||
the one user store, shared with `swarm-authelia-bridge`, which creates
|
the one user store, shared with `swarm-authelia-bridge`, which creates
|
||||||
agent identities in the same file. No restart: authelia watches it. Full
|
agent identities in the same file. No restart: authelia watches it. Full
|
||||||
reference: [`../tools/swarmctl-cli.md`](../tools/swarmctl-cli.md).
|
reference: [`../tools/swarmctl-cli.md`](../tools/swarmctl-cli.md).
|
||||||
|
|
@ -84,7 +84,7 @@ Two behaviours worth knowing before you rely on them:
|
||||||
already was. Revocation is the exception on purpose: a typo'd group
|
already was. Revocation is the exception on purpose: a typo'd group
|
||||||
name that reported success would leave an account holding access you
|
name that reported success would leave an account holding access you
|
||||||
believe you took away, and that's the one outcome nobody re-checks.
|
believe you took away, and that's the one outcome nobody re-checks.
|
||||||
- **The resulting group list is printed** because group names have no
|
- **`swarmctl` prints the resulting group list** because group names have no
|
||||||
registry anywhere. A misspelled `--add-group` creates a real group that
|
registry anywhere. A misspelled `--add-group` creates a real group that
|
||||||
no access-control rule mentions, so the user gains nothing and no error
|
no access-control rule mentions, so the user gains nothing and no error
|
||||||
is possible — reading the line back is the only check there is.
|
is possible — reading the line back is the only check there is.
|
||||||
|
|
@ -115,8 +115,8 @@ authelia's first boot to mint that client's secret and copies it into the
|
||||||
service's container, and the service's own module contributes its client
|
service's container, and the service's own module contributes its client
|
||||||
entry — callback URL included — to authelia's client list.
|
entry — callback URL included — to authelia's client list.
|
||||||
|
|
||||||
The callback is built once and read twice, so the redirect URI authelia is
|
The callback is built once and read twice, so the redirect URI authelia
|
||||||
told to allow and the one the service actually sends can't drift apart. A
|
accepts and the one the service actually sends can't drift apart. A
|
||||||
mismatch there is a rejected login with no error text worth reading.
|
mismatch there is a rejected login with no error text worth reading.
|
||||||
|
|
||||||
⚠️ The delivery is a copy, not a `bindMounts` entry, and deliberately so:
|
⚠️ The delivery is a copy, not a `bindMounts` entry, and deliberately so:
|
||||||
|
|
@ -147,7 +147,7 @@ services.hyperhive.swarm = {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
**Both are asserted at eval.** A hive that boots with SSO
|
**This module asserts both at eval.** A hive that boots with SSO
|
||||||
half-configured shows a login button that always fails — a symptom
|
half-configured shows a login button that always fails — a symptom
|
||||||
many layers from its cause, and far worse to diagnose than an
|
many layers from its cause, and far worse to diagnose than an
|
||||||
evaluation error.
|
evaluation error.
|
||||||
|
|
@ -173,9 +173,9 @@ Two consequences worth stating plainly:
|
||||||
host-side ownership arrangement to maintain.
|
host-side ownership arrangement to maintain.
|
||||||
- **Matrix SSO lives inside the homeserver.** The client-server API is
|
- **Matrix SSO lives inside the homeserver.** The client-server API is
|
||||||
spoken by non-browser clients holding matrix access tokens — every
|
spoken by non-browser clients holding matrix access tokens — every
|
||||||
agent's own daemon — as well as by federation, so `/_matrix/` is served
|
agent's own daemon — as well as by federation, so the homeserver
|
||||||
directly and authenticates itself. The forward-auth vhosts protect
|
serves `/_matrix/` directly and authenticates it itself. The
|
||||||
browser surfaces; this isn't one of them.
|
forward-auth vhosts protect browser surfaces; this isn't one of them.
|
||||||
|
|
||||||
## Machine clients
|
## Machine clients
|
||||||
|
|
||||||
|
|
@ -197,7 +197,7 @@ thing to keep in step, and it drifts silently — a mismatch presents as a
|
||||||
valid token refused at the target, which reads like a broken credential
|
valid token refused at the target, which reads like a broken credential
|
||||||
rather than a broken name.
|
rather than a broken name.
|
||||||
|
|
||||||
**Tokens are signed (`RS256`), not opaque**, because a resource server
|
**Authelia signs tokens (`RS256`), not opaque ones**, because a resource server
|
||||||
that can't call the provider back is a real case here: the telemetry
|
that can't call the provider back is a real case here: the telemetry
|
||||||
collector verifies offline against `/jwks.json`, and an opaque token gives
|
collector verifies offline against `/jwks.json`, and an opaque token gives
|
||||||
it nothing to verify. The queue's responder introspects instead — a
|
it nothing to verify. The queue's responder introspects instead — a
|
||||||
|
|
@ -218,15 +218,15 @@ HTML instead of the failure that actually happened:
|
||||||
- `/api/oidc/token`, `/.well-known/openid-configuration` — a client
|
- `/api/oidc/token`, `/.well-known/openid-configuration` — a client
|
||||||
parsing an error page as its JSON document
|
parsing an error page as its JSON document
|
||||||
|
|
||||||
Authelia's `/api/` and `/.well-known/` prefixes are routed **without**
|
The gateway routes authelia's `/api/` and `/.well-known/` prefixes
|
||||||
error interception. The split is by _audience_, not by an enumerated path
|
**without** error interception. The split is by _audience_, not by an enumerated path
|
||||||
list: a human gets the page, every machine caller gets the status.
|
list: a human gets the page, every machine caller gets the status.
|
||||||
Enumerating endpoints individually would leave the next one added
|
Enumerating endpoints individually would leave the next one added
|
||||||
silently intercepted.
|
silently intercepted.
|
||||||
|
|
||||||
The same shape bites any machine route behind a browser-shaped gate: a
|
The same shape bites any machine route behind a browser-shaped gate: the
|
||||||
`302` to a login page is followed, the login page answers `200`, and the
|
caller follows a `302` to a login page, the login page answers `200`,
|
||||||
caller reports success while nothing happened. Log ingest hit exactly this
|
and the caller reports success while nothing happened. Log ingest hit exactly this
|
||||||
and lost eleven hours of delivery in silence.
|
and lost eleven hours of delivery in silence.
|
||||||
|
|
||||||
**Checking it, if you change this routing.** Point the vhost at a dead
|
**Checking it, if you change this routing.** Point the vhost at a dead
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ between "an operator's page" and "anyone with a session."
|
||||||
|
|
||||||
## What it costs to be reachable
|
## What it costs to be reachable
|
||||||
|
|
||||||
The apex is published to the hive's resolver like every other swarm
|
hyperhive publishes the apex to the hive's resolver like every other
|
||||||
service, so agent containers can resolve it. That's deliberate and it's
|
swarm service, so agent containers can resolve it. That's deliberate and it's
|
||||||
not a hole: **reachability isn't the access control here.** An agent
|
not a hole: **reachability isn't the access control here.** An agent
|
||||||
that resolves the name and connects still has no operator session, and
|
that resolves the name and connects still has no operator session, and
|
||||||
the subrequest denies it.
|
the subrequest denies it.
|
||||||
|
|
@ -74,7 +74,7 @@ a container":
|
||||||
| **certificate name** | `nix/host-modules/swarm.nix` (`serviceDomains`) |
|
| **certificate name** | `nix/host-modules/swarm.nix` (`serviceDomains`) |
|
||||||
|
|
||||||
The DNS record and the local-dev `/etc/hosts` entry need no separate
|
The DNS record and the local-dev `/etc/hosts` entry need no separate
|
||||||
edit: both are derived from `services.hyperhive.gateway.localNames`,
|
edit: both derive from `services.hyperhive.gateway.localNames`,
|
||||||
which a service's own module already has to push its domain into to be
|
which a service's own module already has to push its domain into to be
|
||||||
resolvable — see `nix/host-modules/hive-gateway/dnsmasq.nix` and
|
resolvable — see `nix/host-modules/hive-gateway/dnsmasq.nix` and
|
||||||
`.../default.nix`'s `networking.hosts`. `vhosts.nix` itself is scoped to
|
`.../default.nix`'s `networking.hosts`. `vhosts.nix` itself is scoped to
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue