From 7fa1963798aa91132dcbf8fbaa3375474c2048ab Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Tue, 12 May 2026 18:04:09 +0000 Subject: [PATCH] docs: extend secret docs --- --- a/docs/features/secrets.adoc +++ b/docs/features/secrets.adoc @@ -9,22 +9,247 @@ remote system/add secret to fleet configuration, fleet users are encrypting secrets using received public key instead, they don't need the root access to receive the public encryption key. -== Example +== Per-host and shared secrets + +Secrets come in two flavours; both are declared with the same `secrets.` syntax, but in different +scopes: + +Per-host:: declared inside a host's `nixos` block. Generated and encrypted only for that one host. + Use for things like a machine-specific tokens, or per-peer WireGuard keys. +Shared:: declared at the top-level fleet config with `expectedOwners = [...]`. The same value is + encrypted independently for every owner. Each owning host must claim it with + `secrets..generator = "shared"` in its `nixos` block (the assertion at + link:../../modules/nixos/secrets.nix[modules/nixos/secrets.nix] enforces this). + +[source,nix] +---- +{ config, fleetLib, ... }: +{ + # Shared - top-level, with expectedOwners + secrets."wg-psk" = { + expectedOwners = [ "alpha" "beta" ]; + generator = fleetLib.mkBase64Bytes { count = 32; }; + # Optional: rotate when ownership changes + regenerateOnOwnerAdded = true; + regenerateOnOwnerRemoved = true; + }; + + hosts.alpha.nixos = { ... }: { + # Per-host - lives only on alpha + secrets."alpha-api-token".generator = fleetLib.mkPassword { }; + + # Claim of the shared secret declared above + secrets."wg-psk".generator = "shared"; + }; + + hosts.beta.nixos = { ... }: { + secrets."wg-psk".generator = "shared"; + }; +} +---- + +See link:../examples/wireguard.adoc[the WireGuard example] for a fuller example combining both forms. + +== Built-in secret generators + +Fleet ships a set of generators in link:../../lib/default.nix[`fleetLib`] covering the common cases. +Pass them directly as `generator` (shown here as shared secrets; the same calls work in a per-host +`nixos.secrets..generator` block - just drop `expectedOwners`): + +[source,nix] +---- +{ config, lib, fleetLib, ... }: +{ + secrets = { + # Random 32-char ASCII password (single encrypted `secret` part) + db-password = { + expectedOwners = [ "db" ]; + generator = fleetLib.mkPassword { }; + }; + + # 64-char password + long-password = { + expectedOwners = [ "db" ]; + generator = fleetLib.mkPassword { size = 64; }; + }; + + # 32 random bytes, raw/hex/base64 encoded + api-token = { + expectedOwners = [ "api" ]; + generator = fleetLib.mkBytes { count = 32; encoding = "base64"; }; + }; + session-key = { + expectedOwners = [ "api" ]; + generator = fleetLib.mkHexBytes { count = 32; }; + }; + wg-psk = { + expectedOwners = [ "alpha" "beta" ]; + generator = fleetLib.mkBase64Bytes { count = 32; }; + }; + + # Ed25519 keypair, two parts: plaintext `public`, encrypted `secret` + # `noEmbedPublic` strips the embedded public half, leaving only the 32-byte private scalar + garage-rpc = { + expectedOwners = [ "storage" ]; + generator = fleetLib.mkEd25519 { encoding = "base64"; }; + }; + + # X25519 keypair (e.g. WireGuard peer keys) - usually per-host, shown shared here for brevity + wg-key = { + expectedOwners = [ "alpha" ]; + generator = fleetLib.mkX25519 { encoding = "base64"; }; + }; + + # RSA keypair, PEM-encoded; default size is 4096 + tls-key = { + expectedOwners = [ "edge" ]; + generator = fleetLib.mkRsa { size = 2048; }; + }; + }; +} +---- + +=== Interactive generators +These prompt the operator on the host running `fleet` and are useful for secrets that can't be generated +algorithmically (vendor API keys, manually-issued certificates, etc.). They are impure - the value you type +is what gets encrypted. + +Note that they also require GUI environment on the machine where the systems are built first time. This is intentional, +because fleet console optput is quite cluttered during the deployment, and it is hard to properly show it. +If this constraint bothers you - feel free to implement those however you like, all of those generators are not handled +in a special way on fleet level: link:../../lib/default.nix[lib/default.nix]. + [source,nix] ---- { secrets = { + # Single-line input via kdialog + stripe-api-key = { + expectedOwners = [ "billing" ]; + generator = fleetLib.mkAskPass { + prompt = "Stripe live secret key"; + }; + }; + + # Multi-line input opened in Kate; useful for pasted certificates/blobs + upstream-ca = { + expectedOwners = [ "edge" ]; + generator = fleetLib.mkAskFile { + header = "Paste upstream CA bundle (PEM)"; + part = "cert"; + }; + }; + + # Convenience wrapper that pre-populates an env-file template + service-env = { + expectedOwners = [ "app" ]; + generator = fleetLib.mkAskEnv { + header = "Fill in upstream credentials"; + variables = [ "DATABASE_URL" "API_KEY" "SECRET_TOKEN" ]; + }; + }; + }; +} +---- + +== Custom secret generator example + +[source,nix] +---- +{ + secrets = { "my-secret" = { + # Shared secret will be encrypted for those two hosts expectedOwners = [ "host1" "host2" ]; + # We can force secret to be regenerated when the owner was added regenerateOnOwnerAdded = true; - generator = {mkImpureSecretGenerator}: + + # generator is a function from pkgs extended with helpers, so if you need something special, you can just get + # it from arguments, fleet will do callPackage for you, and provide you with the packages for the machine + # that will be generating this secret + generator = { + mkImpureSecretGenerator, + openssl, + }: mkImpureSecretGenerator { + # Impure secret generator executes arbitrary code on arbitrary host over SSH + impureOn = "host3"; + + # Secret may produce multiple parts, for example, for RSA keypair it would be secret and public part, + # where only secret part should be encrypted. Non-encrypted parts are available as nix store path and as + # an expression, encrypted parts will be only available on owning machines after the deployment. + parts.secret.encrypted = true; + + # In impure secret generator, script is a code that thould prepare the target directory structure + # Target directory should contain one file for every declared part (parts argument in this attrset), + # and may contain expires_at/created_at files if you want to use it with PKI. + # + # Secret parts should be encoded/encrypted to be consumed by fleet, you can use `gh` alias for that, + # it will be prepared implicitly, and for arguments - look at the `fleet-generator-helper` section of the docs. script = '' - echo "secret content" | gh private -o $out/secret + mkdir $out + ${getExe openssl} rand -base64 16 | gh private -o $out/secret ''; }; }; } } ---- + +== fleet-generator-helper + +[source] +---- +Usage: fleet-generator-helper + +Commands: + public Encode public part from stdin + private Encrypt private part from stdin + help Print this message or the help of the given subcommand(s) + +Options: + -h, --help Print help +---- + +[source] +---- +Encode public part from stdin + +Usage: fleet-generator-helper public [OPTIONS] --output + +Options: + -o, --output + + -e, --encoding + Possible values: + - raw: Do not encode data, store as is + - base64: Encode as base64 (with padding) + - hex: Encode as hex (without leading 0x) + + [default: raw] + + -h, --help + Print help (see a summary with '-h') +---- + +[source] +---- +Encrypt private part from stdin + +Usage: fleet-generator-helper private [OPTIONS] --output + +Options: + -o, --output + + -e, --encoding + Possible values: + - raw: Do not encode data, store as is + - base64: Encode as base64 (with padding) + - hex: Encode as hex (without leading 0x) + + [default: raw] + + -h, --help + Print help (see a summary with '-h') +---- -- gitstuff