From 1d0b01520d62d0d57d9a05556f2f2463ec4b4917 Mon Sep 17 00:00:00 2001 From: Alexander Bantyev Date: Fri, 17 Mar 2023 16:46:31 +0400 Subject: [PATCH 1/7] Add nix-store-acls RFC --- rfcs/0143-nix-store-acls.md | 195 ++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 rfcs/0143-nix-store-acls.md diff --git a/rfcs/0143-nix-store-acls.md b/rfcs/0143-nix-store-acls.md new file mode 100644 index 000000000..a524843fd --- /dev/null +++ b/rfcs/0143-nix-store-acls.md @@ -0,0 +1,195 @@ +--- +feature: nix-store-acls +start-date: 2023-01-16 +author: Alexander Bantyev +co-authors: Silvan Mosberger, Théophane Hufschmitt +shepherd-team: (names, to be nominated and accepted by RFC steering committee) +shepherd-leader: (name to be appointed by RFC steering committee) +related-issues: (will contain links to implementation PRs) +--- + +# Summary +[summary]: #summary + +Implement a way to only allow user access to a store path if they provide proof that they have all the necessary sources available, or had the access permission explicitly granted to them. + +# Motivation +[motivation]: #motivation + +Currently, the Nix Store on a local machine is world-listable and world-readable. + +This means that it's not possible to share a machine between multiple users who want to build proprietary software they don't want other people to look at. + +Also, it makes storing secrets in the Nix store even more dangerous then it could be. + +Finally, it means that substituter access is all-or-nothing: either a user can access the cache and download everything there is in there just by knowing store paths (even without having the source code or Nix expressions available), or they can't download anything. + +# Detailed design +[design]: #detailed-design + +Change the implementation of the Nix daemon (and, potentially, nix-serve, depending on the chosen "Remote Store" implementation) to store access control lists as metadata, automatically update them when users provide proof that they have the necessary source, and allow `trusted-user`s to manipulate those ACLs manually. + +This should ensure that this change is as seamless as possible for the users: they will still always be able to execute `nix build` or similar for derivations where they have access to all the sources, substituting as much as possible, as though nothing had changed. +The only major difference would be that `/nix/store` itself is now not readable. +If a user needs to be able to access some store paths without having access to their sources (e.g. for proprietary software where sharing the artifacts is ok but sharing the sources isn't), such access can be granted explicitly by the administrators (`trusted-user`'s). + +## Local store + +For a local store, we keep a list of local (POSIX) users who have access to each store path as path metadata. +When the relevant config option (perhaps `acl`) is enabled, this ACL (access control list) is enforced, meaning that only users on that list can read the store path, execute files from it, or use it as a dependency in their derivations. +We also add a less strict mode (`selective-acl`) in which derivations are only protected if they are marked as such. + +For paths external to the Nix sandbox (added via `nix store add-{file,path}`, paths in the Nix language, `builtins.fetch*`, or flake sources), we add the user to the list when they send the path to the daemon. +We might need to add a flag (like `--protect`) for the selective ACL mode. + +For derivations themselves (.drv files), we add the user to the list when they send the derivation to the daemon. + +For derivation outputs, we add user to the list when the user requests to realise the derivation and has access to the transitive dependencies, including the sources. + +When `selective-acl` is enabled, protected paths should not be readable by anyone during the build. +Necessary permissions are granted after the build. + +There also should be a way to enable the protection for selective ACLs (perhaps `nix store access protect`), and explicitly grant (`nix store access grant`) or revoke (`nix store access revoke`) access of certain user to each individual path. +Naturally, this should only be available to `trusted-user`s and users who have access to this path already. + +### `db.sqlite` + +The `ValidPaths` table should get a new field, something like `allowed-users`, which should list the users who have either provided proof of source for this derivation or had been explicitly granted access. +When `selective-acl` is enabled, there should be a field like `protected` to mark a derivation as protected. + +### Changes to the system + +We should implement a way to restrict access to all the store paths for users. +A first "line of defense" could be something like [RFC 97], which makes the store non-world-listable. +However, it is separate from this RFC, and is not required. +On top of that, we must enforce a stricter access control, using [POSIX ACLs](https://man7.org/linux/man-pages/man5/acl.5.html) to only allow users access to store paths if they are part of the ACL for that path. +Nix daemon (or other local store implementations) should execute the appropriate `setfacl` calls whenever a path is added to store or gets different permissions in the db. + +[RFC 97]: https://github.com/NixOS/rfcs/pull/97 + +### Nix language + +We change the `derivation` and `path` builtins, such that a `__permissions` attribute, when set, is expected to be a list of strings, representing the users who should be granted access to the store path (or the derivation outputs, in the case of `derivation`). + +If this list is set, the Nix daemon (or other store implementation) should be notified that the path/derivation is supposed to be protected (perhaps using a new worker protocol command), and after the build is completed, the ACLs should be set appropriately (only the users specified in the `__permissions` list should have access to the path). + +### Nix Daemon/local store implementation + +The Nix daemon (or the local store implementation) should, if necessary, update, and then check whether the user has permission to access all dependencies before accepting a derivation realization request from any client. +After the build is performed, the user should be granted access to all the derivation outputs. +Also, paths dumped to store by users should automatically be accessible by the user, and the access list should be updated should any other user dump the same path in the future. + +This should work as follows: when `acl` are enabled, we recursively set permissions on all the existing store paths to `500`, so that only the store owner can access it by default. +When `selective-acl` are enabled, we only set this permission on the `protected` store paths. + +Whenever the user adds a path to the store (`wopAdd*ToStore`, `wopImportPaths`), we add them to the ACL in `db.sqlite` for that path, and also set the ACL in the filesystem accordingly, like this (but in C++, of course): + +```shell +setfacl -R -m user:$UID:rx /nix/store/... +``` + +Whenever a user tries to build a store path (`wopBuildPaths*`, `wopBuildDerivation`), we check if they have access to all dependencies or they are granted permission explicilty, then we build the path if necessary, and then recursively add an entry for them to the ACL of the store path both in the DB and in the FS. + +All these operations should support a flag to mark the path as protected, so that it is not exposed during building or adding. + +There should also be a couple of new operations (perhaps `wopSetACL`/ `wopGetACL`) which allows to get and set the list of users/groups with access to the path, both in the DB and FS simultaneously. +This should be emitted by Nix clients after the path is added to the store or a build is completed, and by `nix store access grant`/`nix store access revoke`. + +For all other operations working on paths, we check if the user has access before doing anything. + +When the access is revoked explicilty, we remove the user from the ACL: + +```shell +setfacl -R -x user:$UID:rx /nix/store/... +``` + +Also, depending on the design of substituters, we might need to handle the new proof-of-source protocol, to prove that the necessary sources are present in the store. +As mentioned, this should only happen if the user requesting realization has the relevant access to those paths. + +## Remote stores + +For remote stores, this problem is a bit more difficult since sending all the inputs to the remote store every time a dependency has to be downloaded is really expensive. +There should be a protocol to establish proof-of-source, as in proof that the client has access to all the sources of a derivation, before that derivation output is provided. + +Simply providing the hash of the inputs as the proof would be one possible (but problematic) implementation. +It is succeptible to replay attacks, furthermore typically obtaining the hash of the inputs is easier then obtaining the inputs themselves. + +Another example of such a protocol would be a challenge-response protocol, where the substituter sends a challenge salt to the client, the client then hashes all the NARs of inputs of the derivation, adding this salt, and finally sends the hash back to the substituter, which, if the hash is correct, provides the path. + +Yet another example would be a time-based protocol, where the salt is the current POSIX timestamp. +The substituter then checks that the timestamp is recent enough (say, 5 seconds to allow for discrepancies between clocks and the network delays) and then validates the hash. +If all is good, the path is returned. +This has the advantage that it does not need two-way interaction, so it can easily work with e.g. HTTPS. +But it's somewhat problematic since a replay attack is possible if executed quickly. + +Another solution (which can be implemented together with the previous one) is to keep an access list as metadata in the cache, and keep a mapping between local users with access to that list and simple credentials (e.g. simple HTTP auth). +This has the benefit of being the easiest one to implement, but is a hassle to use (requires granting the permissions and setting up credentials externally), and also is succeptible to credential leaks. + +Finally, in a situation where local daemons have full access to the cache but restrict local user access, it is possible to leave the substituter logic as is, and offload all the checks to the local daemons. +It should be rather easy to implement, since all the same checks would have to be done for substitution as for simply reusing the locally built outputs. + +# Examples and Interactions +[examples-and-interactions]: #examples-and-interactions + +## Local example + +Alice, Bob, Carol and Eve share a build-machine with a single nix store. +The daemon on this machine has `acl` enabled. + +Alice builds some proprietary software from source. +She can do this as usual, by just running `nix build`. +She now can access all the relevant store paths, including the source (which has been copied to the store for the build), the build dependencies, the runtime dependencies, and the resulting derivation output. + +She wants to collaborate on this software with Bob. +She shares the source with Bob, he executes `nix build` as well, which is really fast -- the only thing that happens is that he is granted access to all the same paths as Alice. +No actual building occurs. + +Now, Alice and Bob want to share the resulting binaries (but not the sources) with Carol. +They can add a `__permissions = [ "alice" "bob" "carol" ];` and re-build the derivation (nothing will get re-built, only the permissions will be updated). +Alternatively, either of them can issue a `nix store access grant --recursive --user carol /nix/store/...-software` command, granting Carol access to software and all its runtime dependencies, but none of the sources. +Carol can inspect and run the binary version of the software, and even (theoretically) build other software on top of it. + +Finally, an evil Eve wants to steal the software. +She has multiple obstacles: + +1. The nix store is not readable, hence she can't easily figure out the store path of the software +2. If she somehow figures out the store path of the software or the sources, those paths are not readable to her. + +The only two ways for her to get access to the software would be either to obtain the sources via some other means, at which point she doesn't really need anything in the store anyways since it would be trivial to produce the binary artifacts on her personal machine, or trick the machine's administrators into explicitly granting her access. + +## Remote example + +Alice sets up a binary cache with `acl` enabled. + +She uploads some proprietary software (both the source and the realised derivation output) to the store. + +Alice wants to collaborate on this software with Bob. +She shares the source with him, and he can now add this substituter to his `substituters` and fetch both the sources and the binary products for local development. + +Alice wants to share the resulting binary with Carol. +She grants explicit access to `carol` on the substituter, generates some credentials and adds them to the "password file" for the substituter. +Carol then can add the substituter with these credentials to her `substituters`, and fetch the artifact (but not the source) to run it locally. + +Evil Eve still can't steal the software, since to download the path she once again either needs to obtain the source or the credentials to download it from the cache. + +# Drawbacks +[drawbacks]: #drawbacks + +- This change requires significant refactoring of many Nix components; +- Implementing ACLs will impose a performance penalty. Hopefully this penalty will not be too bad, and also since ACLs are entirely optional this shouldn't affect users who don't need it; +- Short secrets in the store can be brute-forced, since the hash of the content is known, unless [RFC 97] is also implemented. +- Security achieved by ACLs on a multi-user system will depend on the Nix daemon implementation, which has quite a large attack surface; +- Syncing ACLs between machines can be difficult to do properly. + +# Alternatives +[alternatives]: #alternatives + +# Unresolved questions +[unresolved]: #unresolved-questions + +- Does the nix store permission setup belong as a NixOS module in nixpkgs, or as part of the Nix installation, or both? More investigation is needed. +- How to handle the remote store case. + +# Future work +[future]: #future-work + From a3e6322255bcc42128c4ebbe552ed324f040d19f Mon Sep 17 00:00:00 2001 From: Alexander Bantyev Date: Tue, 21 Mar 2023 11:26:42 +0400 Subject: [PATCH 2/7] RFC 143: mention groups more --- rfcs/0143-nix-store-acls.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rfcs/0143-nix-store-acls.md b/rfcs/0143-nix-store-acls.md index a524843fd..4d0249d33 100644 --- a/rfcs/0143-nix-store-acls.md +++ b/rfcs/0143-nix-store-acls.md @@ -36,7 +36,7 @@ If a user needs to be able to access some store paths without having access to t ## Local store For a local store, we keep a list of local (POSIX) users who have access to each store path as path metadata. -When the relevant config option (perhaps `acl`) is enabled, this ACL (access control list) is enforced, meaning that only users on that list can read the store path, execute files from it, or use it as a dependency in their derivations. +When the relevant config option (perhaps `acl`) is enabled, this ACL (access control list) is enforced, meaning that only users on that list, or belonging to groups on that list, can read the store path, execute files from it, or use it as a dependency in their derivations. We also add a less strict mode (`selective-acl`) in which derivations are only protected if they are marked as such. For paths external to the Nix sandbox (added via `nix store add-{file,path}`, paths in the Nix language, `builtins.fetch*`, or flake sources), we add the user to the list when they send the path to the daemon. @@ -49,12 +49,12 @@ For derivation outputs, we add user to the list when the user requests to realis When `selective-acl` is enabled, protected paths should not be readable by anyone during the build. Necessary permissions are granted after the build. -There also should be a way to enable the protection for selective ACLs (perhaps `nix store access protect`), and explicitly grant (`nix store access grant`) or revoke (`nix store access revoke`) access of certain user to each individual path. -Naturally, this should only be available to `trusted-user`s and users who have access to this path already. +There also should be a way to enable the protection for selective ACLs (perhaps `nix store access protect`), and explicitly grant (`nix store access grant`) or revoke (`nix store access revoke`) access of certain user or group to each individual path. +Naturally, this should only be available to `trusted-user`s and users who have access to this path already (either because they are on the ACL, or they belong to a group on the ACL). ### `db.sqlite` -The `ValidPaths` table should get a new field, something like `allowed-users`, which should list the users who have either provided proof of source for this derivation or had been explicitly granted access. +The `ValidPaths` table should get a new field, something like `allowed-users`, which should list the users and groups who have either provided proof of source for this derivation or had been explicitly granted access. When `selective-acl` is enabled, there should be a field like `protected` to mark a derivation as protected. ### Changes to the system @@ -62,14 +62,14 @@ When `selective-acl` is enabled, there should be a field like `protected` to mar We should implement a way to restrict access to all the store paths for users. A first "line of defense" could be something like [RFC 97], which makes the store non-world-listable. However, it is separate from this RFC, and is not required. -On top of that, we must enforce a stricter access control, using [POSIX ACLs](https://man7.org/linux/man-pages/man5/acl.5.html) to only allow users access to store paths if they are part of the ACL for that path. +On top of that, we must enforce a stricter access control, using [POSIX ACLs](https://man7.org/linux/man-pages/man5/acl.5.html) to only allow users access to store paths if they are part of the ACL (or belong to a group on the ACL) for that path. Nix daemon (or other local store implementations) should execute the appropriate `setfacl` calls whenever a path is added to store or gets different permissions in the db. [RFC 97]: https://github.com/NixOS/rfcs/pull/97 ### Nix language -We change the `derivation` and `path` builtins, such that a `__permissions` attribute, when set, is expected to be a list of strings, representing the users who should be granted access to the store path (or the derivation outputs, in the case of `derivation`). +We change the `derivation` and `path` builtins, such that a `__permissions` attribute, when set, is expected to be a list of strings, representing the users and groups who should be granted access to the store path (or the derivation outputs, in the case of `derivation`). If this list is set, the Nix daemon (or other store implementation) should be notified that the path/derivation is supposed to be protected (perhaps using a new worker protocol command), and after the build is completed, the ACLs should be set appropriately (only the users specified in the `__permissions` list should have access to the path). From 519ca11a13e4ce61d3fe6b323da9d4d697dced53 Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Wed, 9 Aug 2023 15:26:20 +0200 Subject: [PATCH 3/7] Add shepherd metadata --- rfcs/0143-nix-store-acls.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/0143-nix-store-acls.md b/rfcs/0143-nix-store-acls.md index 4d0249d33..ea002e5b5 100644 --- a/rfcs/0143-nix-store-acls.md +++ b/rfcs/0143-nix-store-acls.md @@ -3,8 +3,8 @@ feature: nix-store-acls start-date: 2023-01-16 author: Alexander Bantyev co-authors: Silvan Mosberger, Théophane Hufschmitt -shepherd-team: (names, to be nominated and accepted by RFC steering committee) -shepherd-leader: (name to be appointed by RFC steering committee) +shepherd-team: John Ericson, Théophane Hufschmitt, Eelco Dolstra +shepherd-leader: Théophane Hufschmitt related-issues: (will contain links to implementation PRs) --- From bc29a46017b1550fa26acb1b44b67519ca477098 Mon Sep 17 00:00:00 2001 From: Alexander Bantyev Date: Mon, 16 Oct 2023 17:44:40 +0400 Subject: [PATCH 4/7] Address some meeting feedback --- rfcs/0143-nix-store-acls.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/rfcs/0143-nix-store-acls.md b/rfcs/0143-nix-store-acls.md index ea002e5b5..11ae85f31 100644 --- a/rfcs/0143-nix-store-acls.md +++ b/rfcs/0143-nix-store-acls.md @@ -35,10 +35,6 @@ If a user needs to be able to access some store paths without having access to t ## Local store -For a local store, we keep a list of local (POSIX) users who have access to each store path as path metadata. -When the relevant config option (perhaps `acl`) is enabled, this ACL (access control list) is enforced, meaning that only users on that list, or belonging to groups on that list, can read the store path, execute files from it, or use it as a dependency in their derivations. -We also add a less strict mode (`selective-acl`) in which derivations are only protected if they are marked as such. - For paths external to the Nix sandbox (added via `nix store add-{file,path}`, paths in the Nix language, `builtins.fetch*`, or flake sources), we add the user to the list when they send the path to the daemon. We might need to add a flag (like `--protect`) for the selective ACL mode. @@ -46,32 +42,31 @@ For derivations themselves (.drv files), we add the user to the list when they s For derivation outputs, we add user to the list when the user requests to realise the derivation and has access to the transitive dependencies, including the sources. -When `selective-acl` is enabled, protected paths should not be readable by anyone during the build. +Protected paths should not be readable by anyone during the build. Necessary permissions are granted after the build. There also should be a way to enable the protection for selective ACLs (perhaps `nix store access protect`), and explicitly grant (`nix store access grant`) or revoke (`nix store access revoke`) access of certain user or group to each individual path. Naturally, this should only be available to `trusted-user`s and users who have access to this path already (either because they are on the ACL, or they belong to a group on the ACL). -### `db.sqlite` - -The `ValidPaths` table should get a new field, something like `allowed-users`, which should list the users and groups who have either provided proof of source for this derivation or had been explicitly granted access. -When `selective-acl` is enabled, there should be a field like `protected` to mark a derivation as protected. - ### Changes to the system We should implement a way to restrict access to all the store paths for users. A first "line of defense" could be something like [RFC 97], which makes the store non-world-listable. However, it is separate from this RFC, and is not required. On top of that, we must enforce a stricter access control, using [POSIX ACLs](https://man7.org/linux/man-pages/man5/acl.5.html) to only allow users access to store paths if they are part of the ACL (or belong to a group on the ACL) for that path. -Nix daemon (or other local store implementations) should execute the appropriate `setfacl` calls whenever a path is added to store or gets different permissions in the db. +Nix daemon (or other local store implementations) should execute the appropriate `setfacl` calls whenever a path is added to store or gets different permissions. [RFC 97]: https://github.com/NixOS/rfcs/pull/97 ### Nix language -We change the `derivation` and `path` builtins, such that a `__permissions` attribute, when set, is expected to be a list of strings, representing the users and groups who should be granted access to the store path (or the derivation outputs, in the case of `derivation`). +We define an "access control status" as an attrset `{ protected : bool; users : [string]; groups : [string]; }`. -If this list is set, the Nix daemon (or other store implementation) should be notified that the path/derivation is supposed to be protected (perhaps using a new worker protocol command), and after the build is completed, the ACLs should be set appropriately (only the users specified in the `__permissions` list should have access to the path). +We change the `derivation` builtin, adding a `__permissions` argument. This argument is supposed to be of form `{ drv : access control status; outputs. : access control status; log : access control status; }`, each attribute being optional. The attributes correspond to the desired permissions of the derivation itself (`.drv` file), outputs of the derivation, and the build log. + +We change the `path` builtin, adding a `permissions` argument, which is an access control status representing the desired permissions of the resulting store path. + +If either of those arguments are passed, the Nix daemon (or other store implementation) should be notified that the corresponding store object is supposed to be protected (perhaps using a new worker protocol command), and after the build is completed, the ACLs should be set appropriately (only the users specified in the argument, and the building user, should have access to the path). ### Nix Daemon/local store implementation @@ -88,12 +83,13 @@ Whenever the user adds a path to the store (`wopAdd*ToStore`, `wopImportPaths`), setfacl -R -m user:$UID:rx /nix/store/... ``` -Whenever a user tries to build a store path (`wopBuildPaths*`, `wopBuildDerivation`), we check if they have access to all dependencies or they are granted permission explicilty, then we build the path if necessary, and then recursively add an entry for them to the ACL of the store path both in the DB and in the FS. +Whenever a user tries to build a store path (`BuildPaths*`, `BuildDerivation`), we check if they have access to all dependencies or they are granted permission explicilty, then we build the path if necessary, and then recursively add an entry for them to the ACL of the store path. All these operations should support a flag to mark the path as protected, so that it is not exposed during building or adding. -There should also be a couple of new operations (perhaps `wopSetACL`/ `wopGetACL`) which allows to get and set the list of users/groups with access to the path, both in the DB and FS simultaneously. -This should be emitted by Nix clients after the path is added to the store or a build is completed, and by `nix store access grant`/`nix store access revoke`. +There should also be a couple of new operations in the worker protocol (perhaps `SetAccessStatus`/ `GetAccessStatus`) which allows to get and set the list of users/groups with access to the path, potentially in the future if the path does not exist. + +This should be emitted by Nix clients before the path is added to the store or a build is completed, and by `nix store access grant`/`nix store access revoke`. For all other operations working on paths, we check if the user has access before doing anything. @@ -145,7 +141,7 @@ She shares the source with Bob, he executes `nix build` as well, which is really No actual building occurs. Now, Alice and Bob want to share the resulting binaries (but not the sources) with Carol. -They can add a `__permissions = [ "alice" "bob" "carol" ];` and re-build the derivation (nothing will get re-built, only the permissions will be updated). +They can add a `__permissions.outputs.out.users = [ "alice" "bob" "carol" ];` and re-build the derivation (nothing will get re-built, only the permissions will be updated). Alternatively, either of them can issue a `nix store access grant --recursive --user carol /nix/store/...-software` command, granting Carol access to software and all its runtime dependencies, but none of the sources. Carol can inspect and run the binary version of the software, and even (theoretically) build other software on top of it. @@ -159,7 +155,7 @@ The only two ways for her to get access to the software would be either to obtai ## Remote example -Alice sets up a binary cache with `acl` enabled. +Alice sets up a binary cache. She uploads some proprietary software (both the source and the realised derivation output) to the store. From bd3f6a6ee7ce073390533e6972c903b973c7fbbd Mon Sep 17 00:00:00 2001 From: Alexander Bantyev Date: Mon, 16 Oct 2023 18:02:05 +0400 Subject: [PATCH 5/7] Remove some implementation details --- rfcs/0143-nix-store-acls.md | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/rfcs/0143-nix-store-acls.md b/rfcs/0143-nix-store-acls.md index 11ae85f31..0054f92ac 100644 --- a/rfcs/0143-nix-store-acls.md +++ b/rfcs/0143-nix-store-acls.md @@ -42,19 +42,20 @@ For derivations themselves (.drv files), we add the user to the list when they s For derivation outputs, we add user to the list when the user requests to realise the derivation and has access to the transitive dependencies, including the sources. -Protected paths should not be readable by anyone during the build. +Protected paths and derivation outputs should not be readable by anyone during the build. Necessary permissions are granted after the build. There also should be a way to enable the protection for selective ACLs (perhaps `nix store access protect`), and explicitly grant (`nix store access grant`) or revoke (`nix store access revoke`) access of certain user or group to each individual path. -Naturally, this should only be available to `trusted-user`s and users who have access to this path already (either because they are on the ACL, or they belong to a group on the ACL). +Naturally, this should only be available to `trusted-user`s and users who have access to this path already. +Additionally, only trusted users should be able to protect a path or revoke permissions, since that could potentially break things for other users. ### Changes to the system -We should implement a way to restrict access to all the store paths for users. +It should be possible to restrict access to all the store paths for users. A first "line of defense" could be something like [RFC 97], which makes the store non-world-listable. However, it is separate from this RFC, and is not required. On top of that, we must enforce a stricter access control, using [POSIX ACLs](https://man7.org/linux/man-pages/man5/acl.5.html) to only allow users access to store paths if they are part of the ACL (or belong to a group on the ACL) for that path. -Nix daemon (or other local store implementations) should execute the appropriate `setfacl` calls whenever a path is added to store or gets different permissions. +Nix daemon (or other local store implementations) should ensure that the proper ACLs are set whenever a path is added to store or gets different permissions. [RFC 97]: https://github.com/NixOS/rfcs/pull/97 @@ -74,10 +75,7 @@ The Nix daemon (or the local store implementation) should, if necessary, update, After the build is performed, the user should be granted access to all the derivation outputs. Also, paths dumped to store by users should automatically be accessible by the user, and the access list should be updated should any other user dump the same path in the future. -This should work as follows: when `acl` are enabled, we recursively set permissions on all the existing store paths to `500`, so that only the store owner can access it by default. -When `selective-acl` are enabled, we only set this permission on the `protected` store paths. - -Whenever the user adds a path to the store (`wopAdd*ToStore`, `wopImportPaths`), we add them to the ACL in `db.sqlite` for that path, and also set the ACL in the filesystem accordingly, like this (but in C++, of course): +Whenever the user adds a path to the store (`Add*ToStore`, `ImportPaths`), set the ACL in the filesystem accordingly, like this (but in C++, of course): ```shell setfacl -R -m user:$UID:rx /nix/store/... @@ -85,9 +83,8 @@ setfacl -R -m user:$UID:rx /nix/store/... Whenever a user tries to build a store path (`BuildPaths*`, `BuildDerivation`), we check if they have access to all dependencies or they are granted permission explicilty, then we build the path if necessary, and then recursively add an entry for them to the ACL of the store path. -All these operations should support a flag to mark the path as protected, so that it is not exposed during building or adding. - -There should also be a couple of new operations in the worker protocol (perhaps `SetAccessStatus`/ `GetAccessStatus`) which allows to get and set the list of users/groups with access to the path, potentially in the future if the path does not exist. +There should be a couple of new operations in the worker protocol which allow getting and setting the list of users/groups with access to the path. +If the specified path does not exist yet, those commands should operate on the "future" access status of the path, meaning the access status that will be applied as soon as the path gets added to the store. This should be emitted by Nix clients before the path is added to the store or a build is completed, and by `nix store access grant`/`nix store access revoke`. @@ -110,13 +107,8 @@ There should be a protocol to establish proof-of-source, as in proof that the cl Simply providing the hash of the inputs as the proof would be one possible (but problematic) implementation. It is succeptible to replay attacks, furthermore typically obtaining the hash of the inputs is easier then obtaining the inputs themselves. -Another example of such a protocol would be a challenge-response protocol, where the substituter sends a challenge salt to the client, the client then hashes all the NARs of inputs of the derivation, adding this salt, and finally sends the hash back to the substituter, which, if the hash is correct, provides the path. - -Yet another example would be a time-based protocol, where the salt is the current POSIX timestamp. -The substituter then checks that the timestamp is recent enough (say, 5 seconds to allow for discrepancies between clocks and the network delays) and then validates the hash. -If all is good, the path is returned. -This has the advantage that it does not need two-way interaction, so it can easily work with e.g. HTTPS. -But it's somewhat problematic since a replay attack is possible if executed quickly. +An improvement on simply providing the hash would be providing the hash of the concatenation of substituter URI and the NAR contents of the input closure. +This still has some potential for MITM attacks, but prevents replays. Using an HTTPS cache should prevent most types of MITM attacks. Another solution (which can be implemented together with the previous one) is to keep an access list as metadata in the cache, and keep a mapping between local users with access to that list and simple credentials (e.g. simple HTTP auth). This has the benefit of being the easiest one to implement, but is a hassle to use (requires granting the permissions and setting up credentials externally), and also is succeptible to credential leaks. From 830cdd388fe3cea358cd7e2383e3b6bee19ef3ba Mon Sep 17 00:00:00 2001 From: Alexander Bantyev Date: Mon, 16 Oct 2023 18:24:55 +0400 Subject: [PATCH 6/7] Reword the Detailed Design section & drop implementation details --- rfcs/0143-nix-store-acls.md | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/rfcs/0143-nix-store-acls.md b/rfcs/0143-nix-store-acls.md index 0054f92ac..15ec51359 100644 --- a/rfcs/0143-nix-store-acls.md +++ b/rfcs/0143-nix-store-acls.md @@ -27,11 +27,17 @@ Finally, it means that substituter access is all-or-nothing: either a user can a # Detailed design [design]: #detailed-design -Change the implementation of the Nix daemon (and, potentially, nix-serve, depending on the chosen "Remote Store" implementation) to store access control lists as metadata, automatically update them when users provide proof that they have the necessary source, and allow `trusted-user`s to manipulate those ACLs manually. +Change the implementation of the Nix daemon (and, potentially, nix-serve, depending on the chosen "Remote Store" implementation) to: + +- Apply necessary [POSIX ACLs](https://man7.org/linux/man-pages/man5/acl.5.html) to store paths, automatically update them when users provide proof that they have the necessary source, and allow `trusted-user`s and users with access to the paths in question to manipulate those ACLs manually. +- Add a setting (perhaps `protect-by-default`) to protect all new store paths by default. This should ensure that this change is as seamless as possible for the users: they will still always be able to execute `nix build` or similar for derivations where they have access to all the sources, substituting as much as possible, as though nothing had changed. -The only major difference would be that `/nix/store` itself is now not readable. -If a user needs to be able to access some store paths without having access to their sources (e.g. for proprietary software where sharing the artifacts is ok but sharing the sources isn't), such access can be granted explicitly by the administrators (`trusted-user`'s). +If a user needs to be able to access some store paths without having access to their sources (e.g. for proprietary software where sharing the artifacts is ok but sharing the sources isn't), such access can be granted explicitly by the administrators (`trusted-user`'s) or users with access to said path. + +Additionally, [RFC 97] can be implemented for some extra security. + +[RFC 97]: https://github.com/NixOS/rfcs/pull/97 ## Local store @@ -45,20 +51,10 @@ For derivation outputs, we add user to the list when the user requests to realis Protected paths and derivation outputs should not be readable by anyone during the build. Necessary permissions are granted after the build. -There also should be a way to enable the protection for selective ACLs (perhaps `nix store access protect`), and explicitly grant (`nix store access grant`) or revoke (`nix store access revoke`) access of certain user or group to each individual path. -Naturally, this should only be available to `trusted-user`s and users who have access to this path already. +There also should be a way to enable the protection (perhaps `nix store access protect`), explicitly grant (`nix store access grant`), and revoke (`nix store access revoke`) access of certain user or group to each individual path. +Naturally, this should only be available to `trusted-user`s and users who already have access to this path. Additionally, only trusted users should be able to protect a path or revoke permissions, since that could potentially break things for other users. -### Changes to the system - -It should be possible to restrict access to all the store paths for users. -A first "line of defense" could be something like [RFC 97], which makes the store non-world-listable. -However, it is separate from this RFC, and is not required. -On top of that, we must enforce a stricter access control, using [POSIX ACLs](https://man7.org/linux/man-pages/man5/acl.5.html) to only allow users access to store paths if they are part of the ACL (or belong to a group on the ACL) for that path. -Nix daemon (or other local store implementations) should ensure that the proper ACLs are set whenever a path is added to store or gets different permissions. - -[RFC 97]: https://github.com/NixOS/rfcs/pull/97 - ### Nix language We define an "access control status" as an attrset `{ protected : bool; users : [string]; groups : [string]; }`. @@ -86,11 +82,11 @@ Whenever a user tries to build a store path (`BuildPaths*`, `BuildDerivation`), There should be a couple of new operations in the worker protocol which allow getting and setting the list of users/groups with access to the path. If the specified path does not exist yet, those commands should operate on the "future" access status of the path, meaning the access status that will be applied as soon as the path gets added to the store. -This should be emitted by Nix clients before the path is added to the store or a build is completed, and by `nix store access grant`/`nix store access revoke`. +Those operations should be emitted by Nix clients before the path is added to the store or a build is completed, and by `nix store access grant`/`nix store access revoke`. For all other operations working on paths, we check if the user has access before doing anything. -When the access is revoked explicilty, we remove the user from the ACL: +When the access is revoked explicitly, we remove the user from the ACL: ```shell setfacl -R -x user:$UID:rx /nix/store/... @@ -122,7 +118,6 @@ It should be rather easy to implement, since all the same checks would have to b ## Local example Alice, Bob, Carol and Eve share a build-machine with a single nix store. -The daemon on this machine has `acl` enabled. Alice builds some proprietary software from source. She can do this as usual, by just running `nix build`. @@ -138,10 +133,6 @@ Alternatively, either of them can issue a `nix store access grant --recursive -- Carol can inspect and run the binary version of the software, and even (theoretically) build other software on top of it. Finally, an evil Eve wants to steal the software. -She has multiple obstacles: - -1. The nix store is not readable, hence she can't easily figure out the store path of the software -2. If she somehow figures out the store path of the software or the sources, those paths are not readable to her. The only two ways for her to get access to the software would be either to obtain the sources via some other means, at which point she doesn't really need anything in the store anyways since it would be trivial to produce the binary artifacts on her personal machine, or trick the machine's administrators into explicitly granting her access. From 676f1c92bdaae6065cbad4b09c9872275803e0d9 Mon Sep 17 00:00:00 2001 From: Alexander Bantyev Date: Wed, 8 Nov 2023 15:06:26 +0400 Subject: [PATCH 7/7] Address meeting feedback --- rfcs/0143-nix-store-acls.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rfcs/0143-nix-store-acls.md b/rfcs/0143-nix-store-acls.md index 15ec51359..9303b7e96 100644 --- a/rfcs/0143-nix-store-acls.md +++ b/rfcs/0143-nix-store-acls.md @@ -24,6 +24,10 @@ Also, it makes storing secrets in the Nix store even more dangerous then it coul Finally, it means that substituter access is all-or-nothing: either a user can access the cache and download everything there is in there just by knowing store paths (even without having the source code or Nix expressions available), or they can't download anything. +This can be rectified by adding access control list (ACL) functionality to Nix, making it possible to set ACLs on store paths. + +This RFC targets the use-case of protecting proprietary software on a shared nix store or substituter. It could potentially be used to store secrets (such as passwords) in the Nix store, but this is potentially succeptible to some attacks; for example, some store paths are content-addressed (including files simply imported into the store), thus it may be possible to brute-force their contents based on the world-readable hash part of the store path. It is definitely **not** intended for storing small (<64 bits of entropy) secrets. + # Detailed design [design]: #detailed-design @@ -32,6 +36,8 @@ Change the implementation of the Nix daemon (and, potentially, nix-serve, depend - Apply necessary [POSIX ACLs](https://man7.org/linux/man-pages/man5/acl.5.html) to store paths, automatically update them when users provide proof that they have the necessary source, and allow `trusted-user`s and users with access to the paths in question to manipulate those ACLs manually. - Add a setting (perhaps `protect-by-default`) to protect all new store paths by default. +An additional invariant that must be ensured at all times is that the complete runtime closure of the store path is available to a user if the store path itself is available. + This should ensure that this change is as seamless as possible for the users: they will still always be able to execute `nix build` or similar for derivations where they have access to all the sources, substituting as much as possible, as though nothing had changed. If a user needs to be able to access some store paths without having access to their sources (e.g. for proprietary software where sharing the artifacts is ok but sharing the sources isn't), such access can be granted explicitly by the administrators (`trusted-user`'s) or users with access to said path.