diff options
-rw-r--r-- | consumers/users.nix | 11 | ||||
-rw-r--r-- | contracts/secrets.nix | 99 | ||||
-rw-r--r-- | default.nix | 59 | ||||
-rw-r--r-- | modules/userSecret.nix | 20 | ||||
-rw-r--r-- | providers/asecret.nix | 127 |
5 files changed, 38 insertions, 278 deletions
diff --git a/consumers/users.nix b/consumers/users.nix deleted file mode 100644 index 9e4f401..0000000 --- a/consumers/users.nix +++ /dev/null @@ -1,11 +0,0 @@ -{ lib, config, ... }: -{ - options = { - userPasswords.secrets.consumer = lib.mkOption { - type = config.contracts.secrets.provider; - }; - }; - config = { - users.users.root.passwordFile = config.userPasswords.secrets.consumer.output.path; - }; -} diff --git a/contracts/secrets.nix b/contracts/secrets.nix deleted file mode 100644 index 89b6466..0000000 --- a/contracts/secrets.nix +++ /dev/null @@ -1,99 +0,0 @@ -{ lib, ... }: -{ - contracts.secrets = { - meta = { - maintainer = [ "cindi" ]; - description = "provide 'secrets'"; - }; - - input = { - options.type = lib.mkOption { - type = lib.types.str; - default = "random-password"; - }; - }; - - output = { - options.path = lib.mkOption { - type = lib.types.path; - description = '' - Path to the file containing the secret generated out of band. - This path will exist after deploying to a target host, - it is not available through the nix store. - ''; - }; - }; - - behaviorTest = { providerRoot, extraModules ? [] }: { - nodes.machine = { config, ... }: { - imports = extraModules; - - options.test = { - owner = lib.mkOption { - type = lib.types.str; - default = "root"; - }; - - group = lib.mkOption { - type = lib.types.str; - default = "root"; - }; - - mode = lib.mkOption { - type = lib.types.str; - default = "0400"; - }; - - content = lib.mkOption { - type = lib.types.str; - default = "a super secret secret!"; - }; - }; - - config = lib.mkMerge [ - (lib.setAttrByPath providerRoot { - # We set consumer.input and not input directly because the latter is readOnly. - consumer.input = { - inherit (config.test) owner group mode; - }; - }) - (lib.mkIf (config.test.owner != "root") { - users.users.${config.test.owner}.isNormalUser = true; - }) - (lib.mkIf (config.test.group != "root") { - users.groups.${config.test.group} = {}; - }) - ]; - }; - - testScript = { nodes, ... }: - let - cfg = nodes.machine; - inherit (lib.getAttrFromPath providerRoot nodes.machine) output; - in - '' - owner = machine.succeed("stat -c '%U' ${output.path}").strip() - print(f"Got owner {owner}") - if owner != "${cfg.test.owner}": - raise Exception(f"Owner should be '${cfg.test.owner}' but got '{owner}'") - - group = machine.succeed("stat -c '%G' ${output.path}").strip() - print(f"Got group {group}") - if group != "${cfg.test.group}": - raise Exception(f"Group should be '${cfg.test.group}' but got '{group}'") - - mode = str(int(machine.succeed("stat -c '%a' ${output.path}").strip())) - print(f"Got mode {mode}") - wantedMode = str(int("${cfg.test.mode}")) - if mode != wantedMode: - raise Exception(f"Mode should be '{wantedMode}' but got '{mode}'") - - content = machine.succeed("cat ${output.path}").strip() - print(f"Got content {content}") - if content != "${cfg.test.content}": - raise Exception(f"Content should be '${cfg.test.content}' but got '{content}'") - ''; - }; - }; - -} diff --git a/default.nix b/default.nix index 5fe0306..03a67f4 100644 --- a/default.nix +++ b/default.nix @@ -3,47 +3,24 @@ }: with (import ./lib {}); eval { - machines.bob = { self, config, ... }: { - imports = [ - ./consumers/users.nix - ./contracts/secrets.nix - ./providers/asecret.nix - ]; - networking.hostName = "bob"; - asecret.secrets.provider = config.userPasswords.secrets; - userPasswords.secrets.consumer = config.asecret.secrets; - }; + machines.bob.imports = [ + ({ self, config, ... }: { + imports = [ + "${sources.nixpkgs}/nixos/modules/testing/hardcodedSecret.nix" + ./modules/userSecret.nix + ]; + networking.hostName = "bob"; + + testing.hardcodedSecret.rootPassword = { + secret.consumer = config.users.users.root.passwordSecret; + content = "nixos"; + }; + + users.users.root.passwordSecret.provider = + config.testing.hardcodedSecret.rootPassword.secret; + }) + ]; machines.alice = { networking.hostName = "alice"; }; -} // -(let - lib = pkgs.lib; - config = {}; -in -{ - test = - let - inherit ((import ./contracts/secrets.nix { - inherit lib; - }).contracts.secrets) behaviorTest; - in - pkgs.testers.runNixOSTest ({ - name = "contracts-filebackup-restic"; - meta.maintainers = [ lib.maintainers.ibizaman ]; - # I tried using the following line but it leads to infinite recursion. - # Instead, I made a hacky import. pkgs.callPackage was also giving an - # infinite recursion. - # - # } // config.contracts.secret.behaviorTest { - # - } // behaviorTest { - providerRoot = [ "testing" "asecret" "mysecret" "secret" ]; - extraModules = [ - ./providers/asecret.nix - ({ config, ... }: { - testing.asecret.mysecret.content = config.test.content; - }) - ]; - }); -}) +} diff --git a/modules/userSecret.nix b/modules/userSecret.nix new file mode 100644 index 0000000..af1e978 --- /dev/null +++ b/modules/userSecret.nix @@ -0,0 +1,20 @@ +# "secret" consumer +{ config, lib, ... }: +{ + options.users.users = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule { + options.passwordSecret = lib.mkOption { + type = lib.types.nullOr config.contracts.secret.consumer; + }; + }); + }; + config = { + # TODO other users than root + users.users.root.passwordFile = lib.mkIf (config.users.users.root.passwordSecret != null) config.users.users.root.passwordSecret.output.path; + users.users.root.passwordSecret.input = lib.mkIf (config.users.users.root.passwordSecret != null) { + owner = "root"; + group = "root"; + mode = "0400"; + }; + }; +} diff --git a/providers/asecret.nix b/providers/asecret.nix deleted file mode 100644 index df7fe20..0000000 --- a/providers/asecret.nix +++ /dev/null @@ -1,127 +0,0 @@ -{ config, lib, pkgs, ... }: -let - cfg = config.asecret; - - inherit (lib) mapAttrs' mkOption nameValuePair; - inherit (lib.types) attrsOf nullOr str submodule; - inherit (pkgs) writeText; -in -{ - options.asecret = mkOption { - default = {}; - description = '' - A secret. These should only be used in tests. - ''; - - example = lib.literalExpression '' - { - mySecret = { - secret.input = { - user = "me"; - mode = "0400"; - restartUnits = [ "myservice.service" ]; - }; - settings.content = "My Secret"; - }; - } - ''; - type = attrsOf (submodule (mod@{ name, options, ... }: { - options = { - mode = mkOption { - description = '' - Mode of the secret file. - ''; - type = str; - default = "0400"; - }; - - owner = mkOption { - description = '' - Linux user owning the secret file. - ''; - type = str; - }; - - group = mkOption { - description = '' - Linux group owning the secret file. - ''; - type = str; - default = options.user.default; - defaultText = "user"; - }; - - content = mkOption { - type = nullOr str; - description = '' - Content of the secret as a string. - - This will be stored in the nix store and should only be used for testing or maybe in dev. - ''; - default = null; - }; - - source = mkOption { - type = nullOr str; - description = '' - Source of the content of the secret as a path in the nix store. - ''; - default = null; - }; - - path = mkOption { - type = str; - description = '' - Path where the secret should be located. - ''; - default = "/run/hardcodedSecrets/hardcodedSecret_${name}"; - }; - - secrets = mkOption { - type = config.contracts.secrets.provider; - }; - }; - - config = { - inherit (mod.config.secret.input) mode owner group; - secret.output.path = mod.config.path; - }; - })); - }; - - config = { - system.activationScripts = mapAttrs' (n: cfg': - let - source = if cfg'.source != null - then cfg'.source - else writeText "hardcodedSecret_${n}_content" cfg'.content; - in - nameValuePair "hardcodedSecret_${n}" '' - ( - set -e - mkdir -p "$(dirname "${cfg'.path}")" - touch "${cfg'.path}" - chmod ${cfg'.mode} "${cfg'.path}" - chown ${cfg'.owner}:${cfg'.group} "${cfg'.path}" - cp ${source} "${cfg'.path}" - ) || echo "Failed to create hardcoded secret at ${cfg'.path}" - '' - ) cfg; - }; - - # Without `meta.buildDocsInSandbox = false;`, I get: - # - # > error: attribute 'contracts' missing - # > at /nix/store/2gd9yzcfpqqp00vskxlqq4ds48mpgdzv-nixos/modules/testing/hardcodedSecret.nix:81:18: - # > 80| secret = mkOption { - # > 81| type = config.contracts.secret.provider; - # > | ^ - # > 82| }; - # > Cacheable portion of option doc build failed. - # > Usually this means that an option attribute that ends up in documentation (eg `default` or `description`) depends on the restricted module arguments `config` or `pkgs`. - # > - # > Rebuild your configuration with `--show-trace` to find the offending location. Remove the references to restricted arguments (eg by escaping their antiquotations or adding a `defaultText`) or disable the sandboxed build for the failing module by setting `meta.buildDocsInSandbox = false`. - # - # With the line, I don't get the warning but still get the missing 'contracts' attribute error. - meta.buildDocsInSandbox = false; -} |