diff --git a/flake.nix b/flake.nix index fc9db7a..2bcabe4 100644 --- a/flake.nix +++ b/flake.nix @@ -27,15 +27,16 @@ checks = let nixosTests = (pkgs.callPackages ./nix/checks/nixos-test.nix { makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix"); - vaultAgentModule = self.nixosModules.vaultAgent; + inherit (self.nixosModules) vaultAgent systemdVaultd; }); in { treefmt = pkgs.callPackage ./nix/checks/treefmt.nix {}; - inherit (nixosTests) unittests vault-agent; + inherit (nixosTests) unittests vault-agent systemd-vaultd; }; }; flake.nixosModules = { vaultAgent = ./nix/modules/vault-agent.nix; + systemdVaultd = ./nix/modules/systemd-vaultd.nix; }; }; } diff --git a/nix/checks/dev-vault-server.nix b/nix/checks/dev-vault-server.nix new file mode 100644 index 0000000..2a60ede --- /dev/null +++ b/nix/checks/dev-vault-server.nix @@ -0,0 +1,54 @@ +{ config, lib, pkgs, ... }: + +{ + environment.systemPackages = [ pkgs.vault ]; + services.vault = { + enable = true; + dev = true; + devRootTokenID = "phony-secret"; + }; + environment.variables.VAULT_ADDR = "http://127.0.0.1:8200"; + environment.variables.VAULT_TOKEN = config.services.vault.devRootTokenID; + + 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.environment.variables.VAULT_TOKEN}" + "VAULT_ADDR=${config.environment.variables.VAULT_ADDR}" + ]; + }; + + 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" ]; + }; +} diff --git a/nix/checks/nixos-test.nix b/nix/checks/nixos-test.nix index b567f4a..838bdc6 100644 --- a/nix/checks/nixos-test.nix +++ b/nix/checks/nixos-test.nix @@ -1,7 +1,8 @@ { makeTest ? import , pkgs ? (import {}), - vaultAgentModule ? ../modules/vault-agent.nix + vaultAgent ? ../modules/vault-agent.nix, + systemdVaultd ? ../modules/systemd-vaultd.nix, }: let makeTest' = args: makeTest args { @@ -11,87 +12,130 @@ in { vault-agent = makeTest' { name = "vault-agent"; - nodes.server = {config, pkgs, ...}: { + nodes.server = { + config, + pkgs, + ... + }: { imports = [ - vaultAgentModule + vaultAgent + ./dev-vault-server.nix ]; - environment.systemPackages = [ pkgs.vault ]; - services.vault = { - enable = true; - dev = true; - devRootTokenID = "phony-secret"; + 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"; + }; + } + ]; + }; }; - systemd.services.setup-vault-agent-approle = { - path = [ pkgs.jq pkgs.vault pkgs.systemd ]; + }; + 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" + ''; + }; + systemd-vaultd = makeTest' { + name = "systemd-vaultd"; + nodes.server = { + config, + pkgs, + ... + }: { + imports = [ + vaultAgent + systemdVaultd + ./dev-vault-server.nix + ]; + + systemd.services.service1 = { wantedBy = ["multi-user.target"]; + script = '' + cat $CREDENTIALS_DIRECTORY/secret > /tmp/service1 + ''; serviceConfig = { Type = "oneshot"; - RemainAfterExit = "yes"; - Environment = [ - "VAULT_TOKEN=${config.services.vault.devRootTokenID}" - "VAULT_ADDR=http://127.0.0.1:8200" - ]; + RemainAfterExit = true; + LoadCredential = ["secret:/run/systemd-vaultd/sock"]; }; + }; + systemd.services.service2 = { + wantedBy = ["multi-user.target"]; 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 + cat $CREDENTIALS_DIRECTORY/secret > /tmp/service2 ''; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + LoadCredential = ["secret:/run/systemd-vaultd/sock"]; + }; }; - # 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"; - }; + template = [ + { + contents = ''{{ with secret "secret/my-secret" }}{{ .Data.data.foo }}{{ end }}''; + destination = "/run/systemd-vaultd/secrets/service1.service-secret"; + } + { + contents = ''{{ with secret "secret/blocking-secret" }}{{ .Data.data.foo }}{{ end }}''; + destination = "/run/systemd-vaultd/secrets/service2.service-secret"; + } + ]; auto_auth = { - method = [{ - type = "approle"; - config = { - role_id_file_path = "/tmp/roleID"; - secret_id_file_path = "/tmp/secretID"; - }; - }]; + 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") + machine.wait_for_unit("service1.service") + out = machine.succeed("cat /tmp/service1") print(out) assert out == "bar" + out = machine.succeed("systemctl list-jobs") + print(out) + assert "service2.service" in out, "service2 should be still blocked" + machine.succeed("vault kv put secret/blocking-secret foo=bar") + machine.wait_for_unit("service2.service") ''; }; unittests = makeTest' { diff --git a/nix/modules/systemd-vaultd.nix b/nix/modules/systemd-vaultd.nix new file mode 100644 index 0000000..ccdff6a --- /dev/null +++ b/nix/modules/systemd-vaultd.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, ... }: + +let + systemd-vaultd = pkgs.callPackage ../../default.nix {}; +in +{ + systemd.sockets.systemd-vaultd = { + description = "systemd-vaultd socket"; + wantedBy = [ "sockets.target" ]; + + socketConfig = { + ListenStream = "/run/systemd-vaultd/sock"; + SocketUser = "root"; + SocketMode = "0600"; + }; + }; + systemd.services.systemd-vaultd = { + description = "systemd-vaultd daemon"; + requires = [ "systemd-vaultd.socket" ]; + after = [ "systemd-vaultd.socket" ]; + serviceConfig = { + ExecStart = "${systemd-vaultd}/bin/systemd-vaultd"; + }; + }; +}