add systemd-vaultd nixos module and test

main
Jörg Thalheim 2 years ago
parent 88d2fbd55d
commit 46bc2aa7a1

@ -27,15 +27,16 @@
checks = let checks = let
nixosTests = (pkgs.callPackages ./nix/checks/nixos-test.nix { nixosTests = (pkgs.callPackages ./nix/checks/nixos-test.nix {
makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix"); makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix");
vaultAgentModule = self.nixosModules.vaultAgent; inherit (self.nixosModules) vaultAgent systemdVaultd;
}); });
in { in {
treefmt = pkgs.callPackage ./nix/checks/treefmt.nix {}; treefmt = pkgs.callPackage ./nix/checks/treefmt.nix {};
inherit (nixosTests) unittests vault-agent; inherit (nixosTests) unittests vault-agent systemd-vaultd;
}; };
}; };
flake.nixosModules = { flake.nixosModules = {
vaultAgent = ./nix/modules/vault-agent.nix; vaultAgent = ./nix/modules/vault-agent.nix;
systemdVaultd = ./nix/modules/systemd-vaultd.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 <<EOF
path "secret/data/*" {
capabilities = ["read"]
}
EOF
vault policy write demo /tmp/policy-file.hcl
vault kv put secret/my-secret foo=bar
# role for our vault agent
vault auth enable approle
vault write auth/approle/role/role1 bind_secret_id=true token_policies=demo
echo -n $(vault read -format json auth/approle/role/role1/role-id | jq -r .data.role_id) > /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" ];
};
}

@ -1,7 +1,8 @@
{ {
makeTest ? import <nixpkgs/nixos/tests/make-test-python.nix>, makeTest ? import <nixpkgs/nixos/tests/make-test-python.nix>,
pkgs ? (import <nixpkgs> {}), pkgs ? (import <nixpkgs> {}),
vaultAgentModule ? ../modules/vault-agent.nix vaultAgent ? ../modules/vault-agent.nix,
systemdVaultd ? ../modules/systemd-vaultd.nix,
}: let }: let
makeTest' = args: makeTest' = args:
makeTest args { makeTest args {
@ -11,87 +12,130 @@
in { in {
vault-agent = makeTest' { vault-agent = makeTest' {
name = "vault-agent"; name = "vault-agent";
nodes.server = {config, pkgs, ...}: { nodes.server = {
config,
pkgs,
...
}: {
imports = [ imports = [
vaultAgentModule vaultAgent
./dev-vault-server.nix
]; ];
environment.systemPackages = [ pkgs.vault ]; services.vault.agents.test.settings = {
services.vault = { vault = {
enable = true; address = "http://localhost:8200";
dev = true; };
devRootTokenID = "phony-secret"; 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"]; wantedBy = ["multi-user.target"];
script = ''
cat $CREDENTIALS_DIRECTORY/secret > /tmp/service1
'';
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
RemainAfterExit = "yes"; RemainAfterExit = true;
Environment = [ LoadCredential = ["secret:/run/systemd-vaultd/sock"];
"VAULT_TOKEN=${config.services.vault.devRootTokenID}"
"VAULT_ADDR=http://127.0.0.1:8200"
];
}; };
};
systemd.services.service2 = {
wantedBy = ["multi-user.target"];
script = '' script = ''
set -eux -o pipefail cat $CREDENTIALS_DIRECTORY/secret > /tmp/service2
while ! vault status; do
sleep 1
done
# capabilities of our vault agent
cat > /tmp/policy-file.hcl <<EOF
path "secret/data/my-secret" {
capabilities = ["read"]
}
EOF
vault policy write demo /tmp/policy-file.hcl
vault kv put secret/my-secret foo=bar
# role for our vault agent
vault auth enable approle
vault write auth/approle/role/role1 bind_secret_id=true token_policies=demo
echo -n $(vault read -format json auth/approle/role/role1/role-id | jq -r .data.role_id) > /tmp/roleID
echo -n $(vault write -force -format json auth/approle/role/role1/secret-id | jq -r .data.secret_id) > /tmp/secretID
''; '';
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 = { services.vault.agents.test.settings = {
vault = { vault = {
address = "http://localhost:8200"; address = "http://localhost:8200";
}; };
template = { template = [
contents = ''{{ with secret "secret/my-secret" }}{{ .Data.data.foo }}{{ end }}''; {
destination = "/run/render.txt"; 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 = { auto_auth = {
method = [{ method = [
type = "approle"; {
config = { type = "approle";
role_id_file_path = "/tmp/roleID"; config = {
secret_id_file_path = "/tmp/secretID"; role_id_file_path = "/tmp/roleID";
}; secret_id_file_path = "/tmp/secretID";
}]; };
}
];
}; };
}; };
}; };
testScript = '' testScript = ''
start_all() start_all()
machine.wait_for_unit("multi-user.target")
machine.wait_for_unit("vault.service") machine.wait_for_unit("vault.service")
machine.wait_for_open_port(8200) machine.wait_for_open_port(8200)
machine.wait_for_unit("setup-vault-agent-approle.service") machine.wait_for_unit("setup-vault-agent-approle.service")
machine.wait_for_unit("service1.service")
# It should be able to write our template out = machine.succeed("cat /tmp/service1")
out = machine.wait_until_succeeds("cat /run/render.txt")
print(out) print(out)
assert out == "bar" 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' { unittests = makeTest' {

@ -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";
};
};
}
Loading…
Cancel
Save