mirror of
https://github.com/Mic92/sops-nix.git
synced 2025-04-09 18:34:12 +00:00
parent
517628cc1d
commit
af29ac4d84
3 changed files with 95 additions and 0 deletions
|
@ -110,6 +110,7 @@ let
|
|||
# Does this need to be configurable?
|
||||
secretsMountPoint = "/run/secrets.d";
|
||||
symlinkPath = "/run/secrets";
|
||||
keepGenerations = cfg.keepGenerations;
|
||||
gnupgHome = cfg.gnupg.home;
|
||||
sshKeyPaths = cfg.gnupg.sshKeyPaths;
|
||||
ageKeyFile = cfg.age.keyFile;
|
||||
|
@ -164,6 +165,14 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
keepGenerations = mkOption {
|
||||
type = types.ints.unsigned;
|
||||
default = 1;
|
||||
description = ''
|
||||
Number of secrets generations to keep. Setting this to 0 disables pruning.
|
||||
'';
|
||||
};
|
||||
|
||||
log = mkOption {
|
||||
type = types.listOf (types.enum [ "keyImport" "secretChanges" ]);
|
||||
default = [ "keyImport" "secretChanges" ];
|
||||
|
|
|
@ -51,6 +51,7 @@ type manifest struct {
|
|||
Secrets []secret `json:"secrets"`
|
||||
SecretsMountPoint string `json:"secretsMountPoint"`
|
||||
SymlinkPath string `json:"symlinkPath"`
|
||||
KeepGenerations int `json:"keepGenerations"`
|
||||
SSHKeyPaths []string `json:"sshKeyPaths"`
|
||||
GnupgHome string `json:"gnupgHome"`
|
||||
AgeKeyFile string `json:"ageKeyFile"`
|
||||
|
@ -554,6 +555,47 @@ func atomicSymlink(oldname, newname string) error {
|
|||
return os.RemoveAll(d)
|
||||
}
|
||||
|
||||
func pruneGenerations(secretsMountPoint, secretsDir string, keepGenerations int) error {
|
||||
if keepGenerations == 0 {
|
||||
return nil // Nothing to prune
|
||||
}
|
||||
|
||||
// Prepare our failsafe
|
||||
currentGeneration, err := strconv.Atoi(path.Base(secretsDir))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Logic error, current generation is not numeric: %w", err)
|
||||
}
|
||||
|
||||
// Read files in the mount directory
|
||||
file, err := os.Open(secretsMountPoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot open %s: %w", secretsMountPoint, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
generations, err := file.Readdirnames(0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot read %s: %w", secretsMountPoint, err)
|
||||
}
|
||||
for _, generationName := range generations {
|
||||
generationNum, err := strconv.Atoi(generationName)
|
||||
// Not a number? Not relevant
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Not strictly necessary but a good failsafe to
|
||||
// make sure we don't prune the current generation
|
||||
if generationNum == currentGeneration {
|
||||
continue
|
||||
}
|
||||
if currentGeneration-keepGenerations >= generationNum {
|
||||
os.RemoveAll(path.Join(secretsMountPoint, generationName))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func importSSHKeys(logcfg loggingConfig, keyPaths []string, gpgHome string) error {
|
||||
secringPath := filepath.Join(gpgHome, "secring.gpg")
|
||||
|
||||
|
@ -920,6 +962,9 @@ func installSecrets(args []string) error {
|
|||
if err := atomicSymlink(*secretDir, manifest.SymlinkPath); err != nil {
|
||||
return fmt.Errorf("Cannot update secrets symlink: %w", err)
|
||||
}
|
||||
if err := pruneGenerations(manifest.SecretsMountPoint, *secretDir, manifest.KeepGenerations); err != nil {
|
||||
return fmt.Errorf("Cannot prune old secrets generations: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
|
|
|
@ -51,6 +51,47 @@
|
|||
inherit (pkgs) system;
|
||||
};
|
||||
|
||||
pruning = makeTest {
|
||||
name = "sops-pruning";
|
||||
machine = { lib, ... }: {
|
||||
imports = [ ../../modules/sops ];
|
||||
sops = {
|
||||
age.keyFile = ./test-assets/age-keys.txt;
|
||||
defaultSopsFile = ./test-assets/secrets.yaml;
|
||||
secrets.test_key = {};
|
||||
keepGenerations = lib.mkDefault 0;
|
||||
};
|
||||
|
||||
specialisation.pruning.configuration.sops.keepGenerations = 10;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
# Force us to generation 100
|
||||
machine.succeed("mkdir /run/secrets.d/{2..99} /run/secrets.d/non-numeric")
|
||||
machine.succeed("ln -fsn /run/secrets.d/99 /run/secrets")
|
||||
machine.succeed("/run/current-system/activate")
|
||||
machine.succeed("test -d /run/secrets.d/100")
|
||||
|
||||
# Ensure nothing is pruned, these are just random numbers
|
||||
machine.succeed("test -d /run/secrets.d/1")
|
||||
machine.succeed("test -d /run/secrets.d/90")
|
||||
machine.succeed("test -d /run/secrets.d/non-numeric")
|
||||
|
||||
machine.succeed("/run/current-system/specialisation/pruning/bin/switch-to-configuration test")
|
||||
print(machine.succeed("ls -la /run/secrets.d/"))
|
||||
|
||||
# Ensure stuff was properly pruned.
|
||||
# We are now at generation 101 so 92 must exist when we keep 10 generations
|
||||
# and 91 must not.
|
||||
machine.fail("test -d /run/secrets.d/91")
|
||||
machine.succeed("test -d /run/secrets.d/92")
|
||||
machine.succeed("test -d /run/secrets.d/non-numeric")
|
||||
'';
|
||||
} {
|
||||
inherit pkgs;
|
||||
inherit (pkgs) system;
|
||||
};
|
||||
|
||||
age-keys = makeTest {
|
||||
name = "sops-age-keys";
|
||||
machine = {
|
||||
|
|
Loading…
Add table
Reference in a new issue