-
-
Notifications
You must be signed in to change notification settings - Fork 15k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nixos/syncthing: define and handle encryptionPassword option #342138
base: master
Are you sure you want to change the base?
Conversation
Rewrite the syncthing config update script to embed secrets into the json request. Specifically, we handle the `encryptionPassword` secret. With this code, the user can embed path to the encrpyption password for a given device the folder is shared with, and have it loaded in, without touching the nix store.
- Change `folder.devices` type into `oneOf [(listOf str) (attrsOf (submodule { ... }))]`. - Expose `encryptionPassord` within the attrSet of the devices option. This allows the user to set the encrpyption password use to share the folder's data with. We do this by file path, as opposed to string literal, because we do not want to embed the encrpyption password into the nix store.
9540845
to
0bc8c16
Compare
494338f
to
3c04dff
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work here. I'd really love to have this feature!
@@ -518,6 +518,8 @@ | |||
- `nix.channel.enable = false` no longer implies `nix.settings.nix-path = []`. | |||
Since Nix 2.13, a `nix-path` set in `nix.conf` cannot be overriden by the `NIX_PATH` configuration variable. | |||
|
|||
- `services.syncthing` now accepts an `attrSet` for `folders[<folder>].devices`, allowing to set `encryptionPassword` file for a device. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Is the "official" term actually attrset
? I see that on nix.dev, at least: https://nix.dev/tutorials/packaging-existing-software.html (emphasis mine):
Only add the top-level xorg derivation to the input attrset, rather than the full xorg.libX11, as the latter would cause a syntax error.
@@ -378,11 +445,30 @@ in { | |||
}; | |||
|
|||
devices = mkOption { | |||
type = types.listOf types.str; | |||
type = types.oneOf [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think it would be slightly more convenient to restructure this as a listOf (oneOf [str (submodule ...)]
. Then people would be able to intermix string devices alongside structured devices with encryption passwords. For example:
devices = [
"deviceId1"
{
deviceId = "deviceId2";
encryptionPassword = "/path/to/secret";
}
];
Edit: Just saw your comment here:
Not sure how applicable it is to change the type of devices, but the current way where it's a list of strings is relatively clunky and differs from syncthing's structure. But that can be changed.
That's fair. To address my concern I propose that we deprecate the old way with the intent of moving entirely to an attrset in the future. We don't have to do the deprecation in this PR, though.
(types.attrsOf (types.submodule ({ name, ... }: { | ||
freeformType = settingsFormat.type; | ||
options = { | ||
encryptionPassword = mkOption { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: There are probably counterexamples out there, but I've always seen these types of settings with a "File" suffix:
encryptionPassword = mkOption { | |
encryptionPasswordFile = mkOption { |
(types.listOf types.str) | ||
(types.attrsOf (types.submodule ({ name, ... }: { | ||
freeformType = settingsFormat.type; | ||
options = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: We should add deviceId
here as well. I'm pretty sure it counts as a "valuable option" (to use the terminology from RFC42)
if builtins.isString device then | ||
{ deviceId = cfg.settings.devices.${device}.id; } | ||
else | ||
device |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand how we'd ever end up here. With the code in this PR, if devices
is a list, then isn't it guaranteed to be a list of str
s? (I am proposing changing that below)
|
||
# B should be able to decrypt, check that content of file matches | ||
b.wait_for_file("/var/lib/syncthing/bar/plainname") | ||
b.succeed("grep plaincontent /var/lib/syncthing/bar/plainname") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aPerhaps a bit stricter?
b.succeed("grep plaincontent /var/lib/syncthing/bar/plainname") | |
assert "plaincontent" == b.succeed("cat /var/lib/syncthing/bar/plainname") |
(not sure if there will be a trailing newline in there or not)
b.succeed("grep plaincontent /var/lib/syncthing/bar/plainname") | ||
|
||
# Bar on C is untrusted, check that content is not in cleartext | ||
c.fail("grep -R plaincontent /var/lib/syncthing/bar") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could fail if the file doesn't exist. I'd suggest something like this:
c.fail("grep -R plaincontent /var/lib/syncthing/bar") | |
assert "plaincontent" not in c.succeed("cat /var/lib/syncthing/bar") |
secretVarsScript = generateSecretVars secretPaths; | ||
|
||
jsonString = builtins.toJSON resolved_cfg; | ||
escapedJson = builtins.replaceStrings ["\""] ["\\\""] jsonString; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why aren't we using escapeShellArg
anymore?
edit: Nevermind, I think I see now. We're generating a JSON string that happens to have interpolatable bash variables in it. This feels problematic to me. Is there a chance there are other $
variables just randomly in our resulting JSON, and letting bash try to treat them as variables is going to do the wrong thing (either error out, or worse, silently return an empty string)?
I experimented with a different approach that doesn't rely upon bash string interpolation. What do you think of this? e2ce674 (I've confirmed that nix-build --attr nixosTests.syncthing-folders
still passes with this change. Thanks for writing the test!)
syncthing = handleTest ./syncthing/default.nix {}; | ||
syncthing-no-settings = handleTest ./syncthing/no-settings.nix {}; | ||
syncthing-init = handleTest ./syncthing/init.nix {}; | ||
syncthing-many-devices = handleTest ./syncthing/many-devices.nix {}; | ||
syncthing-folders = handleTest ./syncthing/folders.nix {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These renames are a nice refactor to do, but they're also conflict prone (I think it's the reason this PR is currently conflicting). Suggestion: do this in a separate, simpler PR that can get merged quickly.
varName = "secret_${builtins.hashString "sha256" path}"; | ||
in | ||
'' | ||
if [ ! -r ${path} ]; then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't path
have spaces in it?
$ test -r /foo bar
test: too many arguments
$ test -r "/foo bar"
Quotes seem necessary to me:
if [ ! -r ${path} ]; then | |
if [ ! -r "${path}" ]; then |
I've rebased the changes in the PR and implemented changes according to my feedback above in #383442 |
Description of changes
devices
to accept either a list of strings, or an attrset.encryptionPassword
as a reference to password to use for device.Closes #121286
cc @kira-bruneau @WillPower3309 @bjornfor
Things done
./result/bin/
)Add a 👍 reaction to pull requests you find important.