git.delta.rocks / jrsonnet / refs/commits / cd1bdf72b91a

difftreelog

doc: tried to improve docs, realised I'm still too lazy for that

Yaroslav Bolyukin2025-03-23parent: #a70ed4a.patch.diff
in: trunk

5 files changed

addeddocs/secrets.adocdiffbeforeafterboth

no changes

modifiedlib/default.nixdiffbeforeafterboth
33 inherit (options) mkHostsOption;33 inherit (options) mkHostsOption;
3434
35 modules = {35 modules = {
36 # mkDefault = mkOverride 100036 /**
37 # For places, where fleet knows better than nixpkgs defaults.37 Use in places, where fleet might know better than nixpkgs defaults to
38 */
38 mkFleetDefault = mkOverride 999;39 mkFleetDefault = mkOverride 999;
40 /**
39 # Some generators use mkDefault, but optionDefault is set by nixpkgs.41 Some generators use mkDefault, but optionDefault is set by nixpkgs.
42 */
40 mkFleetGeneratorDefault = mkOverride 1001;43 mkFleetGeneratorDefault = mkOverride 1001;
41 };44 };
4245
43 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;46 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;
4447
45 secrets = {48 secrets = {
49 /**
50 Generate a random secret password, 32 ascii characters by default
51
52 Options:
53 size: generated password length in ascii characters (bytes).
54 noSymbols: by default, character set includes various special characters ($ , ! + * : ~), and might
55 not be accepted in some contexts, this option switches charset to just [A-Za-z0-9].
56
57 Output:
58 Resulting secret has only part: secret, which contains encrypted password.
59 */
46 mkPassword = {size ? 32}: {60 mkPassword = {size ? 32}: {
47 coreutils,61 coreutils,
48 mkSecretGenerator,62 mkSecretGenerator,
54 '';68 '';
55 };69 };
5670
71 /**
72 Generate a random ed25519 keypair
73
74 Options:
75 noEmbedPublic: By default, secret key also embeds public key in itself ("extended" format, 64 bytes)
76 When noEmbedPublis is enabled - only the private scalar is included.
77 encoding: Encoring of public and secret parts, can be "raw" (default), "base64" or "hex".
78
79 Output:
80 Resulting secret has two parts: public and secret, where the secret part is encrypted.
81
82 This secret format is used by e.g Garage S3 server
83 */
57 mkEd25519 = {84 mkEd25519 = {
58 noEmbedPublic ? false,85 noEmbedPublic ? false,
59 encoding ? null,86 encoding ? null,
67 '';94 '';
68 };95 };
6996
97 /**
98 Generate a random x25519 keypair
99
100 Options:
101 encoding: Encoring of public and secret parts, can be "raw" (default), "base64" or "hex".
102
103 Output:
104 Resulting secret has two parts: public and secret, where the secret part is encrypted.
105
106 This secret format is used by e.g Wireguard VPN for peers (base64-encoded)
107 */
70 mkX25519 = {encoding ? null}: {mkSecretGenerator}:108 mkX25519 = {encoding ? null}: {mkSecretGenerator}:
71 mkSecretGenerator {109 mkSecretGenerator {
72 script = ''110 script = ''
76 '';114 '';
77 };115 };
78116
117 /**
118 Generate a random RSA keypair
119
120 Options:
121 size: RSA key size, 4096 by default
122
123 Output:
124 Resulting secret has two parts: public and secret, where the secret part is encrypted.
125 Both parts are PEM encoded.
126 */
79 mkRsa = {size ? 4096}: {127 mkRsa = {size ? 4096}: {
80 openssl,128 openssl,
81 mkSecretGenerator,129 mkSecretGenerator,
92 '';140 '';
93 };141 };
94142
143 /**
144 Generate a random byte sequence
145
146 Options:
147 size: generated password length in bytes, 32 by default.
148 encoding: how the generated bytes should be encoded, "raw" (default), "hex" or "base64"
149 noNuls: prevent output byte sequence from containing internal \0, useful for some C applications
150 that can't handle their strings properly.
151
152 Output:
153 Resulting secret has only part: secret, which contains encrypted bytes.
154
155 Might be used for e.g. Wireguard VPN PSK keys (base64-encoded)
156 */
95 mkBytes = {157 mkBytes = {
96 count ? 32,158 count ? 32,
97 encoding,159 encoding,
104 ${optionalString noNuls "--no-nuls"}166 ${optionalString noNuls "--no-nuls"}
105 '';167 '';
106 };168 };
169 /**
170 Shorthand for `mkBytes`, which defaults to "hex" encoding
171 */
107 mkHexBytes = {count ? 32}:172 mkHexBytes = {count ? 32}:
108 mkBytes {173 mkBytes {
109 inherit count;174 inherit count;
110 encoding = "hex";175 encoding = "hex";
111 };176 };
177 /**
178 Shorthand for `mkBytes`, which defaults to "base64" encoding
179 */
112 mkBase64Bytes = {count ? 32}:180 mkBase64Bytes = {count ? 32}:
113 mkBytes {181 mkBytes {
114 inherit count;182 inherit count;
126 plaintextPrefix = "<PLAINTEXT>";194 plaintextPrefix = "<PLAINTEXT>";
127 plaintextNewlinePrefix = "<PLAINTEXT-NL>";195 plaintextNewlinePrefix = "<PLAINTEXT-NL>";
128 in {196 in {
197 /**
198 Decode public secret part into string
199 */
129 decodeRawSecret = raw:200 decodeRawSecret = raw:
130 if hasPrefix plaintextPrefix raw201 if hasPrefix plaintextPrefix raw
131 then removePrefix plaintextPrefix raw202 then removePrefix plaintextPrefix raw
modifiedmodules/hosts.nixdiffbeforeafterboth
11 inherit (lib.attrsets) mapAttrsToList mapAttrs;11 inherit (lib.attrsets) mapAttrsToList mapAttrs;
12 inherit (lib.lists) flatten groupBy;12 inherit (lib.lists) flatten groupBy;
13in {13in {
14 # Fleet Meta Configuration Module
15
14 options = {16 options = {
15 data = mkOption {17 data = mkOption {
18 version = mkOption {20 version = mkOption {
19 type = str;21 type = str;
20 internal = true;22 internal = true;
23 description = "Internal version identifier for saved fleet state";
21 };24 };
25
22 gcRootPrefix = mkOption {26 gcRootPrefix = mkOption {
23 type = str;27 type = str;
24 internal = true;28 internal = true;
29 description = "Prefix for fleet-generated gc garbage collection roots";
25 };30 };
31
26 hosts = mkOption {32 hosts = mkOption {
27 type = attrsOf (submodule {33 type = attrsOf (submodule {
28 options.encryptionKey = mkOption {34 options.encryptionKey = mkOption {
29 type = str;35 type = str;
30 description = "Rage SSH encryption key for secrets.";36 description = "Rage SSH encryption key for host-bound secrets";
31 };37 };
32 });38 });
33 };39 };
34 };40 };
35 };41 };
36 description = ''42 description = ''
37 Configuration provided from outside.43 Persistent configuration data for fleet management.
38 Usually used to persist fleet data between runs.44 Typically used to maintain state between fleet configuration runs.
39 '';45 '';
40 };46 };
47
41 taggedWith = mkOption {48 taggedWith = mkOption {
42 type = attrsOf (listOf str);49 type = attrsOf (listOf str);
43 internal = true;50 internal = true;
51 description = "Mapping of hosts grouped by tags, used by fleet CLI";
44 };52 };
53
45 hosts = mkOption {54 hosts = mkOption {
46 type = mkHostsType ({config, ...}: {55 type = mkHostsType ({config, ...}: {
47 options = {56 options = {
48 system = mkOption {57 system = mkOption {
49 description = "Type of the system.";58 description = "System architecture and platform identifier";
50 type = str;59 type = str;
51 example = "x86_64-linux";60 example = "x86_64-linux";
52 };61 };
62
53 tags = mkOption {63 tags = mkOption {
54 description = "Host tag. In CLI, you can refer to all hosts having this tag using @tag syntax.";64 description = ''
65 Tags for host classification.
66 Used for host selection via @tag syntax in CLI tools.
67 '';
55 type = listOf str;68 type = listOf str;
56 };69 };
70
71 # Network configuration details
57 network = mkOption {72 network = mkOption {
58 type = submodule {73 type = submodule {
59 options = {74 options = {
60 internalIps = mkOption {75 internalIps = mkOption {
61 description = "Internal ips";76 description = "List of internal IP addresses for the host";
62 type = listOf str;77 type = listOf str;
63 default = [];78 default = [];
64 };79 };
80
65 externalIps = mkOption {81 externalIps = mkOption {
66 description = "External ips";82 description = "List of external IP addresses for the host";
67 type = listOf str;83 type = listOf str;
68 default = [];84 default = [];
69 };85 };
70 };86 };
71 };87 };
72 description = "Network definition of host";
73 };88 };
74 };89 };
75 config = {90 config = {
91 # Default hostname generation
76 nixos.networking.hostName = mkFleetGeneratorDefault config._module.args.name;92 nixos.networking.hostName = mkFleetGeneratorDefault config._module.args.name;
93 # Default 'all' tag for every host
77 tags = ["all"];94 tags = ["all"];
78 };95 };
79 _file = ./meta.nix;96 _file = ./meta.nix;
80 });97 });
81 default = {};98 default = {};
82 description = "Configurations of individual hosts";
83 };99 };
84 };100 };
101
102 # Generate a mapping of hosts indexed by their tags
85 config.taggedWith = let103 config.taggedWith = let
104 # Flatten host tags into a list of {hostname, tag} pairs
86 hostTagList = flatten (mapAttrsToList (hostname: host: map (tag: {inherit hostname tag;}) host.tags) config.hosts);105 hostTagList = flatten (mapAttrsToList (hostname: host: map (tag: {inherit hostname tag;}) host.tags) config.hosts);
106 # Group hostnames by their tags
87 grouped = mapAttrs (_: hosts: lib.map (pair: pair.hostname) hosts) (groupBy (elem: elem.tag) hostTagList);107 grouped = mapAttrs (_: hosts: lib.map (pair: pair.hostname) hosts) (groupBy (elem: elem.tag) hostTagList);
88 in108 in
89 grouped;109 grouped;
110
111 # Source file reference
90 _file = ./meta.nix;112 _file = ./meta.nix;
91}113}
92114
modifiedmodules/secrets-data.nixdiffbeforeafterboth
15 options = {15 options = {
16 raw = mkOption {16 raw = mkOption {
17 type = nullOr str;17 type = nullOr str;
18 description = "Encrypted + encoded secret data";18 description = "Raw secret data in unspecified encoded and optionally encrypted format.";
19 default = null;19 default = null;
20 };20 };
21 };21 };
26 options = {26 options = {
27 createdAt = mkOption {27 createdAt = mkOption {
28 type = str;28 type = str;
29 description = "When this secret was (re)generated";29 description = "Timestamp of secret generation/last rotation.";
30 default = null;30 default = null;
31 };31 };
32 expiresAt = mkOption {32 expiresAt = mkOption {
33 type = nullOr str;33 type = nullOr str;
34 description = "On which date this secret will expire, someone should regenerate this secret before it expires.";34 description = "Expiration timestamp triggering mandatory secret rotation.";
35 default = null;35 default = null;
36 };36 };
3737
38 owners = mkOption {38 owners = mkOption {
39 type = listOf str;39 type = listOf str;
40 description = ''40 description = ''
41 For which owners this secret is currently encrypted,41 List of hosts currently authorized to decrypt this shared secret.
42 if not matches expectedOwners - then this secret is considered outdated, and42
43 should be regenerated/reencrypted.43 If owners differ from expected owners, the secret is considered outdated
4444 and requires regeneration or re-encryption.
45 Imported from fleet.nix
46 '';45 '';
47 default = [];46 default = [];
48 };47 };
49 generationData = mkOption {48 generationData = mkOption {
50 type = unspecified;49 type = unspecified;
51 description = "Data that is embedded into secret part";50 description = "Contextual metadata associated with secret part.";
52 default = null;51 default = null;
53 };52 };
54 };53 };
60 options = {59 options = {
61 createdAt = mkOption {60 createdAt = mkOption {
62 type = str;61 type = str;
63 description = "When this secret was (re)generated";62 description = "Timestamp of secret generation/last rotation.";
64 default = null;63 default = null;
65 };64 };
66 expiresAt = mkOption {65 expiresAt = mkOption {
67 type = nullOr str;66 type = nullOr str;
68 description = "On which date this secret will expire, someone should regenerate this secret before it expires.";67 description = "Expiration timestamp triggering mandatory secret rotation.";
69 default = null;68 default = null;
70 };69 };
71 shared = mkOption {70 shared = mkOption {
72 type = bool;71 type = bool;
73 description = "On which date this secret will expire, someone should regenerate this secret before it expires.";72 description = "Indicates if secret is a shared secret, so other hosts might have the same piece of secret data.";
74 default = false;73 default = false;
75 };74 };
76 generationData = mkOption {75 generationData = mkOption {
77 type = unspecified;76 type = unspecified;
78 description = "Data that is embedded into secret part";77 description = "Contextual metadata associated with secret part.";
79 default = null;78 default = null;
80 };79 };
81 };80 };
87 sharedSecrets = mkOption {86 sharedSecrets = mkOption {
88 type = attrsOf (submodule sharedSecretData);87 type = attrsOf (submodule sharedSecretData);
89 default = {};88 default = {};
90 description = "Stored shared secret data.";89 description = "Shared secret data.";
91 };90 };
92 hostSecrets = mkOption {91 hostSecrets = mkOption {
93 type = attrsOf (attrsOf (submodule hostSecretData));92 type = attrsOf (attrsOf (submodule hostSecretData));
94 default = {};93 default = {};
95 description = "Host secrets.";94 description = "Host-specific secrets.";
96 internal = true;95 internal = true;
97 };96 };
98 };97 };
modifiedmodules/secrets.nixdiffbeforeafterboth
4 ...4 ...
5}: let5}: let
6 inherit (lib.options) mkOption literalExpression;6 inherit (lib.options) mkOption literalExpression;
7 inherit (lib.types) unspecified nullOr listOf str bool attrsOf submodule;7 inherit (lib.types) unspecified nullOr listOf str bool attrsOf submodule functionTo package;
8 inherit (lib.strings) concatStringsSep;8 inherit (lib.strings) concatStringsSep;
9 inherit (lib.attrsets) mapAttrs;9 inherit (lib.attrsets) mapAttrs;
1010
11 sharedSecret = {config, ...}: {11 sharedSecret = {config, ...}: {
12 options = {12 options = {
13 expectedOwners = mkOption {13 expectedOwners = mkOption {
14 type = nullOr (listOf str);14 type = nullOr (listOf str);
15 description = ''15 description = ''
16 List of hosts to encrypt secret for. null if managed by user (= via owners field from fleet.nix)16 Specifies the list of hosts authorized to decrypt and access this shared secret.
1717
18 Secrets would be decrypted and stored to /run/secrets/$\{name} on owners18 When null, secret ownership is managed manually via fleet.nix and CLI.
19 Decrypted secrets will be stored at /run/secrets/$\{name} on authorized hosts.
19 '';20 '';
20 default = null;21 default = null;
21 };22 };
22 # TODO: Aren't those options may be just desugared to data/expectedData?
23 regenerateOnOwnerAdded = mkOption {23 regenerateOnOwnerAdded = mkOption {
24 type = bool;24 type = bool;
25 description = ''25 description = ''
26 Is this secret owner-dependent, and needs to be regenerated on ownership set change, or it may be just reencrypted.26 Controls whether the secret must be regenerated when new owners are added.
2727
28 You want to have this option set to true, when this secret contains some reference to its owners, i.e x509 SANs.28 Set to true when the secret contains owner-specific references (e.g., X.509 Subject Alternative Names).
29 When true, adding a new owner will trigger secret regeneration instead of simple re-encryption.
29 '';30 '';
30 };31 };
31 regenerateOnOwnerRemoved = mkOption {32 regenerateOnOwnerRemoved = mkOption {
32 default = config.regenerateOnOwnerAdded;33 default = config.regenerateOnOwnerAdded;
33 defaultText = literalExpression "regenerateOnOwnerAdded";34 defaultText = literalExpression "regenerateOnOwnerAdded";
34 type = bool;35 type = bool;
35 description = ''36 description = ''
36 Should this secret be removed on owner removal, or it may be just reencrypted37 Determines secret behavior when owners are removed from the configuration.
3738
38 Most probably its value should be equal to regenerateOnOwnerAdded, override only if you know what are you doing.39 Typically mirrors regenerateOnOwnerAdded. Override cautiously.
39 Contrary to regenerateOnOwnerAdded, you may want to set this option to false, when host permissions are revoked40 Set to false if host permissions are revoked through alternative mechanisms like firewall rules.
40 in some other way than by this secret ownership, I.e by firewall/etc.
41 '';41 '';
42 };42 };
43 generator = mkOption {43 generator = mkOption {
44 type = nullOr unspecified;44 type = nullOr (functionTo package);
45 description = "Derivation to evaluate for secret generation";45 description = ''
46 Function evaluating to nix derivation responsible for (re)generating the secret's content.
47
48 An input to this function - `pkgs` of a generator host with implementation-defined representation of extra encryption data,
49 use `mkSecretGenerator` helpers to implement own generators.
50 '';
46 default = null;51 default = null;
47 };52 };
48 expectedGenerationData = mkOption {53 expectedGenerationData = mkOption {
49 type = unspecified;54 type = unspecified;
50 description = "Data that gets embedded into secret part";55 description = "Contextual metadata embedded within the secret part value";
51 default = null;56 default = null;
52 };57 };
53 };58 };
57 sharedSecrets = mkOption {62 sharedSecrets = mkOption {
58 type = attrsOf (submodule sharedSecret);63 type = attrsOf (submodule sharedSecret);
59 default = {};64 default = {};
60 description = "Shared secrets";65 description = "Collection of secrets shared across multiple hosts with configurable ownership";
61 };66 };
62 };67 };
63 config = {68 config = {