How to set environment variables with sops-nix?

Hello, I’ve recently managed to set up sops-nix for storing secrets and using it to set the user password.
However, I’m not sure how to set up environment variables with secrets.
For example, if I want to set my github token, do I just do
environment.variables.GITHUB_TOKEN = builtins.readFile /run/secrets/github-token?
In this case, there is an error “access to absolute path ‘/run/secrets/github-token’ is forbidden in pure eval mode (use ‘–impure’ to override)”
Is there a better way to do this?

1 Like

Doing that would defy the purpose of using sops-nix. builtins.readFile-ing will take the file and place it in your nix store, publicly readable and unencrypted. Luckily pure mode has saved you from leaking your secret here.

One way to fix this is to simply read the file in your shell init:

{ config, ... }: {
  programs.bash.shellInit = ''
    export GITHUB_TOKEN="$(cat ${config.sops.secrets.github-token.path})"
  '';
}

Generally it’s preferred to use whatever mechanism the application you’re trying to give the secret has to read it from a file. gh seems to prefer you using gh auth login.

Also alternatively you could use something like keepassxc and the secret service to provide these secrets to your applications when you use them, instead of setting them at boot with sops-nix. I generally prefer this for things that do not need to have access to the secret at boot (such as gh), because then you can keep the secrets encrypted at rest and don’t need to expose them to your configuration at all.

On personal computers, sops-nix is more useful for wifi or VPN passwords or such.

As a side note, in general when using sops secrets, access their location using the .path attribute of the config option. That way if anything changes on the sops config end you don’t need to propagate that everywhere.

5 Likes

Thank you so much for the detailed explanation! I will look into keepassxc

Just updating because I haven’t found any other discussion with a solution.
In a systemd service I could make it work using

  systemd.services.frigate = {
    serviceConfig = {
      EnvironmentFile = config.sops.secrets.FRIGATE_PLUS_API_KEY.path;
    };
  };
3 Likes

can you also post the sops part of the configuration too? I don’t know what’s supposed to be the content of your file in .path.

NVM, since I solved it myself, here are my tips:

  # sops-nix
  sops.defaultSopsFile = ./secrets.yaml;
  sops.defaultSopsFormat = "yaml";

  sops.secrets = {
    "database/password" = { };
  };
  sops.templates."p2p-scout-db.conf".content = ''
    DBPASSWORD=${config.sops.placeholder."database/password"}
  '';

on your systemd service configuration do this then:

    serviceConfig = {
      Type = "exec";
      EnvironmentFile = config.sops.templates."p2p-scout-db.conf".path;
      ExecStart = ''your-command --password $DBPASSWORD"';
      Restart = "on-failure";
      RestartSec = 10;
    };

Note the line EnvironmentFile = config.sops.templates."p2p-scout-db.conf".path;

1 Like

That’s somewhat insecure, the password will be exposed globally via /proc unless you specifically configure your kernel not to.

It’s best to use whatever other mechanisms your application has for password provision (e.g. env variable, or reading from files directly).

exposed on /proc by what? systemd? sops?

By the kernel, as TLATER mentioned.
And of course the contents of ExecStart would be the command line for the process where that command is run.
If this is a problem for you ultimately depends on your threat model, of course.

3 Likes

To be extra clear, linux puts the command line arguments of processes in a file in their world-readable /proc (read: “processes”) directory, unless you configure it not to.

I believe it’s just so you can list running processes, and so interpreters don’t just give you the binary name, but actually tell you what they’re doing. May even be a POSIX requirement. Just happens to cause issues under some conditions, and be very overlooked since people don’t think about it.

5 Likes