Managing Secrets in NixOS Home Manager with SOPS

Discover how to securely manage secrets in NixOS Home Manager using SOPS and sops-nix. Learn to set up Age encryption, create encrypted secret files, integrate SOPS with NixOS, and access secrets as environment variables. Perfect for maintaining secure, declarative NixOS configurations.
Managing Secrets in NixOS Home Manager with SOPS
Screenshot of NixOS using LLM tool with secrets securely saved

When setting up a NixOS system, we often want to store secrets in environment variables for various tools to use. For example, you might want to use the llm CLI tool, which requires setting the OPENAI_API_KEY environment variable.

However, if you're using declarative configuration files or flakes to set up your NixOS system (which is a great practice!), you can't simply update those files with the secret. That would be insecure, especially if you're tracking your configurations in Git and pushing to a public repository.

Enter SOPS

To manage secrets on machines, we can use a tool called SOPS (Secrets OPerationS). SOPS allows you to manage secrets by encrypting them when storing them in a file. You use the tool to edit the secrets, and when you save, it writes them in an encrypted manner.

In NixOS, we have sops-nix, which helps manage SOPS-based secrets using configuration. Here's how it works: when SOPS secrets are loaded from a file at runtime, each secret is stored in a different file. This means at runtime, you can read from those files to get the secret and export it as an environment variable.

Setting Up SOPS with Age

We'll use Age as the encryption backend for SOPS.

Install Age and SOPS

In your home manager where you manage packages make sure to add age and sops and rebuilt to install them.

Generate an Age Key

First, create a directory for SOPS configuration and generate an Age key pair:

mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt

The public key is derived from the private key:

age-keygen -y ~/.config/sops/age/keys.txt

Copy the output public key; you'll need it to encrypt your secrets.

Create the .sops.yaml Configuration File

Create a .sops.yaml file in your project's root directory to define encryption rules and specify the public key:

   keys:
  - &host_hostname <YOUR PUBLIC KEY>
creation_rules:
  - path_regex: secrets.yaml$
    key_groups:
    - age:
      - *host_hostname

Replace <YOUR_PUBLIC_KEY> with the public key you obtained earlier. And replace hostname with your machine host-name. This configuration tells SOPS to use Age for encrypting any file named secrets.yaml using the specified public key.

Create and Encrypt the Secrets File

Use nix-shell with SOPS available to run creating the secrets.yaml file. Make sure to run it from the same directory where .sops.yaml exist. It will open the editor that is configured using EDITOR in your shell.

nix-shell -p sops --run "sops secrets.yaml"

Add your secrets to the file:

openai_api_key: 'YOUR_OPENAI_API_KEY'

Save and exit the editor. SOPS will encrypt the file upon saving, and secrets.yaml will now contain encrypted data along with other metadata.

Integrate SOPS with NixOS Using sops-nix

Update Your flake.nix

Add sops-nix to your Nix flakes:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
    sops-nix.url = "github:Mic92/sops-nix";
    # other inputs...
  };

  outputs = { self, nixpkgs, sops-nix, ... }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs { inherit system; };
    in
    {
      nixosConfigurations.mySystem = pkgs.lib.nixosSystem {
        inherit system;
        modules = [
          ./configuration.nix
          sops-nix.nixosModules.sops
          # other modules...
          home-manager.nixosModules.home-manager
          {
            # other config...
            home-manager.sharedModules = [
              sops-nix.homeManagerModules.sops
            ];
          }
        ];
      };
    };
}

Adding sops-nix to the Inputs:

inputs = {
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
  sops-nix.url = "github:Mic92/sops-nix";
  # other inputs...
};

This line adds the sops-nix input, fetching the sops-nix repository from GitHub. It provides the necessary modules to integrate SOPS (Secrets OPerationS) into your NixOS system.

Including the SOPS NixOS Module:

modules = [
  ./configuration.nix
  sops-nix.nixosModules.sops
  # other modules...
];

By adding sops-nix.nixosModules.sops to the modules list, you're integrating the SOPS module into your NixOS system configuration. This enables system-wide management of encrypted secrets.

Integrating SOPS with Home Manager:

home-manager.nixosModules.home-manager
{
  # other config...
  home-manager.sharedModules = [
    sops-nix.homeManagerModules.sops
  ];
}

This section includes the SOPS module in Home Manager's shared modules by adding sops-nix.homeManagerModules.sops. It allows you to manage user-specific secrets through SOPS within your home environment configurations.

Configure SOPS in home.nix

In your NixOS configuration, set up SOPS:

{
  sops = {
    age.keyFile = "/home/<your username>/.config/sops/age/keys.txt"; # must have no password!

    defaultSopsFile = ./secrets.yaml;
    defaultSymlinkPath = "/run/user/1000/secrets";
    defaultSecretsMountPoint = "/run/user/1000/secrets.d";

    secrets.openai_api_key = {
      # sopsFile = ./secrets.yml.enc; # optionally define per-secret files
      path = "${config.sops.defaultSymlinkPath}/openai_api_key";
    };
  };
}

This configuration tells NixOS to use the encrypted secrets.yaml file and makes the openai_api_key available at the specified path. Whenever you add a new secret, you should update the config here with that secret info as well.

Accessing the Secret as an Environment Variable

In your config you can use $(cat ${config.sops.secrets.openai_api_key.path}) to get the secret. For example I configure my zsh file to export the OPENAI_API_KEY.

 programs.zsh = {
    initExtra = ''
      # other config...
      export OPENAI_API_KEY=$(cat ${config.sops.secrets.openai_api_key.path})
    '';
  };

Reload Configuration

After updating your configurations, rebuild your NixOS system:

sudo nixos-rebuild switch

Now, your OPENAI_API_KEY is securely stored and accessible as an environment variable without exposing the key in your configuration files.

Conclusion

By integrating SOPS with NixOS, you maintain a declarative and secure configuration while managing sensitive secrets. This setup ensures that API keys and other confidential data remain encrypted in your repositories and are only decrypted on your local machine during runtime.

Remember to always be cautious when dealing with secrets and never commit unencrypted secrets to your repository!

References