difftreelog
fix post secret management refactor
in: trunk
3 files changed
cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth--- a/cmds/fleet/src/cmds/secrets/mod.rs
+++ b/cmds/fleet/src/cmds/secrets/mod.rs
@@ -6,16 +6,20 @@
};
use anyhow::{anyhow, bail, ensure, Context, Result};
use chrono::{DateTime, Utc};
-use clap::Parser;
+use clap::{error::ErrorKind, Parser};
+use crossterm::{terminal, tty::IsTty};
+use itertools::Itertools;
use owo_colors::OwoColorize;
use serde::Deserialize;
use std::{
collections::{BTreeSet, HashSet},
- io::{self, Cursor, Read},
+ ffi::OsString,
+ io::{self, stdin, Cursor, Read, Write},
path::PathBuf,
};
use tabled::{Table, Tabled};
-use tokio::fs::read_to_string;
+use tempfile::NamedTempFile;
+use tokio::{fs::read_to_string, process::Command};
use tracing::{error, info, info_span, warn, Instrument};
#[derive(Parser)]
@@ -586,7 +590,7 @@
{
Ok(v) => v,
Err(e) => {
- error!("{e}");
+ error!("{e:?}");
continue;
}
};
cmds/fleet/src/host.rsdiffbeforeafterboth--- a/cmds/fleet/src/host.rs
+++ b/cmds/fleet/src/host.rs
@@ -385,7 +385,7 @@
let config_unchecked_field = nix_go!(fleet_field.unchecked.config);
let import = nix_go!(builtins_field.import);
- let overlays = nix_go!(fleet_field.overlays);
+ let overlays = nix_go!(config_unchecked_field.overlays);
let nixpkgs = nix_go!(fleet_field.nixpkgs | import);
let default_pkgs = nix_go!(nixpkgs(Obj {
modules/fleet/secrets.nixdiffbeforeafterboth1{2 lib,3 fleetLib,4 config,5 ...6}:7with lib;8with fleetLib; let9 sharedSecret = with types; ({config, ...}: {10 options = {11 managed = mkOption {12 type = bool;13 description = ''14 Is this secret managed by configuration (I.e will work with reencrypt/etc), or it is configured by user15 '';16 };1718 expectedOwners = mkOption {19 type = nullOr (listOf str);20 description = ''21 List of hosts to encrypt secret for. null if managed by user (= via owners field from fleet.nix)2223 Secrets would be decrypted and stored to /run/secrets/$\{name} on owners24 '';25 default = null;26 };27 # TODO: Aren't those options may be just desugared to data/expectedData?28 regenerateOnOwnerAdded = mkOption {29 type = bool;30 description = ''31 Is this secret owner-dependent, and needs to be regenerated on ownership set change, or it may be just reencrypted.3233 You want to have this option set to true, when this secret contains some reference to its owners, i.e x509 SANs.34 '';35 };36 regenerateOnOwnerRemoved = mkOption {37 default = config.regenerateOnOwnerAdded;38 type = bool;39 description = ''40 Should this secret be removed on owner removal, or it may be just reencrypted4142 Most probably its value should be equal to regenerateOnOwnerAdded, override only if you know what are you doing.43 Contrary to regenerateOnOwnerAdded, you may want to set this option to false, when host permissions are revoked44 in some other way than by this secret ownership, I.e by firewall/etc.45 '';46 };47 generator = mkOption {48 type = nullOr unspecified;49 description = "Derivation to evaluate for secret generation";50 default = null;51 };52 createdAt = mkOption {53 type = nullOr str;54 description = "When this secret was (re)generated";55 default = null;56 };57 expiresAt = mkOption {58 type = nullOr str;59 description = "On which date this secret will expire, someone should regenerate this secret before it expires.";60 default = null;61 };6263 owners = mkOption {64 type = listOf str;65 description = ''66 For which owners this secret is currently encrypted,67 if not matches expectedOwners - then this secret is considered outdated, and68 should be regenerated/reencrypted.6970 Imported from fleet.nix71 '';72 default = [];73 };74 # TODO: Make secret generator generate arbitrary number of secret/public parts?75 # Make it generate a folder, where all files except suffixed by .enc are public, and the rest are secret?76 # How should modules refer to those files then?77 public = mkOption {78 type = nullOr str;79 description = "Secret public data. Imported from fleet.nix";80 default = null;81 };82 secret = mkOption {83 type = nullOr str;84 description = "Encrypted secret data. Imported from fleet.nix";85 default = null;86 internal = true;87 };88 };89 });90 hostSecret = with types; {91 options = {92 createdAt = mkOption {93 type = nullOr str;94 default = null;95 };96 expiresAt = mkOption {97 type = nullOr str;98 default = null;99 };100 public = mkOption {101 type = nullOr str;102 description = "Secret public data. Imported from fleet.nix";103 default = null;104 };105 secret = mkOption {106 type = nullOr str;107 description = "Encrypted secret data. Imported from fleet.nix";108 default = null;109 internal = true;110 };111 };112 };113in {114 options = with types; {115 sharedSecrets = mkOption {116 type = attrsOf (submodule sharedSecret);117 default = {};118 description = "Shared secrets";119 };120 hostSecrets = mkOption {121 type = attrsOf (attrsOf (submodule hostSecret));122 default = {};123 description = "Host secrets. Imported from fleet.nix";124 internal = true;125 };126 };127 config = {128 assertions =129 mapAttrsToList130 (name: secret: {131 assertion = secret.expectedOwners == null || builtins.sort (a: b: a < b) secret.owners == builtins.sort (a: b: a < b) secret.expectedOwners;132 message = "Shared secret ${name} is expected to be encrypted for ${builtins.toJSONsecret.expectedOwners}, but it is encrypted for ${builtins.toJSONsecret.owners}. Run fleet secrets regenerate to fix";133 })134 config.sharedSecrets;135 hosts = hostsToAttrs (host: {136 modules = let137 cleanupSecret = secretName: v: {138 inherit (v) public secret;139 shared = true;140 };141 in [142 {143 secrets =144 (145 mapAttrs cleanupSecret146 (filterAttrs (_: v: builtins.elem host v.owners) config.sharedSecrets)147 )148 // (mapAttrs cleanupSecret (config.hostSecrets.${host} or {}));149 }150 ];151 });152 # TODO: Should this attribute be moved to `nixpkgs.overlays`?153 overlays = [154 (final: prev: let155 lib = final.lib;156 inherit (lib) strings;157 inherit (strings) escapeShellArgs;158 in {159 mkEncryptSecret = {160 rage ? prev.rage,161 recipients,162 }:163 prev.writeShellScript "encryptor" ''164 #!/bin/sh165 exec ${rage}/bin/rage ${escapeShellArgsrecipients} -e "$@"166 '';167 # TODO: Move to fleet168 # TODO: Merge both generators to one with consistent options syntax?169 # Impure generator is built on local machine, then built closure is copied to remote machine,170 # and then it is ran in inpure context, so that this generator may access HSMs and other things.171 mkImpureSecretGenerator = {172 script,173 # If set - script will be run on remote machine, otherwise it will be run with fleet project in CWD174 # (Some secrets-encryption-in-git/managed PKI solution is expected)175 impureOn ? null,176 }:177 (prev.writeShellScript "impureGenerator.sh" ''178 #!/bin/sh179 set -eu180 cd /var/empty181182 created_at=date-u"%Y-%m-%dT%H:%M:%S.%NZ"183184 ${script}185186 if ! test -d $out; then187 echo "impure generator script did not produce expected \$out output"188 exit 1189 fi190191 echo -n $created_at > $out/created_at192 echo -n SUCCESS > $out/marker193 '')194 .overrideAttrs (old: {195 passthru = {196 inherit impureOn;197 generatorKind = "impure";198 };199 });200 # Pure generators are disabled for now201 mkSecretGenerator = {script}: final.mkImpureSecretGenerator {inherit script;};202203 # TODO: Implement consistent naming204 # Pure secret generator is supposed to be run entirely by nix, using `__impure` derivation type...205 # But for now, it is ran the same way as `impureSecretGenerator`, but on the local machine.206 # mkSecretGenerator = {script}:207 # (prev.writeShellScript "generator.sh" ''208 # #!/bin/sh209 # set -eu210 # # TODO: make nix daemon build secret, not just the script.211 # cd /var/empty212 #213 # created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")214 #215 # ${script}216 # if ! test -d $out; then217 # echo "impure generator script did not produce expected \$out output"218 # exit 1219 # fi220 #221 # echo -n $created_at > $out/created_at222 # echo -n SUCCESS > $out/marker223 # '')224 # .overrideAttrs (old: {225 # passthru = {226 # generatorKind = "pure";227 # };228 # # TODO: make nix daemon build secret, not just the script.229 # # __impure = true;230 # });231 })232 ];233 };234}1{2 lib,3 fleetLib,4 config,5 ...6}:7with lib;8with fleetLib; let9 sharedSecret = with types; ({config, ...}: {10 options = {11 managed = mkOption {12 type = bool;13 description = ''14 Is this secret managed by configuration (I.e will work with reencrypt/etc), or it is configured by user15 '';16 };1718 expectedOwners = mkOption {19 type = nullOr (listOf str);20 description = ''21 List of hosts to encrypt secret for. null if managed by user (= via owners field from fleet.nix)2223 Secrets would be decrypted and stored to /run/secrets/$\{name} on owners24 '';25 default = null;26 };27 # TODO: Aren't those options may be just desugared to data/expectedData?28 regenerateOnOwnerAdded = mkOption {29 type = bool;30 description = ''31 Is this secret owner-dependent, and needs to be regenerated on ownership set change, or it may be just reencrypted.3233 You want to have this option set to true, when this secret contains some reference to its owners, i.e x509 SANs.34 '';35 };36 regenerateOnOwnerRemoved = mkOption {37 default = config.regenerateOnOwnerAdded;38 type = bool;39 description = ''40 Should this secret be removed on owner removal, or it may be just reencrypted4142 Most probably its value should be equal to regenerateOnOwnerAdded, override only if you know what are you doing.43 Contrary to regenerateOnOwnerAdded, you may want to set this option to false, when host permissions are revoked44 in some other way than by this secret ownership, I.e by firewall/etc.45 '';46 };47 generator = mkOption {48 type = nullOr unspecified;49 description = "Derivation to evaluate for secret generation";50 default = null;51 };52 createdAt = mkOption {53 type = nullOr str;54 description = "When this secret was (re)generated";55 default = null;56 };57 expiresAt = mkOption {58 type = nullOr str;59 description = "On which date this secret will expire, someone should regenerate this secret before it expires.";60 default = null;61 };6263 owners = mkOption {64 type = listOf str;65 description = ''66 For which owners this secret is currently encrypted,67 if not matches expectedOwners - then this secret is considered outdated, and68 should be regenerated/reencrypted.6970 Imported from fleet.nix71 '';72 default = [];73 };74 # TODO: Make secret generator generate arbitrary number of secret/public parts?75 # Make it generate a folder, where all files except suffixed by .enc are public, and the rest are secret?76 # How should modules refer to those files then?77 public = mkOption {78 type = nullOr str;79 description = "Secret public data. Imported from fleet.nix";80 default = null;81 };82 secret = mkOption {83 type = nullOr str;84 description = "Encrypted secret data. Imported from fleet.nix";85 default = null;86 internal = true;87 };88 };89 });90 hostSecret = with types; {91 options = {92 createdAt = mkOption {93 type = nullOr str;94 default = null;95 };96 expiresAt = mkOption {97 type = nullOr str;98 default = null;99 };100 public = mkOption {101 type = nullOr str;102 description = "Secret public data. Imported from fleet.nix";103 default = null;104 };105 secret = mkOption {106 type = nullOr str;107 description = "Encrypted secret data. Imported from fleet.nix";108 default = null;109 internal = true;110 };111 };112 };113in {114 options = with types; {115 sharedSecrets = mkOption {116 type = attrsOf (submodule sharedSecret);117 default = {};118 description = "Shared secrets";119 };120 hostSecrets = mkOption {121 type = attrsOf (attrsOf (submodule hostSecret));122 default = {};123 description = "Host secrets. Imported from fleet.nix";124 internal = true;125 };126 };127 config = {128 assertions =129 mapAttrsToList130 (name: secret: {131 assertion = secret.expectedOwners == null || builtins.sort (a: b: a < b) secret.owners == builtins.sort (a: b: a < b) secret.expectedOwners;132 message = "Shared secret ${name} is expected to be encrypted for ${builtins.toJSONsecret.expectedOwners}, but it is encrypted for ${builtins.toJSONsecret.owners}. Run fleet secrets regenerate to fix";133 })134 config.sharedSecrets;135 hosts = hostsToAttrs (host: {136 modules = let137 cleanupSecret = secretName: v: {138 inherit (v) public secret;139 shared = true;140 };141 in [142 {143 secrets =144 (145 mapAttrs cleanupSecret146 (filterAttrs (_: v: builtins.elem host v.owners) config.sharedSecrets)147 )148 // (mapAttrs cleanupSecret (config.hostSecrets.${host} or {}));149 }150 ];151 });152 # TODO: Should this attribute be moved to `nixpkgs.overlays`?153 overlays = [154 (final: prev: let155 lib = final.lib;156 inherit (lib) strings concatMap;157 inherit (strings) escapeShellArgs;158 in {159 mkEncryptSecret = {160 rage ? prev.rage,161 recipients,162 }:163 prev.writeShellScript "encryptor" ''164 #!/bin/sh165 exec ${rage}/bin/rage ${escapeShellArgs(concatMap(r["-r"r])recipients)} -e "$@"166 '';167 # TODO: Move to fleet168 # TODO: Merge both generators to one with consistent options syntax?169 # Impure generator is built on local machine, then built closure is copied to remote machine,170 # and then it is ran in inpure context, so that this generator may access HSMs and other things.171 mkImpureSecretGenerator = {172 script,173 # If set - script will be run on remote machine, otherwise it will be run with fleet project in CWD174 # (Some secrets-encryption-in-git/managed PKI solution is expected)175 impureOn ? null,176 }:177 (prev.writeShellScript "impureGenerator.sh" ''178 #!/bin/sh179 set -eu180181 # TODO: Provide tempdir from outside, to make it securely erasurable as needed?182 tmp=mktemp-d183 cd $tmp184 # cd /var/empty185186 created_at=date-u"%Y-%m-%dT%H:%M:%S.%NZ"187188 ${script}189190 if ! test -d $out; then191 echo "impure generator script did not produce expected \$out output"192 exit 1193 fi194195 echo -n $created_at > $out/created_at196 echo -n SUCCESS > $out/marker197 '')198 .overrideAttrs (old: {199 passthru = {200 inherit impureOn;201 generatorKind = "impure";202 };203 });204 # Pure generators are disabled for now205 mkSecretGenerator = {script}: final.mkImpureSecretGenerator {inherit script;};206207 # TODO: Implement consistent naming208 # Pure secret generator is supposed to be run entirely by nix, using `__impure` derivation type...209 # But for now, it is ran the same way as `impureSecretGenerator`, but on the local machine.210 # mkSecretGenerator = {script}:211 # (prev.writeShellScript "generator.sh" ''212 # #!/bin/sh213 # set -eu214 # # TODO: make nix daemon build secret, not just the script.215 # cd /var/empty216 #217 # created_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%NZ")218 #219 # ${script}220 # if ! test -d $out; then221 # echo "impure generator script did not produce expected \$out output"222 # exit 1223 # fi224 #225 # echo -n $created_at > $out/created_at226 # echo -n SUCCESS > $out/marker227 # '')228 # .overrideAttrs (old: {229 # passthru = {230 # generatorKind = "pure";231 # };232 # # TODO: make nix daemon build secret, not just the script.233 # # __impure = true;234 # });235 })236 ];237 };238}