Skip to content
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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

h33p
Copy link

@h33p h33p commented Sep 15, 2024

Description of changes

  • Redefine devices to accept either a list of strings, or an attrset.
  • If attrset, allow specifying encryptionPassword as a reference to password to use for device.
  • Modify activation script to read all encryption passwords and concatinate them to JSON without hitting nix store.

Closes #121286

cc @kira-bruneau @WillPower3309 @bjornfor

Things done

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • Tested, as applicable:
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 24.11 Release Notes (or backporting 23.11 and 24.05 Release notes)
    • (Module updates) Added a release notes entry if the change is significant
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

@github-actions github-actions bot added 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: module (update) This PR changes an existing module in `nixos/` labels Sep 15, 2024
@NixOSInfra NixOSInfra added the 12. first-time contribution This PR is the author's first one; please be gentle! label Sep 15, 2024
@ofborg ofborg bot added 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin 10.rebuild-linux: 1-10 labels Sep 15, 2024
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.
@h33p h33p force-pushed the 121286_syncthing_encryption_password branch from 9540845 to 0bc8c16 Compare September 19, 2024 15:22
@h33p h33p force-pushed the 121286_syncthing_encryption_password branch from 494338f to 3c04dff Compare September 30, 2024 09:38
@github-actions github-actions bot added 8.has: documentation This PR adds or changes documentation 8.has: changelog labels Sep 30, 2024
@h33p h33p marked this pull request as ready for review September 30, 2024 09:49
@h33p h33p changed the title syncthing: define and handle encryptionPassword option nixos/syncthing: define and handle encryptionPassword option Sep 30, 2024
@wegank wegank added the 2.status: merge conflict This PR has merge conflicts with the target branch label Nov 10, 2024
Copy link
Contributor

@jfly jfly left a 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.
Copy link
Contributor

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 [
Copy link
Contributor

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 {
Copy link
Contributor

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:

Suggested change
encryptionPassword = mkOption {
encryptionPasswordFile = mkOption {

(types.listOf types.str)
(types.attrsOf (types.submodule ({ name, ... }: {
freeformType = settingsFormat.type;
options = {
Copy link
Contributor

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
Copy link
Contributor

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 strs? (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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aPerhaps a bit stricter?

Suggested change
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")
Copy link
Contributor

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:

Suggested change
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;
Copy link
Contributor

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!)

Comment on lines +944 to +948
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 {};
Copy link
Contributor

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
Copy link
Contributor

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:

Suggested change
if [ ! -r ${path} ]; then
if [ ! -r "${path}" ]; then

@jfly
Copy link
Contributor

jfly commented Feb 19, 2025

I've rebased the changes in the PR and implemented changes according to my feedback above in #383442

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.status: merge conflict This PR has merge conflicts with the target branch 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: changelog 8.has: documentation This PR adds or changes documentation 8.has: module (update) This PR changes an existing module in `nixos/` 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin 10.rebuild-linux: 1-10 12. first-time contribution This PR is the author's first one; please be gentle!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feature request: syncthing: add support for untrusted folders
4 participants