-
-
Notifications
You must be signed in to change notification settings - Fork 14.7k
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
Add types.secretPath
#78640
base: master
Are you sure you want to change the base?
Add types.secretPath
#78640
Conversation
Pushes the `let` outside too, which should make it a bit faster
Let's be very cautious about this kind of change.
I find the fact that this PR comes virtually without documentation concerning. I know it's on the TODO list, but I think it should be done first. The documentation should expose every flaw in this partial security solution. |
Does this prevents me from shooting myself in the foot with |
@aanderse Indeed it does! |
Mostly a good thing then... though it was kinda nice to have that fallback as justification for dropping |
@aanderse Oh you can still fallback to it being in the store with |
So I think the main concern is what could happen to the path before and after the Idea: a distinct typeIt seems possible to have a bit more control by introducing an actual new type. This is actually a pattern that works for in-memory secrets too. (
That manual section will have guidelines, particularly
On the module side,
On the user configuration side, we can require via |
lib/types.nix
Outdated
/* Explicitly define a secret path in the world-readable Nix store, allowing | ||
it to be used as a value for options of type secretPath. | ||
*/ | ||
secretInNixStore = path: |
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.
Some of us are conditioned to recognize that this is dangerous, but newcomers aren't.
secretInNixStore = path: | |
makeWorldReadable = path: |
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 originally didn't choose that because world-readable sounds like literally everybody can read it, but really it's just the people that have access to the machine.
Also, secrets don't have to be present on the evaluation host, in which case the secret can't be made world readable (though it already is on the remote host).
I think it's also important to clearly indicate what a function does, and this function does "Allow a secret path to be in the nix store, potentially importing it first". Though I like the idea of better indicating that it's it's not secure in general.
How about:
storeExposedSecret
nixStoreExposedSecret
unprotectedStoreSecret
insecureStoreSecret
secretInUnprotectedStore
secretInUnprotectedNixStore
exposedStoreSecret
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.
in which case the secret can't be made world readable (though it already is on the remote host).
Judging from the examples in #78640 (comment), this seems to apply only to (iv). That one seems like a particularly inconvenient and insecure method.
A name like that seems ok. Which one is best may depend on whether or not the { _type = "secretPath"; }
idea is implemented.
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 would like to see the word insecure
here. insecureStoreSecret
is good in my book
# Split out all invalid characters | ||
# https://github.com/NixOS/nix/blob/2.3.2/src/libstore/store-api.cc#L85-L112 | ||
# https://github.com/NixOS/nix/blob/2242be83c61788b9c0736a92bb0b5c7bbfc40803/nix-rust/src/store/path.rs#L100-L125 | ||
(builtins.split "[^[:alnum:]+-._?=]+") |
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.
huh, these becomes a pattern https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/kernel/make-initrd.nix#L24
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.
Nice, something to refactor with the new function introduced here!
@roberth I like the idea of Some comments on what you said:
|
Currently. It's basically one NixOS/nix#8 is an interesting idea, but unlikely to become successful. I'd love to be proven wrong, but the status quo of an unencumbered nix store seems quite attractive. To return to the PR topic :) It looks like it's going to be a good change. |
also see NixOS/rfcs#59 |
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.
👍 for the idea, it's good that somebody is tackling this.
In terms of implementation, I think we can get away with much less machinery by introducing:
lib.secretPath = path: { type = "path to secret"; __toString = _: toString path; };
This is enough to encapsulate the original path so it doesn't get added to the store when interpolated in values no?
Then the module option would also wrap the value in a secretPath if it's not already a secretPath.
I might be missing a use-case. It would be interesting to lay the out with a few examples.
builtins.typeOf x != "path" && ! hasPrefix builtins.storeDir (toString x) | ||
# Unless this is explicitly wanted with secretInNixStore | ||
|| x._secretInNixStore or false); | ||
checkFailedMessage = x: optionalString (path.check x) "If you want to import the secret into the globally readable Nix store, use lib.secretInNixStore on the value."; |
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.
What do you think of linking the user to the documentation where the user would have access to all the context instead?
Right now I think the user would try to use the secret in the store, get the error message on nixos-rebuild and just apply lib.secretInNixStore as directed.
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.
Sounds good (once the docs exist)
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.
Instead of secretInNixStore
, which may still be interpreted as being secure/encrypted, I would call it something more ominous like unprotectedSecretPath
.
# meaning there's no way to put this path into the store | ||
else throw ("secretInNixStore: The path '${toString path}' can't be in " | ||
+ "the Nix store. You probably don't need to use secretInNixStore for " | ||
+ "this path."); |
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.
Same
@zimbatm Not entirely sure what you're saying, but no that's not enough, because people can still pass The goal with this is to prevent any secrets from accidentally ending up in the store. Unfortunately as I just discovered, there's a problem with Nix evaluation itself that prevents some safety for this, see NixOS/nix#3358. Unfortunately I think this is a blocking issue for this PR. |
@infinisil IMHO the string context functions are edge cases that shouldn't block this. Even if this change doesn't support complete safety because of these cases, it's a significant improvement over the status quo (where paths where it's avoidable are copied in silently) and should be merged and used. |
Maybe you're right, I'll try to look into this again. Before I forget: This needs to be verified to work with flakes, which don't support |
I marked this as stale due to inactivity. → More info |
validDerivationName pkgs.hello | ||
=> "-nix-store-2g75chlbpxlrqn15zlby2dfh8hr9qwbk-hello-2.10" | ||
*/ | ||
validDerivationName = lib.flip lib.pipe [ |
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.
Seems like this was a precursor to sanitizeDerivationName
:)
Times have changed!
nix-repl> stdenv.mkDerivation { name = "hi!@#$%^&*()"; }
«derivation /nix/store/180bgrm5h84yc9vcg8fpp9p7jks1n9yj-hi-.drv»
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/low-effort-and-high-impact-tasks/21095/15 |
What's the status on this? Is there more a minimal version we could have that'd still be an improvement over the status quo? |
Motivation for this change
NixOS modules currently have no way to declare a path option as a secret. Using
types.path
in no way prevents people from putting secrets in the Nix store (you can prevent it only for path literals like/run/keys/foo
by settingapply = toString
, which I've been suggesting for some time).This PR solves this problem by introducing
types.secretPath
which is a type that prevents assignments to values in the Nix store. Along with it comes the functionlib.secretInNixStore
, which explicitly imports a secret into the Nix store, and is the only way to get around the checks insecretPath
. This is for scenarios where you don't care about a secret being in the Nix store.On the side this also introduces
lib.strings.validDerivationName
for generating a valid derivation name from a potentially invalid one.Things done
secretInNixStore
is usedtypes.path
withtypes.secretPath
where appropriate_secretInNixStore
attribute doesn't end up in the nix store. This doesn't matter much since the secret is already in the store, but we shouldn't help an attacker by letting them know which files are secrets and which aren'tPing @Profpatsch @rycee @roberth