Hi everyone:
First of all, a disclaimer: I’ve never used discord before or this forum, so let me know if there is a better place to ask this, or any other advice about where to go, and apologies in advance if this should be asked elsewhere.
Anyway, I work at a company that’s using nix as a build system and for deployment. We want to be able to use nix copy
to move programs between hosts, but also have some centralized control over what is allowed to be copied. For instance, it’s pretty easy to get source code in your runtime closure, if you compile C without -ffile-prefix-map
, or if you compile with debugging symbols. We can freely copy source around internally, but are not allowed to copy it to external machines, and nix copy
makes it too easy to accidentally copy more than you bargained for.
I have a plan, which I’ll describe below, but first, is this something which has been discussed or planned before? Perhaps there are RFCs for this kind of thing, or previous talk, or perhaps it’s even possible currently and I just don’t know how? Supposing I go ahead with my plan, it would be nicer to get it upstreamed than continue maintaining a branch, so advice about how get involved in that process would be appreciated.
That said, the plan (very preliminary). I looked into path signing, but it seems like it’s backwards, it’s oriented around the copy-to being able to reject paths because it doesn’t trust them, not the copy-from rejecting to send paths because it doesn’t trust the copy-to. So I think it can’t be made to do what I want.
It looks like copyStorePath
in store-api.cc
is in the right place to intercept copies from all nix commands and be able to make a per-path decision. We wind up with two problems:
-
Differentiate destinations, so we can apply different rules based on the destination.
- An easy hack I thought of was to overload the protocol version. The idea being a trusted host will have been given the patched nix, which will support a higher protocol version.
copyStorePath
already has visibility on the protocol so it can just look at it to know if the host is trusted or not. The problems with this are that it’s just advisory (a patched client could just claim whatever version number). We don’t mind because this is meant to prevent mistakes, not actually be secure. Another problem is that it’s implicit and is overloading a protocol number intended for, well protocol versions, and of course the protocol will change on its own for its own reasons and we’d have to keep up with that, so I suspect the general nix community will not love the idea. But it’s quick and easy. - A more proper solution would be some kind of host key thing, as far as I know (and I’m no expert) we’d need to propagate some ssh metadata up from the transport layer… by the time we see it in
copyStorePath
it’s just a generic data sink. - Or perhaps there’s some other host-level metadata we could latch on to, or create.
Like protocol version, but without the baggage of being intended for something else.
- An easy hack I thought of was to overload the protocol version. The idea being a trusted host will have been given the patched nix, which will support a higher protocol version.
-
Differentiate paths, to know which ones are ok to copy and which are not. I’d probably implement this as asking an external process, so it can use whatever mechanism it wants to decide. That said, here are ideas about what it could use:
-
Use the name: Because internally we have central control over the derivation names, and we enforce a naming scheme based on source directory, we can reliably make decisions based on the source directory or even just a hardcoded list of allowed names. Since we are ok to copy anything out of nixpkgs, we can blanket allow any path that doesn’t match the internal naming scheme.
-
Put a file inside, e.g.
nix-support/public
. Then individual builds can opt-in, without a centralized list. Won’t work for single-file outputs of course. -
Some other metadata associated with the path, that’s not the name. I don’t know what there is, but signing keys seem to be one. I assume they’re in the sqlite db. This would allow per-build opt-in and not clutter up the output with a dummy file. But it seems a lot more complicated and less visible.
-
So my conclusions were: patch store-api.cc
, use the protocol number hack (unless something better pops up), and add a “verify-copy” type option which will point to a program that takes store paths on the input and returns which ones are not ok to copy. Internally it will just use the path name, but it could also look inside them to make its decision (i.e. is there source in there?).
A whole other tack on this is to prevent the unwanted runtime deps in the first place. It’s not sufficient because in the case of debug symbols they are in fact wanted runtime deps, just don’t want to copy them to some places. But I did manage to get checkOutputs
to work, while disallowedRequisites
seemed to have no effect. Also I could find no way to override the runtime dep detection mechanism, but I wrote a binary patcher to simply 0-out /nix/path hashes at the end of the build, which does work. Of course if the deps were legit after all now you have something that may break at runtime, but that’s the deal.
Thoughts? Thanks in advance!