mirror of
https://github.com/Mic92/sops-nix.git
synced 2026-07-17 06:25:16 +08:00
sopsFiles
Allow multiple sopsFiles for easier secret sharing across multiple configurations
This commit is contained in:
parent
2fc3c9edc3
commit
3147535526
2 changed files with 79 additions and 10 deletions
|
|
@ -12,7 +12,10 @@ let
|
|||
secretType = types.submodule ({ config, ... }: {
|
||||
config = {
|
||||
sopsFile = lib.mkOptionDefault cfg.defaultSopsFile;
|
||||
#sopsFiles = lib.mkOptionDefault cfg.defaultSopsFiles;
|
||||
sopsFiles = lib.mkOptionDefault [];
|
||||
sopsFileHash = mkOptionDefault (optionalString cfg.validateSopsFiles "${builtins.hashFile "sha256" config.sopsFile}");
|
||||
sopsFilesHash = mkOptionDefault (optionals cfg.validateSopsFiles (forEach config.sopsFiles (builtins.hashFile "sha256")));
|
||||
};
|
||||
options = {
|
||||
name = mkOption {
|
||||
|
|
@ -77,6 +80,13 @@ let
|
|||
Sops file the secret is loaded from.
|
||||
'';
|
||||
};
|
||||
sopsFiles = mkOption {
|
||||
type = types.listOf types.path;
|
||||
defaultText = "\${config.sops.defaultSopsFile}";
|
||||
description = ''
|
||||
Sops file the secret is loaded from.
|
||||
'';
|
||||
};
|
||||
sopsFileHash = mkOption {
|
||||
type = types.str;
|
||||
readOnly = true;
|
||||
|
|
@ -84,6 +94,13 @@ let
|
|||
Hash of the sops file, useful in <xref linkend="opt-systemd.services._name_.restartTriggers" />.
|
||||
'';
|
||||
};
|
||||
sopsFilesHash = mkOption {
|
||||
type = types.listOf types.str;
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Hash of the sops file, useful in <xref linkend="opt-systemd.services._name_.restartTriggers" />.
|
||||
'';
|
||||
};
|
||||
restartUnits = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
|
|
@ -172,6 +189,13 @@ in {
|
|||
Default sops file used for all secrets.
|
||||
'';
|
||||
};
|
||||
defaultSopsFiles = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [ cfg.defaulSopsFile ];
|
||||
description = ''
|
||||
Default sops file used for all secrets.
|
||||
'';
|
||||
};
|
||||
|
||||
defaultSopsFormat = mkOption {
|
||||
type = types.str;
|
||||
|
|
@ -331,7 +355,7 @@ in {
|
|||
assertion = (filterAttrs (_: v: v.owner != "root" || v.group != "root") secretsForUsers) == {};
|
||||
message = "neededForUsers cannot be used for secrets that are not root-owned";
|
||||
}] ++ optionals cfg.validateSopsFiles (
|
||||
concatLists (mapAttrsToList (name: secret: [{
|
||||
(concatLists (mapAttrsToList (name: secret: [{
|
||||
assertion = builtins.pathExists secret.sopsFile;
|
||||
message = "Cannot find path '${secret.sopsFile}' set in sops.secrets.${strings.escapeNixIdentifier name}.sopsFile";
|
||||
} {
|
||||
|
|
@ -340,6 +364,22 @@ in {
|
|||
(builtins.isString secret.sopsFile && hasPrefix builtins.storeDir secret.sopsFile);
|
||||
message = "'${secret.sopsFile}' is not in the Nix store. Either add it to the Nix store or set sops.validateSopsFiles to false";
|
||||
}]) cfg.secrets)
|
||||
) ++ (concatLists (mapAttrsToList
|
||||
(name: secret:
|
||||
concatMap
|
||||
(sopsFile: [{
|
||||
assertion = builtins.pathExists sopsFile;
|
||||
message = "Cannot find path '${sopsFile}' set in sops.secrets.${strings.escapeNixIdentifier name}.sopsFiles";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
builtins.isPath sopsFile ||
|
||||
(builtins.isString sopsFile && hasPrefix builtins.storeDir sopsFile);
|
||||
message = "'${sopsFile}' is not in the Nix store. Either add it to the Nix store or set sops.validateSopsFiles to false";
|
||||
}])
|
||||
(toList (if (secret.sopsFiles == null) then [ ] else secret.sopsFiles)))
|
||||
cfg.secrets)
|
||||
)
|
||||
);
|
||||
|
||||
sops.environment.SOPS_GPG_EXEC = mkIf (cfg.gnupg.home != null) (mkDefault "${pkgs.gnupg}/bin/gpg");
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type secret struct {
|
|||
Owner string `json:"owner"`
|
||||
Group string `json:"group"`
|
||||
SopsFile string `json:"sopsFile"`
|
||||
SopsFiles []string `json:"sopsFiles"`
|
||||
Format FormatType `json:"format"`
|
||||
Mode string `json:"mode"`
|
||||
RestartUnits []string `json:"restartUnits"`
|
||||
|
|
@ -257,12 +258,16 @@ func recurseSecretKey(keys map[string]interface{}, wantedKey string) (string, er
|
|||
return strVal, nil
|
||||
}
|
||||
|
||||
func decryptSecret(s *secret, sourceFiles map[string]plainData) error {
|
||||
sourceFile := sourceFiles[s.SopsFile]
|
||||
func decryptSecretInner(s *secret, sopsFile *string, sourceFiles map[string]plainData) error {
|
||||
if sopsFile == nil {
|
||||
sopsFile = &s.SopsFile
|
||||
}
|
||||
|
||||
sourceFile := sourceFiles[*sopsFile]
|
||||
if sourceFile.data == nil || sourceFile.binary == nil {
|
||||
plain, err := decrypt.File(s.SopsFile, string(s.Format))
|
||||
plain, err := decrypt.File(*sopsFile, string(s.Format))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to decrypt '%s': %w", s.SopsFile, err)
|
||||
return fmt.Errorf("Failed to decrypt '%s': %w", *sopsFile, err)
|
||||
}
|
||||
|
||||
switch s.Format {
|
||||
|
|
@ -270,14 +275,14 @@ func decryptSecret(s *secret, sourceFiles map[string]plainData) error {
|
|||
sourceFile.binary = plain
|
||||
case Yaml:
|
||||
if err := yaml.Unmarshal(plain, &sourceFile.data); err != nil {
|
||||
return fmt.Errorf("Cannot parse yaml of '%s': %w", s.SopsFile, err)
|
||||
return fmt.Errorf("Cannot parse yaml of '%s': %w", *sopsFile, err)
|
||||
}
|
||||
case Json:
|
||||
if err := json.Unmarshal(plain, &sourceFile.data); err != nil {
|
||||
return fmt.Errorf("Cannot parse json of '%s': %w", s.SopsFile, err)
|
||||
return fmt.Errorf("Cannot parse json of '%s': %w", *sopsFile, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Secret of type %s in %s is not supported", s.Format, s.SopsFile)
|
||||
return fmt.Errorf("Secret of type %s in %s is not supported", s.Format, *sopsFile)
|
||||
}
|
||||
}
|
||||
switch s.Format {
|
||||
|
|
@ -286,11 +291,35 @@ func decryptSecret(s *secret, sourceFiles map[string]plainData) error {
|
|||
case Yaml, Json:
|
||||
strVal, err := recurseSecretKey(sourceFile.data, s.Key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err)
|
||||
return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, *sopsFile, err)
|
||||
}
|
||||
s.value = []byte(strVal)
|
||||
}
|
||||
sourceFiles[s.SopsFile] = sourceFile
|
||||
sourceFiles[*sopsFile] = sourceFile
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decryptSecret(s *secret, sourceFiles map[string]plainData) error {
|
||||
if len(s.SopsFiles) == 0 {
|
||||
if err := decryptSecretInner(s, &s.SopsFile, sourceFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Check SopsFiles in reverse order and use first matched secret
|
||||
for ii := len(s.SopsFiles) - 1; ii >= 0; ii-- {
|
||||
if err := decryptSecretInner(s, &s.SopsFiles[ii], sourceFiles); err != nil {
|
||||
if ii == 0 {
|
||||
// Secret not found in any of the SopsFiles
|
||||
// TODO: print SopsFiles
|
||||
return fmt.Errorf("secret %s not found in SopsFiles", s.Name)
|
||||
}
|
||||
} else {
|
||||
// Found the secret
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue