diff --git a/flake.nix b/flake.nix index f52f560..fc9db7a 100644 --- a/flake.nix +++ b/flake.nix @@ -24,20 +24,18 @@ }: { packages.default = pkgs.callPackage ./default.nix {}; devShells.default = pkgs.callPackage ./shell.nix {}; - checks = { + checks = let + nixosTests = (pkgs.callPackages ./nix/checks/nixos-test.nix { + makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix"); + vaultAgentModule = self.nixosModules.vaultAgent; + }); + in { treefmt = pkgs.callPackage ./nix/checks/treefmt.nix {}; - inherit - (pkgs.callPackages ./nix/checks/nixos-test.nix { - makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix"); - }) - unittests - ; + inherit (nixosTests) unittests vault-agent; }; }; - flake = { - # The usual flake attributes can be defined here, including system- - # agnostic ones like nixosModule and system-enumerating ones, although - # those are more easily expressed in perSystem. + flake.nixosModules = { + vaultAgent = ./nix/modules/vault-agent.nix; }; }; } diff --git a/nix/checks/nixos-test.nix b/nix/checks/nixos-test.nix index d409908..b567f4a 100644 --- a/nix/checks/nixos-test.nix +++ b/nix/checks/nixos-test.nix @@ -1,6 +1,7 @@ { makeTest ? import , pkgs ? (import {}), + vaultAgentModule ? ../modules/vault-agent.nix }: let makeTest' = args: makeTest args { @@ -8,12 +9,94 @@ inherit (pkgs) system; }; in { - unittests = makeTest' { - name = "unitests"; - nodes.server = {pkgs, ...}: { - # Important to get the systemd service running for root - #environment.variables.XDG_RUNTIME_DIR = "/run/user/0"; + vault-agent = makeTest' { + name = "vault-agent"; + nodes.server = {config, pkgs, ...}: { + imports = [ + vaultAgentModule + ]; + + environment.systemPackages = [ pkgs.vault ]; + services.vault = { + enable = true; + dev = true; + devRootTokenID = "phony-secret"; + }; + systemd.services.setup-vault-agent-approle = { + path = [ pkgs.jq pkgs.vault pkgs.systemd ]; + wantedBy = ["multi-user.target"]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + Environment = [ + "VAULT_TOKEN=${config.services.vault.devRootTokenID}" + "VAULT_ADDR=http://127.0.0.1:8200" + ]; + }; + + script = '' + set -eux -o pipefail + while ! vault status; do + sleep 1 + done + + # capabilities of our vault agent + cat > /tmp/policy-file.hcl < /tmp/roleID + echo -n $(vault write -force -format json auth/approle/role/role1/secret-id | jq -r .data.secret_id) > /tmp/secretID + ''; + }; + # Make sure our setup service is started before our vault-agent + systemd.services.vault-agent-test = { + wants = [ "setup-vault-agent-approle.service" ]; + after = [ "setup-vault-agent-approle.service" ]; + }; + services.vault.agents.test.settings = { + vault = { + address = "http://localhost:8200"; + }; + template = { + contents = ''{{ with secret "secret/my-secret" }}{{ .Data.data.foo }}{{ end }}''; + destination = "/run/render.txt"; + }; + + auto_auth = { + method = [{ + type = "approle"; + config = { + role_id_file_path = "/tmp/roleID"; + secret_id_file_path = "/tmp/secretID"; + }; + }]; + }; + }; }; + testScript = '' + start_all() + machine.wait_for_unit("multi-user.target") + machine.wait_for_unit("vault.service") + machine.wait_for_open_port(8200) + machine.wait_for_unit("setup-vault-agent-approle.service") + + # It should be able to write our template + out = machine.wait_until_succeeds("cat /run/render.txt") + print(out) + assert out == "bar" + ''; + }; + unittests = makeTest' { + name = "unittests"; + nodes.server = {}; testScript = '' start_all() diff --git a/nix/modules/vault-agent.nix b/nix/modules/vault-agent.nix new file mode 100644 index 0000000..5851805 --- /dev/null +++ b/nix/modules/vault-agent.nix @@ -0,0 +1,83 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.vault; + settingsFormat = pkgs.formats.json {}; + + autoAuthMethodModule = lib.types.submodule { + freeformType = lib.types.attrsOf lib.types.unspecified; + + options = { + type = lib.mkOption { + type = lib.types.str; + }; + + config = lib.mkOption { + type = lib.types.attrsOf lib.types.unspecified; + }; + }; + }; + + autoAuthModule = lib.types.submodule { + freeformType = lib.types.attrsOf lib.types.unspecified; + + options = { + method = lib.mkOption { + type = lib.types.listOf autoAuthMethodModule; + default = [ ]; + }; + }; + }; + + templateConfigModule = lib.types.submodule { + freeformType = lib.types.attrsOf lib.types.unspecified; + + options = { + exit_on_retry_failure = lib.mkOption { + type = lib.types.bool; + default = true; + }; + }; + }; + + agentConfigType = lib.types.submodule { + freeformType = lib.types.attrsOf lib.types.unspecified; + + options = { + auto_auth = lib.mkOption { + type = autoAuthModule; + default = { }; + }; + + template_config = lib.mkOption { + type = templateConfigModule; + default = { }; + }; + }; + }; +in +{ + options.services.vault.agents = lib.mkOption { + default = {}; + description = "Instances of vault agent"; + type = lib.types.attrsOf (lib.types.submodule { + options = { + settings = lib.mkOption { + description = "agent configuration"; + type = agentConfigType; + }; + }; + }); + }; + config = { + systemd.services = lib.mapAttrs' (name: instanceCfg: lib.nameValuePair "vault-agent-${name}" ({ + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + # Needs getent in PATH + path = [ pkgs.glibc ]; + serviceConfig = { + Restart = "on-failure"; + ExecStart = "${pkgs.vault}/bin/vault agent -config=${settingsFormat.generate "agent.json" instanceCfg.settings}"; + }; + })) cfg.agents; + }; +}