From 97b656163a789c2f5ef70f82bc46ec7d25a54a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 5 Apr 2023 11:07:59 +0200 Subject: [PATCH] get rid systemd patches --- README.md | 8 +++ cmd/systemd-vaultd-update-secrets/main.go | 77 +++++++++++++++++++++++ flake.nix | 2 - nix/checks/systemd-vaultd-test.nix | 61 ++++++++++++------ nix/checks/unittests.nix | 3 +- nix/modules/systemd-vaultd.nix | 50 +++++++++------ nix/pkgs/systemd.nix | 14 ----- 7 files changed, 160 insertions(+), 55 deletions(-) create mode 100644 cmd/systemd-vaultd-update-secrets/main.go delete mode 100644 nix/pkgs/systemd.nix diff --git a/README.md b/README.md index 50456cc..0c8f2a4 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,14 @@ files: `systemd-vaultd.service`, `systemd-vaultd.socket`: make install ``` +## Known limitations + +systemd's LoadCredential option will not update credentials if a service is +reloaded. However systemd-vaultd called `systemd-vaultd-update-secrets` comes +with a helper program that can write secrets from the json file generated by +systemd-vaultd to a directory readable by the service. Checkout +`systemd-vaultd/nix/checks/systemd-vaultd-test.nix` for more details. + ## License Copyright (c) 2022 [Jörg Thalheim](https://github.com/mic92) and contributors. diff --git a/cmd/systemd-vaultd-update-secrets/main.go b/cmd/systemd-vaultd-update-secrets/main.go new file mode 100644 index 0000000..22ff597 --- /dev/null +++ b/cmd/systemd-vaultd-update-secrets/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "path" + "syscall" + "time" +) + +const ( + systemdVaultdir = "/run/systemd-vaultd/secrets" +) + +func updateSecrets(credentialsDirectory, target string) error { + // get systemd service name from credentials directory + serviceName := path.Base(credentialsDirectory) + stat, err := os.Stat(credentialsDirectory) + if err != nil { + return fmt.Errorf("failed to stat %s: %w", credentialsDirectory, err) + } + // inherit the owner and group of the credentials directory + uid := stat.Sys().(*syscall.Stat_t).Uid + gid := stat.Sys().(*syscall.Stat_t).Gid + + jsonPath := path.Join(credentialsDirectory, fmt.Sprintf("%s.json", serviceName)) + var content []byte + for i := 0; i < 10; i++ { + content, err = os.ReadFile(jsonPath) + if err != nil { + if os.IsNotExist(err) { + // wait for the file to be created + fmt.Printf("waiting for %s to be created", jsonPath) + time.Sleep(1 * time.Second) + continue + } + return fmt.Errorf("failed to read vault json file %s: %w", serviceName, err) + } + break + } + var data map[string]interface{} + if err := json.Unmarshal(content, &data); err != nil { + return fmt.Errorf("failed to unmarshal json from %s: %w", jsonPath, err) + } + for key, value := range data { + targetPath := path.Join(target, key) + err := os.MkdirAll(path.Dir(targetPath), 0o700) + os.Chown(path.Dir(targetPath), int(uid), int(gid)) + + if err != nil { + return fmt.Errorf("failed to create directory %s: %w", path.Dir(targetPath), err) + } + os.WriteFile(targetPath, []byte(value.(string)), 0o400) + os.Chown(targetPath, int(uid), int(gid)) + } + + return nil +} + +func main() { + if len(os.Args) != 2 { + fmt.Println("Usage: systemd-vaultd-update-secrets ") + os.Exit(1) + } + credentialsDirectory := os.Getenv("CREDENTIALS_DIRECTORY") + if credentialsDirectory == "" { + fmt.Println("CREDENTIALS_DIRECTORY environment variable must be set") + os.Exit(1) + } + + target := os.Args[1] + if err := updateSecrets(credentialsDirectory, target); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/flake.nix b/flake.nix index f4b5a46..c50ab55 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,6 @@ , ... }: { packages.default = pkgs.callPackage ./default.nix { }; - packages.systemd = pkgs.callPackage ./nix/pkgs/systemd.nix { }; devShells.default = pkgs.mkShellNoCC { buildInputs = with pkgs; [ python3.pkgs.pytest @@ -35,7 +34,6 @@ go just config.treefmt.build.wrapper - config.packages.systemd ]; }; diff --git a/nix/checks/systemd-vaultd-test.nix b/nix/checks/systemd-vaultd-test.nix index d474473..b7dafe3 100644 --- a/nix/checks/systemd-vaultd-test.nix +++ b/nix/checks/systemd-vaultd-test.nix @@ -2,7 +2,6 @@ name = "systemd-vaultd"; nodes.server = { config - , pkgs , ... }: { imports = [ @@ -10,6 +9,9 @@ ../modules/systemd-vaultd.nix ./dev-vault-server.nix ]; + # speed up tests + virtualisation.cores = 4; + virtualisation.memorySize = 1024; systemd.services.service1 = { wantedBy = [ "multi-user.target" ]; @@ -33,17 +35,33 @@ }; }; + users.users.service2 = { + isSystemUser = true; + group = "service2"; + uid = 1000; + }; + users.groups.service2.gid = 1000; + systemd.services.service2 = { wantedBy = [ "multi-user.target" ]; + preStart = '' + cp -r $CREDENTIALS_DIRECTORY /run/service2/secrets + ''; script = '' set -x while true; do - cat $CREDENTIALS_DIRECTORY/secret > /tmp/service2 + cat /run/service2/secrets/secret >&2 || : + cat /run/service2/secrets/secret > /tmp/service2 || : sleep 0.1 done ''; - serviceConfig.ExecReload = "${pkgs.coreutils}/bin/true"; - serviceConfig.LoadCredential = [ "secret:/run/systemd-vaultd/sock" ]; + serviceConfig = { + ExecReload = "+${config.services.systemd-vaultd.package}/bin/systemd-vaultd-update-secrets /run/service2/secrets"; + User = "service2"; + Group = "service2"; + LoadCredential = [ "secret:/run/systemd-vaultd/sock" ]; + RuntimeDirectory = "service2"; + }; vault = { template = '' {{ with secret "secret/blocking-secret" }}{{ scratch.MapSet "secrets" "secret" .Data.data.foo }}{{ end }} @@ -77,32 +95,37 @@ machine.wait_for_open_port(8200) machine.wait_for_unit("setup-vault-agent-approle.service") - out = machine.wait_until_succeeds("cat /tmp/service1") - print(out) - assert out == "bar", f"{out} != bar for service1" + out = machine.wait_until_succeeds("grep -q bar /tmp/service1") - out = machine.succeed("cat /tmp/service1-env") - print(out) - assert out == "bar", f"{out} != bar for service1-env" + out = machine.succeed("grep -q bar /tmp/service1-env") - out = machine.succeed("systemctl status service2") + out = machine.succeed("systemctl status service2 || :") print(out) assert "(sd-mkdcreds)" in out, "service2 should be still blocked" machine.succeed("vault kv put secret/blocking-secret foo=bar") - out = machine.wait_until_succeeds("cat /tmp/service2") - print(out) - assert out == "bar", f"{out} != bar for service2" + machine.wait_until_succeeds("grep -q bar /tmp/service2 >&2") - machine.succeed("vault kv put secret/blocking-secret foo=reload") + machine.succeed("umount /run/credentials/service2.service") machine.succeed("rm /run/systemd-vaultd/secrets/service2.service.json") + + machine.succeed("vault kv put secret/blocking-secret foo=reload") + machine.succeed("systemctl restart vault-agent-default") machine.wait_until_succeeds("cat /run/systemd-vaultd/secrets/service2.service.json >&2") - machine.succeed("systemctl reload service2") + machine.succeed("systemctl restart service2") machine.succeed("rm /tmp/service2") - out = machine.wait_until_succeeds("cat /tmp/service2") - print(out) - assert out == "reload", f"{out} != reload for service2" + machine.wait_until_succeeds("grep -q reload /tmp/service2 >&2") + + # get uid and gid + out = machine.succeed("stat -c %u /run/service2/secrets/secret").strip() + assert out == "1000", "service2 should have access to secret file with uid 1000, got " + out + out = machine.succeed("stat -c %g /run/service2/secrets/secret").strip() + assert out == "1000", "service2 should have access to secret file with gid 1000, got " + out + + # get permissions in octal + out = machine.succeed("stat -c %a /run/service2/secrets/secret").strip() + assert out == "400", "service2 should have access to secret file with permissions 0400, got " + out ''; } diff --git a/nix/checks/unittests.nix b/nix/checks/unittests.nix index eeb14f8..199e246 100644 --- a/nix/checks/unittests.nix +++ b/nix/checks/unittests.nix @@ -3,11 +3,10 @@ , pkgs , lib , coreutils -, +, systemd }: let systemd-vaultd = pkgs.callPackage ../../default.nix { }; - systemd = pkgs.callPackage ../pkgs/systemd.nix { }; in writeShellScript "unittests" '' set -eu -o pipefail diff --git a/nix/modules/systemd-vaultd.nix b/nix/modules/systemd-vaultd.nix index fdc5589..aa8e7ef 100644 --- a/nix/modules/systemd-vaultd.nix +++ b/nix/modules/systemd-vaultd.nix @@ -1,4 +1,6 @@ { pkgs +, lib +, config , ... }: let @@ -8,27 +10,39 @@ in imports = [ ./vault-secrets.nix ]; + options = { + services.systemd-vaultd = { + package = lib.mkOption { + type = lib.types.package; + default = systemd-vaultd; + defaultText = "pkgs.systemd-vaultd"; + description = '' + The package to use for systemd-vaultd + ''; + }; + }; + }; - systemd.package = pkgs.callPackage ../pkgs/systemd.nix { }; - - systemd.sockets.systemd-vaultd = { - description = "systemd-vaultd socket"; - wantedBy = [ "sockets.target" ]; + config = { + systemd.sockets.systemd-vaultd = { + description = "systemd-vaultd socket"; + wantedBy = [ "sockets.target" ]; - socketConfig = { - ListenStream = "/run/systemd-vaultd/sock"; - SocketUser = "root"; - SocketMode = "0600"; + 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" ]; - # Restarting can break services waiting for secrets - stopIfChanged = false; - serviceConfig = { - ExecStart = "${systemd-vaultd}/bin/systemd-vaultd"; + systemd.services.systemd-vaultd = { + description = "systemd-vaultd daemon"; + requires = [ "systemd-vaultd.socket" ]; + after = [ "systemd-vaultd.socket" ]; + # Restarting can break services waiting for secrets + stopIfChanged = false; + serviceConfig = { + ExecStart = "${config.services.systemd-vaultd.package}/bin/systemd-vaultd"; + }; }; }; } diff --git a/nix/pkgs/systemd.nix b/nix/pkgs/systemd.nix deleted file mode 100644 index 8a402fb..0000000 --- a/nix/pkgs/systemd.nix +++ /dev/null @@ -1,14 +0,0 @@ -{ systemd -, fetchpatch -, -}: -systemd.overrideAttrs (old: { - patches = - old.patches - ++ [ - (fetchpatch { - url = "https://github.com/Mic92/systemd/commit/93a2921a81cab3be9b7eacab6b0095c96a0ae9e2.patch"; - sha256 = "sha256-7WlhMLE7sfD3Cxn6n6R1sUNzUOvas7XMyabi3bsq7jM="; - }) - ]; -})