Skip to content

Commit eee831a

Browse files
jflymergify[bot]
authored andcommitted
Do not render templates when decrypting neededForUsers secrets
This fixes #659 In #649, we started rendering templates twice: 1. When rendering `neededForUsers` secrets (if there are any `neededForUsers` secrets). 2. When decrypting "regular" secrets. This alone was weird and wrong, but didn't cause issues for people until #655, which triggered #659. The cause is not super obvious: 1. When rendering `neededForUsers` secrets, we'd generate templates in `/run/secrets-for-users/rendered`. 2. However, the `path` for these templates is in `/run/secrets/rendered`, which is not inside of the `/run/secrets-for-users` directory we're dealing with, so we'd generate a symlink from `/run/secrets/rendered/<foo>` to `/run/secrets-for-users/rendered/<foo>`, which required making the parent directory of the symlink (`/run/secrets/rendered/`). 3. This breaks sops-nix's assumption that `/run/secrets` either doesn't exist, or is a symlink, and you get the symptoms described in <#659>. Reproducing this in a test was straightforward: just expand our existing template test to also have a `neededForUsers` secret. Fixing this was also straightforward: don't render templates during the `neededForUsers` phase (if we want to add support for `neededForUsers` templates in the future, that would be straightforward to do, but I opted not do that here).
1 parent 47fc1d8 commit eee831a

File tree

6 files changed

+32
-21
lines changed

6 files changed

+32
-21
lines changed

modules/home-manager/sops.nix

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
let
44
cfg = config.sops;
55
sops-install-secrets = (pkgs.callPackage ../.. {}).sops-install-secrets;
6-
secretType = lib.types.submodule ({ config, name, ... }: {
6+
secretType = lib.types.submodule ({ name, ... }: {
77
options = {
88
name = lib.mkOption {
99
type = lib.types.str;
@@ -71,10 +71,11 @@ let
7171
merge = lib.mergeEqualOption;
7272
};
7373

74-
manifestFor = suffix: secrets: pkgs.writeTextFile {
74+
manifestFor = suffix: secrets: templates: pkgs.writeTextFile {
7575
name = "manifest${suffix}.json";
7676
text = builtins.toJSON {
7777
secrets = builtins.attrValues secrets;
78+
templates = builtins.attrValues templates;
7879
secretsMountPoint = cfg.defaultSecretsMountPoint;
7980
symlinkPath = cfg.defaultSymlinkPath;
8081
keepGenerations = cfg.keepGenerations;
@@ -93,11 +94,11 @@ let
9394
'';
9495
};
9596

96-
manifest = manifestFor "" cfg.secrets;
97+
manifest = manifestFor "" cfg.secrets cfg.templates;
9798

9899
escapedAgeKeyFile = lib.escapeShellArg cfg.age.keyFile;
99100

100-
script = toString (pkgs.writeShellScript "sops-nix-user" ((lib.optionalString cfg.age.generateKey ''
101+
script = toString (pkgs.writeShellScript "sops-nix-user" (lib.optionalString cfg.age.generateKey ''
101102
if [[ ! -f ${escapedAgeKeyFile} ]]; then
102103
echo generating machine-specific age key...
103104
${pkgs.coreutils}/bin/mkdir -p $(${pkgs.coreutils}/bin/dirname ${escapedAgeKeyFile})
@@ -106,7 +107,7 @@ let
106107
fi
107108
'' + ''
108109
${sops-install-secrets}/bin/sops-install-secrets -ignore-passwd ${manifest}
109-
'')));
110+
''));
110111
in {
111112
options.sops = {
112113
secrets = lib.mkOption {

modules/sops/default.nix

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ let
88
inherit cfg;
99
inherit (pkgs) writeTextFile;
1010
};
11-
manifest = manifestFor "" regularSecrets {};
11+
manifest = manifestFor "" regularSecrets regularTemplates {};
1212

1313
pathNotInStore = lib.mkOptionType {
1414
name = "pathNotInStore";
@@ -20,6 +20,9 @@ let
2020

2121
regularSecrets = lib.filterAttrs (_: v: !v.neededForUsers) cfg.secrets;
2222

23+
# Currently, all templates are "regular" (there's no support for `neededForUsers` for templates.)
24+
regularTemplates = cfg.templates;
25+
2326
useSystemdActivation = (options.systemd ? sysusers && config.systemd.sysusers.enable) ||
2427
(options.services ? userborn && config.services.userborn.enable);
2528

@@ -354,7 +357,7 @@ in {
354357

355358
sops.environment.SOPS_GPG_EXEC = lib.mkIf (cfg.gnupg.home != null || cfg.gnupg.sshKeyPaths != []) (lib.mkDefault "${pkgs.gnupg}/bin/gpg");
356359

357-
# When using sysusers we no longer be started as an activation script because those are started in initrd while sysusers is started later.
360+
# When using sysusers we no longer are started as an activation script because those are started in initrd while sysusers is started later.
358361
systemd.services.sops-install-secrets = lib.mkIf (regularSecrets != { } && useSystemdActivation) {
359362
wantedBy = [ "sysinit.target" ];
360363
after = [ "systemd-sysusers.service" ];

modules/sops/manifest-for.nix

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
{ writeTextFile, cfg }:
22

3-
suffix: secrets: extraJson:
3+
suffix: secrets: templates: extraJson:
44

55
writeTextFile {
66
name = "manifest${suffix}.json";
77
text = builtins.toJSON ({
88
secrets = builtins.attrValues secrets;
9+
templates = builtins.attrValues templates;
910
# Does this need to be configurable?
1011
secretsMountPoint = "/run/secrets.d";
1112
symlinkPath = "/run/secrets";
@@ -15,7 +16,6 @@ writeTextFile {
1516
ageKeyFile = cfg.age.keyFile;
1617
ageSshKeyPaths = cfg.age.sshKeyPaths;
1718
useTmpfs = cfg.useTmpfs;
18-
templates = cfg.templates;
1919
placeholderBySecretName = cfg.placeholder;
2020
userMode = false;
2121
logging = {

modules/sops/secrets-for-users/default.nix

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
let
33
cfg = config.sops;
44
secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets;
5+
templatesForUsers = {}; # We do not currently support `neededForUsers` for templates.
56
manifestFor = pkgs.callPackage ../manifest-for.nix {
67
inherit cfg;
78
inherit (pkgs) writeTextFile;
89
};
910
withEnvironment = import ../with-environment.nix {
1011
inherit cfg lib;
1112
};
12-
manifestForUsers = manifestFor "-for-users" secretsForUsers {
13+
manifestForUsers = manifestFor "-for-users" secretsForUsers templatesForUsers {
1314
secretsMountPoint = "/run/secrets-for-users.d";
1415
symlinkPath = "/run/secrets-for-users";
1516
};

pkgs/sops-install-secrets/main.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ type template struct {
7171

7272
type manifest struct {
7373
Secrets []secret `json:"secrets"`
74-
Templates map[string]*template `json:"templates"`
74+
Templates []template `json:"templates"`
7575
PlaceholderBySecretName map[string]string `json:"placeholderBySecretName"`
7676
SecretsMountPoint string `json:"secretsMountPoint"`
7777
SymlinkPath string `json:"symlinkPath"`
@@ -185,7 +185,7 @@ func linksAreEqual(linkTarget, targetFile string, info os.FileInfo, owner int, g
185185
return linkTarget == targetFile && validUG
186186
}
187187

188-
func symlinkSecret(targetFile string, path string, owner int, group int, userMode bool) error {
188+
func createSymlink(targetFile string, path string, owner int, group int, userMode bool) error {
189189
for {
190190
stat, err := os.Lstat(path)
191191
if os.IsNotExist(err) {
@@ -217,7 +217,7 @@ func symlinkSecret(targetFile string, path string, owner int, group int, userMod
217217
}
218218
}
219219

220-
func symlinkSecrets(targetDir string, secrets []secret, templates map[string]*template, userMode bool) error {
220+
func symlinkSecretsAndTemplates(targetDir string, secrets []secret, templates []template, userMode bool) error {
221221
for _, secret := range secrets {
222222
targetFile := filepath.Join(targetDir, secret.Name)
223223
if targetFile == secret.Path {
@@ -227,7 +227,7 @@ func symlinkSecrets(targetDir string, secrets []secret, templates map[string]*te
227227
if err := os.MkdirAll(parent, os.ModePerm); err != nil {
228228
return fmt.Errorf("cannot create parent directory of '%s': %w", secret.Path, err)
229229
}
230-
if err := symlinkSecret(targetFile, secret.Path, secret.owner, secret.group, userMode); err != nil {
230+
if err := createSymlink(targetFile, secret.Path, secret.owner, secret.group, userMode); err != nil {
231231
return fmt.Errorf("failed to symlink secret '%s': %w", secret.Path, err)
232232
}
233233
}
@@ -241,7 +241,7 @@ func symlinkSecrets(targetDir string, secrets []secret, templates map[string]*te
241241
if err := os.MkdirAll(parent, os.ModePerm); err != nil {
242242
return fmt.Errorf("cannot create parent directory of '%s': %w", template.Path, err)
243243
}
244-
if err := symlinkSecret(targetFile, template.Path, template.owner, template.group, userMode); err != nil {
244+
if err := createSymlink(targetFile, template.Path, template.owner, template.group, userMode); err != nil {
245245
return fmt.Errorf("failed to symlink template '%s': %w", template.Path, err)
246246
}
247247
}
@@ -610,8 +610,9 @@ func (app *appContext) validateSecret(secret *secret) error {
610610
return app.validateSopsFile(secret, &file)
611611
}
612612

613-
func renderTemplates(templates map[string]*template, secretByPlaceholder map[string]*secret) {
614-
for _, template := range templates {
613+
func renderTemplates(templates []template, secretByPlaceholder map[string]*secret) {
614+
for i := range templates {
615+
template := &templates[i]
615616
rendered := renderTemplate(&template.content, secretByPlaceholder)
616617
template.value = []byte(rendered)
617618
}
@@ -702,7 +703,8 @@ func (app *appContext) validateManifest() error {
702703
}
703704
}
704705

705-
for _, template := range m.Templates {
706+
for i := range m.Templates {
707+
template := &m.Templates[i]
706708
if err := app.validateTemplate(template); err != nil {
707709
return err
708710
}
@@ -893,7 +895,7 @@ func symlinkWalk(filename string, linkDirname string, walkFn filepath.WalkFunc)
893895
return filepath.Walk(filename, symWalkFunc)
894896
}
895897

896-
func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, secretDir string, secrets []secret, templates map[string]*template) error {
898+
func handleModifications(isDry bool, logcfg loggingConfig, symlinkPath string, secretDir string, secrets []secret, templates []template) error {
897899
var restart []string
898900
var reload []string
899901

@@ -1148,7 +1150,7 @@ func replaceRuntimeDir(path, rundir string) (ret string) {
11481150
return
11491151
}
11501152

1151-
func writeTemplates(targetDir string, templates map[string]*template, keysGID int, userMode bool) error {
1153+
func writeTemplates(targetDir string, templates []template, keysGID int, userMode bool) error {
11521154
for _, template := range templates {
11531155
fp := filepath.Join(targetDir, template.Name)
11541156

@@ -1302,7 +1304,7 @@ func installSecrets(args []string) error {
13021304
if isDry {
13031305
return nil
13041306
}
1305-
if err := symlinkSecrets(manifest.SymlinkPath, manifest.Secrets, manifest.Templates, manifest.UserMode); err != nil {
1307+
if err := symlinkSecretsAndTemplates(manifest.SymlinkPath, manifest.Secrets, manifest.Templates, manifest.UserMode); err != nil {
13061308
return fmt.Errorf("failed to prepare symlinks to secret store: %w", err)
13071309
}
13081310
if err := atomicSymlink(*secretDir, manifest.SymlinkPath); err != nil {

pkgs/sops-install-secrets/nixos-test.nix

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,10 @@ in {
266266
age.keyFile = "/run/age-keys.txt";
267267
defaultSopsFile = ./test-assets/secrets.yaml;
268268
secrets.test_key = { };
269+
270+
# Verify that things work even with `neededForUsers` secrets. See
271+
# <https://github.com/Mic92/sops-nix/issues/659>.
272+
secrets."nested/test/file".neededForUsers = true;
269273
};
270274

271275
# must run before sops sets up keys

0 commit comments

Comments
 (0)