-
-
Notifications
You must be signed in to change notification settings - Fork 161
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
[RFC 0143] Nix Store ACLs #143
base: master
Are you sure you want to change the base?
Changes from 3 commits
1d0b015
a3e6322
519ca11
bc29a46
bd3f6a6
830cdd3
676f1c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -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: John Ericson, Théophane Hufschmitt, Eelco Dolstra | ||||||
shepherd-leader: Théophane Hufschmitt | ||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sentence seems to imply that it's actually not required |
||||||
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). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might not matter too much, but shouldn't anyone with access to the path be allowed to share it further? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (it looks like that's what L53 implies too) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, indeed |
||||||
|
||||||
## 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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear at which level these options acts: the first paragraph seems to talk about a specific path (with one access-control list), while the second one seems to be global to the store There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand it correctly:
Is that it? |
||||||
|
||||||
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. | ||||||
Comment on lines
+50
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably irrelevant here, but this is somewhat reminescent of the mechanism for allowing access to paths in pure eval mode |
||||||
|
||||||
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. | ||||||
Comment on lines
+53
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does that mean that it's possible for someone to register a derivation without having access to its closure? How would that work? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that would be theoretically possible; after all, derivations ( |
||||||
|
||||||
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 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). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole section is a bit weird since the Maybe this could be reorganized as
|
||||||
|
||||||
### `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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would just leave that out. It's pure implementation detail and doesn't matter too much |
||||||
|
||||||
### 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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are POSIX ACLs also available on OSX or is it using a different mechanism there? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, they are available but the API differs a lot. |
||||||
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 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). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should that attribute have an impact on the derivation hash? It feels like it shouldn't (as it only touches the metadata), but at the same time not doing so wouldn't be properly backwards-compatible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would also be worth mentioning (maybe in the “alternatives” section) that changing the “derivation” builtin isn't required as it's possible to just give access to the builder by default. (btw, the first part seems to assume that it's the case) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also: Should that work recursively? If I'm Alice and have derivation {
builder = "ln";
args = [ "ln" "-s" "${./somePath}" "${placeholder "out"}"];
__permissions = [ "alice" "bob" ];
...
} should And what if I don't add myself to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It probably shouldn't. The lack of backwards-compatibility is annoying; I think we should make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think so; IMHO, Permissions are applied only to the object on which they are specified.
Perhaps there should be a check to automatically add the evaluating user to the __permissions. |
||||||
|
||||||
### Nix Daemon/local store implementation | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
? |
||||||
|
||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC attic has a similar problem, and some solution to it. It would be nice to see how they handle it (maybe Cachix has it too) |
||||||
|
||||||
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). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Re. my comment about whether |
||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would be a bit stronger here (or somewhere else), and highlight that this isn't a good mechanism for storing secrets There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say it depends on what is meant by "secrets"; after all, the only reason to have ACLs in the first place is to have some information which is only accessible to a select group, and thus "secret" in some sense. Of course, it should be highlighted that this is intended for "secret" (proprietary or otherwise protected) source-code and build artifacts, rather than runtime secrets such as credentials. |
||||||
- 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 | ||||||
|
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 think we need to care about
nix-serve
for this (unless it's already implemented?)