diff --git a/nix/flakeModule.nix b/nix/flakeModule.nix index 214707e..63cdb26 100644 --- a/nix/flakeModule.nix +++ b/nix/flakeModule.nix @@ -16,14 +16,6 @@ in default = [ ]; description = "A list of master keys for encrypting secrets."; }; - homeIdentities = mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ - "laptop-home" - "wawa-wawa-home" - ]; - }; secretsDir = mkOption { type = types.str; default = null; @@ -56,47 +48,68 @@ in config = let secrets = builtins.fromJSON (builtins.readFile "${inputs.self}/${cfg.secretsDir}/secrets.json"); - all_keys = lib.flatten (lib.concatAttrValues per_host_keys); - per_host_keys = lib.mergeAttrsList ( + + toKeyList = + v: + if builtins.isString v then + [ v ] + else if builtins.isList v then + v + else + throw "Unexpected type ${builtins.typeOf v} for sopsPublic"; + + # Every host across every system: hostName -> [host pubkeys]. + host_keys = lib.mergeAttrsList ( lib.flatten ( map ( - x: - builtins.mapAttrs ( - name: value: - let - v = value.sopsPublic or [ ]; - type = builtins.typeOf v; - vv = - if type == "string" then - [ v ] - else if type == "list" then - v - else - throw "Unexpected type ${type} for sopsPublic in host ${name}"; - in - vv - ) x + perSystem: + lib.mapAttrsToList (_: host: { ${host.hostName} = toKeyList (host.sopsPublic or [ ]); }) perSystem ) (builtins.attrValues den.hosts) ) ); + + # Every user on every host is a home identity "@". + home_keys = lib.mergeAttrsList ( + lib.flatten ( + map ( + perSystem: + lib.mapAttrsToList ( + _: host: + lib.mapAttrsToList ( + _: user: { "${host.hostName}@${user.userName}" = toKeyList (user.sopsPublic or [ ]); } + ) (host.users or { }) + ) perSystem + ) (builtins.attrValues den.hosts) + ) + ); + + # Replaces the old hand-maintained `homeIdentities` option. + homeIdentities = builtins.attrNames home_keys; + + # A secret's `hosts` may target either a host or a home identity. + identity_keys = host_keys // home_keys; + all_host_keys = lib.flatten (lib.attrValues host_keys); + all_home_keys = lib.flatten (lib.attrValues home_keys); secret_map = lib.mapAttrs ( name: value: let sopskeys = lib.unique ( - lib.flatten (map (k: per_host_keys.${k}) value.hosts) - ++ (lib.optionals value.global all_keys) + lib.flatten (map (k: identity_keys.${k}) value.hosts) + ++ (lib.optionals value.globalHosts all_host_keys) + ++ (lib.optionals value.globalHomes all_home_keys) ++ cfg.masterKeys ); # Descriptive flag: is this secret consumed by a home-manager user? - # True when it is global or targets any identity in `homeIdentities`. - home = value.global || lib.any (h: lib.elem h cfg.homeIdentities) value.hosts; + # True when it is global to homes or targets any home identity. + home = value.globalHomes || lib.any (h: lib.elem h homeIdentities) value.hosts; in { inherit (value) format neededForUsers hosts - global + globalHosts + globalHomes ; keys = value.keys or [ ]; inherit sopskeys home; @@ -114,7 +127,7 @@ in lib.mapAttrsToList ( name: value: let - hasHost = (lib.elem host value.hosts) || value.global; + hasHost = (lib.elem host value.hosts) || value.globalHosts; isYamlOrJson = value.format == "yaml" || value.format == "json"; in ( @@ -158,7 +171,7 @@ in lib.mapAttrsToList ( name: value: let - hasHost = (lib.elem identity value.hosts) || value.global; + hasHost = (lib.elem identity value.hosts) || value.globalHomes; isYamlOrJson = value.format == "yaml" || value.format == "json"; in ( @@ -193,8 +206,8 @@ in flake.secretsManifest = { secretsDir = cfg.secretsDir; masterKeys = cfg.masterKeys; - hosts = lib.mapAttrs (_: keys: { inherit keys; }) per_host_keys; - homeIdentities = cfg.homeIdentities; + hosts = lib.mapAttrs (_: keys: { inherit keys; }) host_keys; + inherit homeIdentities; secrets = secret_map; }; secrets.nixosModule = { diff --git a/src/manifest.rs b/src/manifest.rs index e2df43d..abec29c 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -25,7 +25,10 @@ pub struct HostInfo { #[derive(Debug, Clone, Deserialize)] pub struct SecretInfo { pub format: SecretFormat, - pub global: bool, + #[serde(rename = "globalHosts")] + pub global_hosts: bool, + #[serde(rename = "globalHomes")] + pub global_homes: bool, pub hosts: Vec, #[serde(rename = "neededForUsers")] pub needed_for_users: bool, diff --git a/src/ui.rs b/src/ui.rs index 6bfddc0..c4395ca 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -81,16 +81,17 @@ fn render_secret_detail(state: &App, area: Rect, buf: &mut Buffer) { ])); // Scope - let scope = if secret.global { - "global".to_string() - } else { - "host-specific".to_string() + let scope = match (secret.global_hosts, secret.global_homes) { + (true, true) => "global (hosts + homes)".to_string(), + (true, false) => "global (hosts)".to_string(), + (false, true) => "global (homes)".to_string(), + (false, false) => "host-specific".to_string(), }; lines.push(Line::from(vec![ Span::styled(" Scope: ", Style::default().fg(Color::DarkGray)), Span::styled(scope, Style::default().fg(Color::White)), ])); - if !secret.global { + if !(secret.global_hosts && secret.global_homes) { // Hosts / identities. Home-manager identities are coloured distinctly // so it is obvious which targets are user (home) rather than system. let mut host_spans: Vec = vec![Span::styled(