Hashing plain-text password directly in configuration.nix

Hashing plain-text password directly in configuration.nix.

It would be nice to put the user’s plain-text password in configuration.nix and have it automatically converted to a hash.

This can be done, if you add a field called password and hash the value and over-write that field with hashedPassword during the nixos-rebuild process. This will make setup easier for users. This is one of many improvements that should be made to the installation process. When added up, these improvements will drastically simplify deployment.

Example:

users.users.alice = {
  isNormalUser = true;
  home = "/home/alice";
  description = "Alice Foobar";
  extraGroups = [ "wheel" "networkmanager" ];
  openssh.authorizedKeys.keys = [ "ssh-dss AAAAB3Nza... alice@foobar" ];
  password = "plaintext";
};

Clarification: The .nix file should be pre-processed to replace the human-friendly plaintext “password” field with a secure hashed text “hashedPassword” field.

1 Like

That way the password would end up in plaintext in the world-readable *.drv file as well… EDIT: at least unless the hashing were purely evaluation-time thing, but I’m not sure about suitability of such an approach.

Vladimír čunát via Nix community nixos1@discoursemail.com writes:

That way the password would end up in plaintext in the world-readable *.drv file as well… EDIT: at least unless the hashing were purely evaluation-time thing, but I’m not sure about suitability of such an approach.

I think it would make sense as a follow-up to [RFC 0005] Nix encryption by edolstra · Pull Request #5 · NixOS/rfcs · GitHub

1 Like

I have a related suggestion that can improve the user install experience: if you follow the installation section step-by-step, it suggests you run useradd to create users, with no mention that you can declaratively manage user accounts in configuration.nix until chapter 7:

We should put a sentence in there letting users known they have a choice.

4 Likes

I’d caution against casually using this, as it mixes safe to share data with definitely not safe to share data.

4 Likes

Even when you’re using declarative user accounts, NixOS will not mess with passwords, so you can still use passwd to change them. So this is perfectly safe:

  users.users.eelco =
    { isNormalUser = true;
      description = "Eelco Dolstra";
      extraGroups = [ "wheel" ];
    };

It’s only unsafe to set users.users.<name>.password, since it stores the password in plaintext in the Nix store.

4 Likes

Yup. If you don’t take a step back and question the sudden transition to using commands to add users, you’ll never even know. Inconsistency is key in a relationship…we computer people aren’t so good at relationships.

I’m not recommending to save this information long-term. I’m saying the nixos-rebuild build process should modify the .nix file in-line. When I wrote:

over-write that field with hashedPassword

I meant literally over-writing the source .nix file.

If the language that the compiler is written in is as powerful as Haskell, it will be easy to use pattern-matching to find the phrase and replace it. Alternately, instead of re-writing the compiler, we can pre-process the text file: password = "plaintext"; gets pre-processed into hashedPassword = "hashed password".

Example command line procedure to do this:

$ preprocess code.nix  # modifies code.nix
$ nixos-rebuild build  # builds using the processed code.nix

It would be a good idea in any case to have a layer of pre-processing to transform fields like this that require plugging and chugging user-friendly representations through an algorithm before being safe and valid.

Note:
Literally over-writing a file is a dangerous operation. I’d recommend writing the modified code to a new file, renaming the old file, renaming the new file to the old file’s old name, and then deleting the old file, to avoid any unsafe file-system copy problems.

Having Nix literally overwrite your Nix files is unprecedented in NixOS. That’s not a technique I would like to see make its way into common use. I’d much rather see nixos-install prompt for passwds for every user (currently only does root) as a solution to this problem.

5 Likes

Another option would be providing some sort of “password to hash” function as a builtin. We’ve got several hashing functions but I don’t know if they’re compatible as unix password hashes. Then the configuration.nix could contain a plaintext password, and at eval time this would be converted to a hash so that the plaintext password never enters the nix store.

This of course would still be unrecommended IMO because the plaintext password should not be written anywhere, not even configuration.nix. Regardless, it does sound like a strict upgrade to the current behavior of the password option.

3 Likes

That might work, but builtins.hashString currently only supports hashes that aren’t suitable for passwords.

I agree vis “overwriting files” - that way lies madness

The only way to do this that I can see is to develop a protocol for including a file which has encrypted passwords generated by some program “pre install time”, and distribute that file along with the .nix file

Whats the usecase where people want to distribute passwords with configuration? I would suggest that usecase needs close analysis before technology is designed to solve it

1 Like

What’s the point in having all these powerful languages if we can’t even make the user experience sane? If you’re worried about file operations being interrupted during a write to a .nix file, then isolate the problem by moving all user/password data to a separate .nix file. Arguably, this shouldn’t even be a concern, since you’re already trusting that the entire system won’t corrupt the config files.

Having a step in the nixos-install process is not a solution…

Use case:
Users (Admins, i.e. smart home users) of computers need to be able to streamline their approach to managing the configuration. This means not being forced to manually run their desired password through a command line utility first and then copy/paste that into the config file.

Remember, the big promise of computing is to REDUCE the work the user has to do to maintain something, not INCREASE it.

You say “unprecedented”. That means I’m getting somewhere. There is not a simpler solution for this problem that satisfies all constraints necessary: ease of use, removal of steps, and security for deployment. Deploying a configuration file whose plaintext passwords get edited and replaced with hashed passwords is a small risk with a large reward. Any sane person would immediately OK this.

To preserve integrity of the files, you can have the nixos-rebuild build process first copy the files to temporary files and then edit those files. Then, rename the old files, and rename the new files to the old file-names, and delete the old renamed files. This is for any filesystem that does not have atomic file write operations. For filesystems with atomic file write operations, this roundabout way of writing files is unnecessary, and for filesystems with automatic file backups, this is actually detrimental to the file history and should be avoided.

If your goal is to allow users to easily set a password without storing that password in the Nix file or store permanently, then I see no drawback to just telling them to use passwd on the command line.

The problem I have with this is that it turns a currently stateless system (your nix expressions) into a stateful one. Running nixos-rebuild twice with no manual changes could produce different results if there’s a bug. Adding failure modes should be avoided when there are already perfectly good solutions.

Furthermore, this requires the invention and maintenance of a new program with a complex purpose: Read a file containing code from a turing complete language, identify which files it imports, identify which lines contain passwords, edit those lines, and save how ever many files as necessary atomically. Every comma in that sentence represents a hard problem that we have to get right and never screw up

This isn’t really a solution since there’s no way to do batch renaming atomically if multiple files have to be edited.

For instance, how could you possibly support this configuration with zero chance of failure?

# key.nix
{ users.users.will.passwrd = {}; }
# password.nix
"hunter2"
# configuration.nix
{ ... }: {
  imports = [(
    lib.mapAttrs
      (_: _: import ./password.nix)
      (import ./key.nix)
  )];
  # ...
}
2 Likes

The entire point of using NixOS is to modify text files that are then reflected in the OS configuration. There is a drawback. It’s called impedance mismatch. The analogy you want is object-relational impedance mismatch.

The entire point of Nix language is to streamline configuration by removing the need to memorize a bunch of stupid commands.

The Nix expression is not stateless. It represents a single state. You change the state by modifying the file. It is no different to have a program filter the file and modify it for you, to remove the need to do routine BS like running a hash function in a terminal. Do you really think the inventors of PHP were complaining about how HTML was “stateless”?

One of the primary attractions of functional languages is pattern matching. You’ve clearly missed a lecture about how pattern matching a syntax in Haskell is dramatically simpler than in C-like languages.

As I stated before, the field would be denoted by a special name, like plaintext_password, and the field name would be replaced with one that is already supported by the Nix language, with the value replaced with the valid hashed password. It’s fool-proof.

The Nix language already has a syntax parser, so this modification sounds like a dead easy addition. The creators have already done the work of determining the context. All they have to do is add one more search string to match in the proper context. That’s it.

What are you even talking about? Every language worth its salt has multithreading and file I/O. The concepts of atomic editing are fully compatible with the concepts of multithreading. Each thread would be responsible for one atomic operation. Done in Haskell, the file IOs would be done on separate threads, even without using special syntax, if I am to believe that monads in fact allow otherwise impure processes to be treated as pure. Even if this isn’t done in Haskell, this could be done in virtually any language: C, Python, etc. with a threading library or built-in syntax.

Again, I would never have thought of such a convoluted implementation of such a simple idea. The plaintext password would be in the same place where the user is declared, not in a separate file on its own. You don’t need to do any mapping at all. I’m saying you can just separate the user declarations from the main configuration, so that the administrator doesn’t risk killing their main file, in the event that the program modifies the file containing the plain-text password. The logic is that, if the file doesn’t contain a match for the plaintext_password field, it never needs to be opened as writable, thus not risking any corruption.

There is no API for moving many files simultaneously. However the program chooses to move many files, it can always be killed in the middle, after some files have been moved but before others have. You’ll definitely break people’s configs by doing this eventually.

You missed the point of my example. It’s not about producing files like those, it’s about handling the case where the user sets their password with such files; a thing they are very much allowed to do. You can’t produce a program that will always find and rewrite any possible password declaration. Nix is turing complete, so a regex can only find declarations formatted in a particular way.

Finally, you still haven’t addressed the biggest issue: How could this be implemented without putting the password in plaintext in the world-readable nix store?

3 Likes

Ahem… You might want to check who you talking to. Just saying…

5 Likes

Nix is turing complete, so a regex can only find declarations formatted in a particular way.

If I understand correctly the proposal the idea is essentially to add a syntax construct. (Of course, it was never actually described like that, although there was a mention of making an assignment to some specially named attribute be a separate syntactical construct from assignment. So if you want to write the actual assignment, just use list to attrset conversion, I guess.) Then, just like Perl’s ACME::Bleach, Nix (or maybe a separate tool) could rewrite the source files at evaluation time (or maybe it becomes a separate preprocessing time?). (Ignoring the minor detail of Nix parser apparently not being succesfully reused anywhere, and the alternative Nix parsers needing to be kept in perfect sync.)

How could this be implemented without putting the password in plaintext in the world-readable nix store?

That part is actually easy — even a pluggable builtin could do it at evaluation time. Of course, a single non-pluggable builtin is not enough because of password hashing schemas in different tools not being in perfect synchronisation.

However the program chooses to move many files, it can always be killed in the middle, after some files have been moved but before others have.

Well, if we avoid Python3-level transition design failure and make sure that rewriting is idempotent one could rerun the entire process in case of failure (only per-file atomicity is needed in this case). Don’t forget to tell all the users to make sure they never check in any configurations before running the rewriting.

Of course, if we buy all the arguments about simplicity of this trick (which ignore maintainability), then this tool doesn’t really need to be shipped as a part of Nix and NixOS, and can be just a third-party rewriter packaged in Nixpkgs.

1 Like

First off, I don’t see any improvement in running a command to get a password hash vs running a command to convert a file. Any new command would not be an improvement, but rather add more places for user errors and bugs.

Secondly, NixOS isn’t primarily about not having to remember a command. And even if it was, this is not about nix*, but about security standards! And as always, security and comfort are not really best friends…

IMHO it misses completely the point of security if a secret user password is ever even written plainly to a world readable file! For dev passwords it’s okay, but anything else is just wrong.

The only really convenient way I see is having a custom function in emacs/vim/someothereditor which asks for a password and writes down the hash.

3 Likes

That’s because the user doesn’t run the command. Why is your kneejerk reaction to force the user to memorize another command? The entire point is to eliminate steps necessary to maintain the system.