• Stars
    star
    1,439
  • Rank 32,712 (Top 0.7 %)
  • Language
    Nix
  • License
    MIT License
  • Created over 4 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Atomic secret provisioning for NixOS based on sops

sops-nix

sops-nix logo

Atomic, declarative, and reproducible secret provisioning for NixOS based on sops.

How it works

Secrets are decrypted from sops files during activation time. The secrets are stored as one secret per file and access-controlled by full declarative configuration of their users, permissions, and groups. GPG keys or age keys can be used for decryption, and compatibility shims are supported to enable the use of SSH RSA or SSH Ed25519 keys. Sops also supports cloud key management APIs such as AWS KMS, GCP KMS, Azure Key Vault and Hashicorp Vault. While not officially supported by sops-nix yet, these can be controlled using environment variables that can be passed to sops.

Features

  • Compatible with all NixOS deployment frameworks: NixOps, nixos-rebuild, krops, morph, nixus, etc.
  • Version-control friendly: Since all files are encrypted they can be directly committed to version control without worry. Diffs of the secrets are readable, and can be shown in cleartext.
  • CI friendly: Since sops files can be added to the Nix store without leaking secrets, a machine definition can be built as a whole from a repository, without needing to rely on external secrets or services.
  • Home-manager friendly: Provides a home-manager module
  • Works well in teams: sops-nix comes with nix-shell hooks that allows multiple people to quickly import all GPG keys. The cryptography used in sops is designed to be scalable: Secrets are only encrypted once with a master key instead of encrypted per machine/developer key.
  • Atomic upgrades: New secrets are written to a new directory which replaces the old directory atomically.
  • Rollback support: If sops files are added to the Nix store, old secrets can be rolled back. This is optional.
  • Fast time-to-deploy: Unlike solutions implemented by NixOps, krops and morph, no extra steps are required to upload secrets.
  • A variety of storage formats: Secrets can be stored in YAML, dotenv, INI, JSON or binary.
  • Minimizes configuration errors: sops files are checked against the configuration at evaluation time.

Demo

There is a configuration.nix example in the deployment step of our usage example.

Supported encryption methods

sops-nix supports two basic ways of encryption, GPG and age.

GPG is based on GnuPG and encrypts against GPG public keys. Private GPG keys may be used to decrypt the secrets on the target machine. The tool ssh-to-pgp can be used to derive a GPG key from a SSH (host) key in RSA format.

The other method is age which is based on age. The tool (ssh-to-age) can convert SSH host or user keys in Ed25519 format to age keys.

Usage example

1. Install sops-nix

Choose one of the following methods. When using it non-globally with home-manager, refer to Use with home-manager.

Flakes (current recommendation)

If you use experimental nix flakes support:

{
  inputs.sops-nix.url = "github:Mic92/sops-nix";
  # optional, not necessary for the module
  #inputs.sops-nix.inputs.nixpkgs.follows = "nixpkgs";

  outputs = { self, nixpkgs, sops-nix }: {
    # change `yourhostname` to your actual hostname
    nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem {
      # customize to your system
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
        sops-nix.nixosModules.sops
      ];
    };
  };
}

niv (recommended if not using flakes)

First add it to niv:

$ niv add Mic92/sops-nix

Then add the following to your configuration.nix in the imports list:

{
  imports = [ "${(import ./nix/sources.nix).sops-nix}/modules/sops" ];
}

nix-channel

As root run:

$ nix-channel --add https://github.com/Mic92/sops-nix/archive/master.tar.gz sops-nix
$ nix-channel --update

Then add the following to your configuration.nix in the imports list:

{
  imports = [ <sops-nix/modules/sops> ];
}

fetchTarball

Add the following to your configuration.nix:

{
  imports = [ "${builtins.fetchTarball "https://github.com/Mic92/sops-nix/archive/master.tar.gz"}/modules/sops" ];
}

or with pinning:

{
  imports = let
    # replace this with an actual commit id or tag
    commit = "298b235f664f925b433614dc33380f0662adfc3f";
  in [ 
    "${builtins.fetchTarball {
      url = "https://github.com/Mic92/sops-nix/archive/${commit}.tar.gz";
      # replace this with an actual hash
      sha256 = "0000000000000000000000000000000000000000000000000000";
    }}/modules/sops"
  ];
}
2. Generate a key for yourself

This key will be used for you to edit secrets.

You can generate yourself a key:

# for age..
$ mkdir -p ~/.config/sops/age
$ age-keygen -o ~/.config/sops/age/keys.txt
# or to convert an ssh ed25519 key to an age key
$ mkdir -p ~/.config/sops/age
$ nix-shell -p ssh-to-age --run "ssh-to-age -private-key -i ~/.ssh/id_ed25519 > ~/.config/sops/age/keys.txt"
# for GPG >= version 2.1.17
$ gpg --full-generate-key
# for GPG < 2.1.17
$ gpg --default-new-key-algo rsa4096 --gen-key

Or you can use the ssh-to-pgp tool to get a GPG key from an SSH key:

$ nix-shell -p gnupg -p ssh-to-pgp --run "ssh-to-pgp -private-key -i $HOME/.ssh/id_rsa | gpg --import --quiet"
2504791468b153b8a3963cc97ba53d1919c5dfd4
# This exports the public key
$ nix-shell -p ssh-to-pgp --run "ssh-to-pgp -i $HOME/.ssh/id_rsa -o $USER.asc"
2504791468b153b8a3963cc97ba53d1919c5dfd4

(Note that ssh-to-pgp only supports RSA keys; to use Ed25519 keys, use age.)
If you get the following,

ssh-to-pgp: failed to parse private ssh key: ssh: this private key is passphrase protected

then your SSH key is encrypted with your password and you will need to create an unencrypted copy temporarily.

$ cp $HOME/.ssh/id_rsa /tmp/id_rsa
$ ssh-keygen -p -N "" -f /tmp/id_rsa
$ nix-shell -p gnupg -p ssh-to-pgp --run "ssh-to-pgp -private-key -i /tmp/id_rsa | gpg --import --quiet"
$ rm /tmp/id_rsa

You can also use an existing SSH Ed25519 key as an age key; to do so, see the following.

How to find the public key of an `age` key

If you generated an age key, the age public key can be found via age-keygen -y $PATH_TO_KEY:

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

Otherwise, you can convert an existing SSH key into an age public key:

$ nix-shell -p ssh-to-age --run "ssh-to-age < ~/.ssh/id_ed25519.pub"
# or
$ nix-shell -p ssh-to-age --run "ssh-add -L | ssh-to-age"
How to find the GPG fingerprint of a key

Invoke this command and look for your key:

$ gpg --list-secret-keys
/tmp/tmp.JA07D1aVRD/pubring.kbx
-------------------------------
sec   rsa2048 1970-01-01 [SCE]
      9F89C5F69A10281A835014B09C3DC61F752087EF
uid           [ unknown] root <root@localhost>

The fingerprint here is 9F89C5F69A10281A835014B09C3DC61F752087EF.

Your age public key or GPG fingerprint can be written to your .sops.yaml in the root of your configuration directory or repository:

# This example uses YAML anchors which allows reuse of multiple keys 
# without having to repeat yourself.
# Also see https://github.com/Mic92/dotfiles/blob/master/nixos/.sops.yaml
# for a more complex example.
keys:
  - &admin_alice 2504791468b153b8a3963cc97ba53d1919c5dfd4
  - &admin_bob age12zlz6lvcdk6eqaewfylg35w0syh58sm7gh53q5vvn7hd7c6nngyseftjxl
creation_rules:
  - path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
    key_groups:
    - pgp:
      - *admin_alice
      age:
      - *admin_bob

Note: Be sure to not include a - before subsequent key types under key_groups (i.e. age in the above example should not have a - in front). This will otherwise cause sops to require multiple keys (shamir secret sharing) to decrypt a secret, which breaks normal sops-nix usage.

3. Get a public key for your target machine

The easiest way to add new machines is by using SSH host keys (this requires OpenSSH to be enabled).

If you are using age, the ssh-to-age tool can be used to convert any SSH Ed25519 public key to the age format:

$ nix-shell -p ssh-to-age --run 'ssh-keyscan example.com | ssh-to-age'
age1rgffpespcyjn0d8jglk7km9kfrfhdyev6camd3rck6pn8y47ze4sug23v3
$ nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
age1rgffpespcyjn0d8jglk7km9kfrfhdyev6camd3rck6pn8y47ze4sug23v3

For GPG, since sops does not natively support SSH keys yet, sops-nix supports a conversion tool (ssh-to-pgp) to store them as GPG keys:

$ ssh root@server01 "cat /etc/ssh/ssh_host_rsa_key" | nix-shell -p ssh-to-pgp --run "ssh-to-pgp -o server01.asc"
# or with sudo
$ ssh youruser@server01 "sudo cat /etc/ssh/ssh_host_rsa_key" | nix-shell -p ssh-to-pgp --run "ssh-to-pgp -o server01.asc"
0fd60c8c3b664aceb1796ce02b318df330331003
# or just read them locally/over ssh
$ nix-shell -p ssh-to-pgp --run "ssh-to-pgp -i /etc/ssh/ssh_host_rsa_key -o server01.asc"
0fd60c8c3b664aceb1796ce02b318df330331003

The output of these commands is the identifier for the server's key, which can be added to your .sops.yaml:

keys:
  - &admin_alice 2504791468b153b8a3963cc97ba53d1919c5dfd4
  - &admin_bob age12zlz6lvcdk6eqaewfylg35w0syh58sm7gh53q5vvn7hd7c6nngyseftjxl
  - &server_azmidi 0fd60c8c3b664aceb1796ce02b318df330331003
  - &server_nosaxa age1rgffpespcyjn0d8jglk7km9kfrfhdyev6camd3rck6pn8y47ze4sug23v3
creation_rules:
  - path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
    key_groups:
    - pgp:
      - *admin_alice
      - *server_azmidi
      age:
      - *admin_bob
      - *server_nosaxa
  - path_regex: secrets/azmidi/[^/]+\.(yaml|json|env|ini)$
    key_groups:
    - pgp:
      - *admin_alice
      - *server_azmidi
      age:
      - *admin_bob

If you prefer having a separate GPG key, see Use with GPG instead of SSH keys.

4. Create a sops file

To create a sops file you need write a .sops.yaml as described above.

When using GnuPG you also need to import your personal GPG key (and your colleagues) and your servers into your GPG key chain.

sops-nix can automate the import of GPG keys with a hook for nix-shell, allowing public keys to be shared via version control (i.e. git).
# shell.nix
with import <nixpkgs> {};
let
  sops-nix = builtins.fetchTarball {
    url = "https://github.com/Mic92/sops-nix/archive/master.tar.gz";
  };
in
mkShell {
  # imports all files ending in .asc/.gpg
  sopsPGPKeyDirs = [ 
    "${toString ./.}/keys/hosts"
    "${toString ./.}/keys/users"
  ];
  # Also single files can be imported.
  #sopsPGPKeys = [ 
  #  "${toString ./.}/keys/users/mic92.asc"
  #  "${toString ./.}/keys/hosts/server01.asc"
  #];
  
  # This hook can also import gpg keys into its own seperate
  # gpg keyring instead of using the default one. This allows
  # to isolate otherwise unrelated server keys from the user gpg keychain.
  # By uncommenting the following lines, it will set GNUPGHOME
  # to .git/gnupg. 
  # Storing it inside .git prevents accedentially commiting private keys.
  # After setting this option you will also need to import your own
  # private key into keyring, i.e. using a a command like this 
  # (replacing 0000000000000000000000000000000000000000 with your fingerprint)
  # $ (unset GNUPGHOME; gpg --armor --export-secret-key 0000000000000000000000000000000000000000) | gpg --import
  #sopsCreateGPGHome = true;
  # To use a different directory for gpg dirs set sopsGPGHome
  #sopsGPGHome = "${toString ./.}/../gnupg";
  
  nativeBuildInputs = [
    (pkgs.callPackage sops-nix {}).sops-import-keys-hook
  ];
}

A valid directory structure for this might look like:

$ tree .
.
├── keys
│   ├── hosts
│   │   └── server01.asc
│   └── users
│       └── mic92.asc

After configuring .sops.yaml, you can open a new file with sops:

$ nix-shell -p sops --run "sops secrets/example.yaml"

This will start your configured editor located at the $EDITOR environment variable.
An example secret file might be:

# Files must always have a string value
example-key: example-value
# Nesting the key results in the creation of directories.
# These directories will be owned by root:keys and have permissions 0751.
myservice:
  my_subdir:
    my_secret: password1

An example result when saving this file could be:

example-key: ENC[AES256_GCM,data:AB8XMyid4P7mXdjj+A==,iv:RRsZC+V+3w22pOi/2TCjBYn/0OYsNGCu5CT1ZBSKGi0=,tag:zT5mlujrSuA6KKxLKL8CMQ==,type:str]
#ENC[AES256_GCM,data:59QWbzCQCP7kLdhyjFOZe503MgegN0kv505PBNHwjp6aYztDHwx2N9+A1Bz6G/vWYo+4LpBo8/s=,iv:89q3ZXgM1wBUg5G29ROor3VXrO3QFGCvfwDoA3+G14M=,tag:hOSnEZ6DKycnF37LCXOjzg==,type:comment]
#ENC[AES256_GCM,data:kUuJCkDE9JT9C+kdNe0CSB3c+gmgE4We1OoX4C1dWeoZCw/o9/09CzjRi9eOBUEL0P1lrt+g6V2uXFVq4n+M8UPGUAbRUr3A,iv:nXJS8wqi+ephoLynm9Nxbqan0V5dBstctqP0WxniSOw=,tag:ALx396Z/IPCwnlqH//Hj3g==,type:comment]
myservice:
    my_subdir:
        my_secret: ENC[AES256_GCM,data:hcRk5ERw60G5,iv:3Ur6iH1Yu0eu2otcEv+hGRF5kTaH6HSlrofJ5JXvewA=,tag:hpECXFnMhGNnAxxzuGW5jg==,type:str]
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age:
        - recipient: age12zlz6lvcdk6eqaewfylg35w0syh58sm7gh53q5vvn7hd7c6nngyseftjxl
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1dFYvSTRHa3IwTVpuZjEz
            SDZZQnc5a0dGVGEzNXZmNEY5NlZDbVgyNVU0Clo3ZC9MRGp4SHhLUTVCeWlOUUxS
            MEtPdW4rUHhjdFB6bFhyUXRQTkRpWjAKLS0tIDVTbWU2V3dJNUZrK1A5U0c5bkc0
            S3VINUJYc3VKcjBZbHVqcGJBSlVPZWcKqPXE01ienWDbTwxo+z4dNAizR3t6uTS+
            KbmSOK1v61Ri0bsM5HItiMP+fE3VCyhqMBmPdcrR92+3oBmiSFnXPA==
            -----END AGE ENCRYPTED FILE-----
        - recipient: age18jtffqax5v0t6ehh4ypaefl4mfhcrhn6ek3p80mhfp9psx6pd35qew2ww3
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzT3FxcDEzaFRQOVFpNkg2
            Skw4WEIxZzNTWkNBaDRhcUN2ejY4QTAwTERvCkx2clIzT2wyaFJZcjl0RkFXL2p6
            enhqVEZ3ZkNKUU5jTlUxRC9Lb090TzAKLS0tIDBEaG00RFJDZ3ZVVjBGUWJkRHdQ
            YkpudG43eURPVWJUejd3Znk5Z29lWlkK0cIngn2qdmiOE5rHOHxTRcjfZYuY3Ej7
            Yy7nYxMwTdYsm/V6Lp2xm8hvSzBEIFL+JXnSTSwSHnCIfgle5BRbug==
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2021-11-20T16:21:10Z"
    mac: ENC[AES256_GCM,data:5ieT/yv1GZfZFr+OAZ/DBF+6DJHijRXpjNI2kfBun3KxDkyjiu/OFmAbsoVFY/y6YCT3ofl4Vwa56Veo3iYj4njgxyLpLuD1B6zkMaNXaPywbAhuMho7bDGEJZHrlYOUNLdBqW2ytTuFA095IncXE8CFGr38A2hfjcputdHk4R4=,iv:UcBXWtaquflQFNDphZUqahADkeege5OjUY38pLIcFkU=,tag:yy+HSMm+xtX+vHO78nej5w==,type:str]
    pgp: []
    unencrypted_suffix: _unencrypted
    version: 3.7.1
5. Deploy

If you derived your server public key from SSH, all you need in your configuration.nix is:

{
  imports = [ <sops-nix/modules/sops> ];
  # This will add secrets.yml to the nix store
  # You can avoid this by adding a string to the full path instead, i.e.
  # sops.defaultSopsFile = "/root/.sops/secrets/example.yaml";
  sops.defaultSopsFile = ./secrets/example.yaml;
  # This will automatically import SSH keys as age keys
  sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
  # This is using an age key that is expected to already be in the filesystem
  sops.age.keyFile = "/var/lib/sops-nix/key.txt";
  # This will generate a new key if the key specified above does not exist
  sops.age.generateKey = true;
  # This is the actual specification of the secrets.
  sops.secrets.example-key = {};
  sops.secrets."myservice/my_subdir/my_secret" = {};
}

On nixos-rebuild switch this will make the keys accessible via /run/secrets/example-key and /run/secrets/myservice/my_subdir/my_secret:

$ cat /run/secrets/example-key
example-value
$ cat /run/secrets/myservice/my_subdir/my_secret
password1

/run/secrets is a symlink to /run/secrets.d/{number}:

$ ls -la /run/secrets
lrwxrwxrwx 16 root 12 Jul  6:23  /run/secrets -> /run/secrets.d/1

Set secret permission/owner and allow services to access it

By default secrets are owned by root:root. Furthermore the parent directory /run/secrets.d is only owned by root and the keys group has read access to it:

$ ls -la /run/secrets.d/1
total 24
drwxr-x--- 2 root keys   0 Jul 12  6:23 .
drwxr-x--- 3 root keys   0 Jul 12  6:23 ..
-r-------- 1 root root  20 Jul 12  6:23 example-secret

The secrets option has further parameter to change secret permission. Consider the following nixos configuration example:

{
  # Permission modes are in octal representation (same as chmod),
  # the digits represent: user|group|owner
  # 7 - full (rwx)
  # 6 - read and write (rw-)
  # 5 - read and execute (r-x)
  # 4 - read only (r--)
  # 3 - write and execute (-wx)
  # 2 - write only (-w-)
  # 1 - execute only (--x)
  # 0 - none (---)
  sops.secrets.example-secret.mode = "0440";
  # Either a user id or group name representation of the secret owner
  # It is recommended to get the user name from `config.users.<?name>.name` to avoid misconfiguration
  sops.secrets.example-secret.owner = config.users.users.nobody.name;
  # Either the group id or group name representation of the secret group
  # It is recommended to get the group name from `config.users.<?name>.group` to avoid misconfiguration
  sops.secrets.example-secret.group = config.users.users.nobody.group;
}

To access secrets each non-root process/service needs to be part of the keys group. For systemd services this can be achieved as following:

{
  systemd.services.some-service = {
    serviceConfig.SupplementaryGroups = [ config.users.groups.keys.name ];
  };
}

For login or system users this can be done like this:

{
  users.users.example-user.extraGroups = [ config.users.groups.keys.name ];
}
This example configures secrets for buildkite, a CI agent; the service needs a token and a SSH private key to function.
{ pkgs, config, ... }:
{
  services.buildkite-agents.builder = {
    enable = true;
    tokenPath = config.sops.secrets.buildkite-token.path;
    privateSshKeyPath = config.sops.secrets.buildkite-ssh-key.path;

    runtimePackages = [
      pkgs.gnutar
      pkgs.bash
      pkgs.nix
      pkgs.gzip
      pkgs.git
    ];

  };

  systemd.services.buildkite-agent-builder = {
    serviceConfig.SupplementaryGroups = [ config.users.groups.keys.name ];
  };

  sops.secrets.buildkite-token.owner = config.users.buildkite-agent-builder.name;
  sops.secrets.buildkite-ssh-key.owner = config.users.buildkite-agent-builder.name;
}

Restarting/reloading systemd units on secret change

It is possible to restart or reload units when a secret changes or is newly initialized.

This behavior can be configured per-secret:

{
  sops.secrets."home-assistant-secrets.yaml" = {
    restartUnits = [ "home-assistant.service" ];
    # there is also `reloadUnits` which acts like a `reloadTrigger` in a NixOS systemd service
  };
}

Symlinks to other directories

Some services might expect files in certain locations. Using the path option a symlink to this directory can be created:

{
  sops.secrets."home-assistant-secrets.yaml" = {
    owner = "hass";
    path = "/var/lib/hass/secrets.yaml";
  };
}
$ ls -la /var/lib/hass/secrets.yaml
lrwxrwxrwx 1 root root 40 Jul 19 22:36 /var/lib/hass/secrets.yaml -> /run/secrets/home-assistant-secrets.yaml

Setting a user's password

sops-nix has to run after NixOS creates users (in order to specify what users own a secret.) This means that it's not possible to set users.users.<name>.passwordFile to any secrets managed by sops-nix. To work around this issue, it's possible to set neededForUsers = true in a secret. This will cause the secret to be decrypted to /run/secrets-for-users instead of /run/secrets before NixOS creates users. As users are not created yet, it's not possible to set an owner for these secrets.

{ config, ... }: {
  sops.secrets.my-password.neededForUsers = true;

  users.users.mic92 = {
    isNormalUser = true;
    passwordFile = config.sops.secrets.my-password.path;
  };
}

Different file formats

At the moment we support the following file formats: YAML, JSON, INI, dotenv and binary.

sops-nix allows specifying multiple sops files in different file formats:

{
  imports = [ <sops-nix/modules/sops> ];
  # The default sops file used for all secrets can be controlled using `sops.defaultSopsFile`
  sops.defaultSopsFile = ./secrets.yaml;
  # If you use something different from YAML, you can also specify it here:
  #sops.defaultSopsFormat = "yaml";
  sops.secrets.github_token = {
    # The sops file can be also overwritten per secret...
    sopsFile = ./other-secrets.json;
    # ... as well as the format
    format = "json";
  };
}

YAML

Open a new file with sops ending in .yaml:

$ sops secrets.yaml

Then, put in the following content:

github_token: 4a6c73f74928a9c4c4bc47379256b72e598e2bd3
ssh_key: |
  -----BEGIN OPENSSH PRIVATE KEY-----
  b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
  QyNTUxOQAAACDENhLwQI4v/Ecv65iCMZ7aZAL+Sdc0Cqyjkd012XwJzQAAAJht4at6beGr
  egAAAAtzc2gtZWQyNTUxOQAAACDENhLwQI4v/Ecv65iCMZ7aZAL+Sdc0Cqyjkd012XwJzQ
  AAAEBizgX7v+VMZeiCtWRjpl95dxqBWUkbrPsUSYF3DGV0rsQ2EvBAji/8Ry/rmIIxntpk
  Av5J1zQKrKOR3TXZfAnNAAAAE2pvZXJnQHR1cmluZ21hY2hpbmUBAg==
  -----END OPENSSH PRIVATE KEY-----

You can include it like this in your configuration.nix:

{
  sops.defaultSopsFile = ./secrets.yaml;
  # YAML is the default 
  #sops.defaultSopsFormat = "yaml";
  sops.secrets.github_token = {
    format = "yaml";
    # can be also set per secret
    sopsFile = ./secrets.yaml;
  };
}

JSON

Open a new file with sops ending in .json:

$ sops secrets.json

Then, put in the following content:

{
  "github_token": "4a6c73f74928a9c4c4bc47379256b72e598e2bd3",
  "ssh_key": "-----BEGIN OPENSSH PRIVATE KEY-----\\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\\nQyNTUxOQAAACDENhLwQI4v/Ecv65iCMZ7aZAL+Sdc0Cqyjkd012XwJzQAAAJht4at6beGr\\negAAAAtzc2gtZWQyNTUxOQAAACDENhLwQI4v/Ecv65iCMZ7aZAL+Sdc0Cqyjkd012XwJzQ\\nAAAEBizgX7v+VMZeiCtWRjpl95dxqBWUkbrPsUSYF3DGV0rsQ2EvBAji/8Ry/rmIIxntpk\\nAv5J1zQKrKOR3TXZfAnNAAAAE2pvZXJnQHR1cmluZ21hY2hpbmUBAg==\\n-----END OPENSSH PRIVATE KEY-----\\n"
}

You can include it like this in your configuration.nix:

{
  sops.defaultSopsFile = ./secrets.json;
  # YAML is the default 
  sops.defaultSopsFormat = "json";
  sops.secrets.github_token = {
    format = "json";
    # can be also set per secret
    sopsFile = ./secrets.json;
  };
}

Binary

This format allows to encrypt an arbitrary binary format that can't be put into JSON/YAML files. Unlike the other two formats, for binary files, one file corresponds to one secret.

To encrypt an binary file use the following command:

$ sops -e /tmp/krb5.keytab > krb5.keytab
# an example of what this might result in:
$ head krb5.keytab
{
        "data": "ENC[AES256_GCM,data:bIsPHrjrl9wxvKMcQzaAbS3RXCI2h8spw2Ee+KYUTsuousUBU6OMIdyY0wqrX3eh/1BUtl8H9EZciCTW29JfEJKfi3ackGufBH+0wp6vLg7r,iv:TlKiOmQUeH3+NEdDUMImg1XuXg/Tv9L6TmPQrraPlCQ=,tag:dVeVvRM567NszsXKK9pZvg==,type:str]",
        "sops": {
                "kms": null,
                "gcp_kms": null,
                "azure_kv": null,
                "lastmodified": "2020-07-06T06:21:06Z",
                "mac": "ENC[AES256_GCM,data:ISjUzaw/5mNiwypmUrOk2DAZnlkbnhURHmTTYA3705NmRsSyUh1PyQvCuwglmaHscwl4GrsnIz4rglvwx1zYa+UUwanR0+VeBqntHwzSNiWhh7qMAQwdUXmdCNiOyeGy6jcSDsXUeQmyIWH6yibr7hhzoQFkZEB7Wbvcw6Sossk=,iv:UilxNvfHN6WkEvfY8ZIJCWijSSpLk7fqSCWh6n8+7lk=,tag:HUTgyL01qfVTCNWCTBfqXw==,type:str]",
                "pgp": [
                        {

It can be decrypted again like this:

$ sops -d krb5.keytab > /tmp/krb5.keytab

This is how it can be included in your configuration.nix:

{
  sops.secrets.krb5-keytab = {
    format = "binary";
    sopsFile = ./krb5.keytab;
  };
}

Use with home manager

sops-nix also provides a home-manager module. This module provides a subset of features provided by the system-wide sops-nix since features like the creation of the ramfs and changing the owner of the secrets are not available for non-root users.

Instead of running as an activation script, sops-nix runs as a systemd user service called sops-nix.service. And instead of decrypting to /run/secrets, the secrets are decrypted to $XDG_RUNTIME_DIR/secrets that is located on a tmpfs or similar non-persistent filesystem.

Depending on whether you use home-manager system-wide or using a home.nix, you have to import it in a different way. This example show the channel approach from the example Install: nix-channel for simplicity, but all other methods work as well.

{
  # NixOS system-wide home-manager configuration
  home-manager.sharedModules = [
    <sops-nix/modules/home-manager/sops.nix>
  ];
}
{
  # Configuration via home.nix
  imports = [
    <sops-nix/modules/home-manager/sops.nix>
  ];
}

The actual sops configuration is in the sops namespace in your home.nix (or in the home-manager.users.<name> namespace when using home-manager system-wide):

{
  sops = {
    age.keyFile = "/home/user/.age-key.txt"; # must have no password!
    # It's also possible to use a ssh key, but only when it has no password:
    #age.sshKeyPaths = [ "/home/user/path-to-ssh-key" ];
    defaultSopsFile = ./secrets.yaml;
    secrets.test = {
      # sopsFile = ./secrets.yml.enc; # optionally define per-secret files

      # %r gets replaced with a runtime directory, use %% to specify a '%'
      # sign. Runtime dir is $XDG_RUNTIME_DIR on linux and $(getconf
      # DARWIN_USER_TEMP_DIR) on darwin.
      path = "%r/test.txt"; 
    };
  };
}

The secrets are decrypted in a systemd user service called sops-nix, so other services needing secrets must order after it:

{
  systemd.user.services.mbsync.Unit.After = [ "sops-nix.service" ];
}

Use with GPG instead of SSH keys

If you prefer having a separate GPG key, sops-nix also comes with a helper tool, sops-init-gpg-key:

$ nix run github:Mic92/sops-nix#sops-init-gpg-key -- --hostname server01 --gpghome /tmp/newkey
# You can use the following command to save it to a file:
$ cat > server01.asc <<EOF
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQENBF8L/iQBCACroEaUfvPBMMorNepNQmideOtNztALejgEJ5wZmxabck+qC1Gb
NWe3tmvChXVHgL7DzodSUfX1PuIjTTeRr2clMXtISPFIsBlRQb4MiErZfsardITM
n4WScg8sTb4nnqEOJiRknwAhBryIjH8kkCXxKlYK67re281dIK4dKBMIolFADlyv
wyHurJ7NPpHxR2WXHcIqXX1DaT6RvGQvZHMpfctob8k/QD4CyV6QwG5IVACQ/tuC
bEUggrkGw+g+XdeieUfWbRsHM4C4pv8BNwA/EYD5d0eKI+rshSPoTT+hcGn8Uh8w
MVQ8PVs6jWMMOAF1JH/stoPr9Yha+TGbMRi5ABEBAAG0GHNlcnZlcjAxIDxyb290
QHNlcnZlcjAxPokBTgQTAQgAOBYhBOTKhnaPF2rrbAFVQVOvjX8UlhOxBQJfC/4k
AhsvBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEFOvjX8UlhOx1XIH/jUOrSR2
wuoqFiHcqaDPgXmTVJk8QanVkmiP3tk0mz5rRKrDX2eX5GnHqYR4PfpjUYNzedQE
sGyTjl7+DvglWJ2Q8m3yD/9+1agBmeqEVQlKqwL6Sc3bI4WBwHaxwVDo/bNwMs0w
o8ngOs1jPd3LfQdfG/rE1NolpHm4LWqYj0D2zEGqozLXVBx2wiuwmm6OKX4U4EHR
UwKax+VZYA+J9oFDN+kOy/yR+bKnOvg5eyOv2ZrK5BKceSBhDTOclMIWTL2cGxcL
jsq4N7fobs4TbwFPxRUi/T9ldXi0LXeGhTl9stImTtj3bL+4Y734TipvB5UvzCDK
CkjjwEvD5MYdGDE=
=uvIf
-----END PGP PUBLIC KEY BLOCK-----
EOF
# fingerprint: E4CA86768F176AEB6C01554153AF8D7F149613B1

You can choose between a RSA GPG key (default, like in the example above) or a Curve25519 based one by adding --keytype Curve25519 like so:

$ nix run github:Mic92/sops-nix#sops-init-gpg-key -- --hostname server01 --gpghome /tmp/newkey --keytype Curve25519
You can use the following command to save it to a file:
cat > server01.asc <<EOF
-----BEGIN PGP PUBLIC KEY BLOCK-----

mDMEY7dJExYJKwYBBAHaRw8BAQdAloRZFyqNh3nIDtyUQKaBSMJOtLkbNeg+4TPg
BG5TduG0OG5peC1hLmhvbWUua3VldGVtZWllci5kZSA8cm9vdEBuaXgtYS5ob21l
Lmt1ZXRlbWVpZXIuZGU+iJMEExYKADsWIQREE2hPxiNijOo+CSmrLxbGte+J7wUC
Y7dJEwIbAwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRCrLxbGte+J79LX
AQDtLfQFDKm04ORIk28DrzTBbMTFQEW21dGBXk7ykBx4jQD/ZOnt1RPnB9mzMc8L
wIS3oI8D9719DjoS9hrHnJ4xvge4OARjt0kTEgorBgEEAZdVAQUBAQdA0t1X35pN
ic+etscIIkHjKUwrXhbTgWrARgXUuEMwwz8DAQgHiHgEGBYKACAWIQREE2hPxiNi
jOo+CSmrLxbGte+J7wUCY7dJEwIbDAAKCRCrLxbGte+J7+0NAQCfj95TSyPEFKz3
eLJ1aCA1bZZV/rkhHd+OwX1MFL3mKQD9GMPgvMzDIoofycDzMY2ttJgkRJfq+zOZ
juXFQdUkMgY=
=pf3V
-----END PGP PUBLIC KEY BLOCK-----
EOF
fingerprint: 4413684FC623628CEA3E0929AB2F16C6B5EF89EF
F0477297E369CD1D189DD901278D1535AB473B9E

In both cases, you must upload the GPG key directory /tmp/newkey onto the server. If you uploaded it to /var/lib/sops than your sops configuration will look like this:

{
  # Make sure that `/var/lib/sops` is owned by root and is not world-readable/writable
  sops.gnupg.home = "/var/lib/sops";
  # disable importing host ssh keys
  sops.gnupg.sshKeyPaths = [];
}

However be aware that this will also run GnuPG on your server including the GnuPG daemon. GnuPG is in general not great software and might break in hilarious ways. If you experience problems, you are on your own. If you want a more stable and predictable solution go with SSH keys or one of the KMS services.

Share secrets between different users

Secrets can be shared between different users by creating different files pointing to the same sops key but with different permissions. In the following example the drone secret is exposed as /run/secrets/drone-server for drone-server and as /run/secrets/drone-agent for drone-agent:

{
  sops.secrets.drone-server = {
    owner = config.systemd.services.drone-server.serviceConfig.User;
    key = "drone";
  };
  sops.secrets.drone-agent = {
    owner = config.systemd.services.drone-agent.serviceConfig.User;
    key = "drone";
  };
}

Migrate from pass/krops

If you have used pass before (e.g. in krops) than you can use the following one-liner to convert all your secrets to a YAML structure:

$ for i in *.gpg; do echo "$(basename $i .gpg): |\n$(pass $(dirname $i)/$(basename $i .gpg)| sed 's/^/  /')"; done

Copy the output to the editor you have opened with sops.

Real-world examples

My personal configuration makes extensive usage of sops-nix. Each host has a secrets directory containing secrets for the host. Also Samuel Leathers explains his personal setup in this blog article.

Known limitations

Initrd secrets

sops-nix does not fully support initrd secrets. This is because nixos-rebuild switch installs the bootloader before running sops-nix's activation hook.
As a workaround, it is possible to run nixos-rebuild test before nixos-rebuild switch to provision initrd secrets before actually using them in the initrd. In the future, we hope to extend NixOS to allow keys to be provisioned in the bootloader install phase.

Using secrets at evaluation time

It is not possible to use secrets at evaluation time of nix code. This is because sops-nix decrypts secrets only in the activation phase of nixos i.e. in nixos-rebuild switch on the target machine. If you rely on this feature for some secrets, you should also include solutions that allow secrets to be stored securely in your version control, e.g. git-agecrypt. These types of solutions can be used together with sops-nix.

Related projects

  • agenix: Similar features as sops-nix but uses age.
  • scalpel: Provides a simple template mechanism to inject secrets into configuration files in the nixos activation phase

Need more commercial support?

We are building sops-nix very much as contributors to the community and are committed to keeping it open source.

That said, many of us that are contributing to sops-nix also work for consultancies. If you want to contact one of those for paid-for support setting up sops-nix in your infrastructure you can do so here:

More Repositories

1

cntr

A container debugging tool based on FUSE
Rust
542
star
2

nix-update

Swiss-knife for updating nix packages.
Python
459
star
3

nixos-shell

Spawns lightweight nixos vms in a shell
Nix
423
star
4

nix-ld

Run unpatched dynamic binaries on NixOS
C
422
star
5

nixpkgs-review

Review pull-requests on https://github.com/NixOS/nixpkgs
Python
383
star
6

python-mpd2

Python library which provides a client interface for the Music Player Daemon.
Python
353
star
7

dotfiles

My NixOS dotfiles
Nix
299
star
8

nix-fast-build

Combine the power of nix-eval-jobs with nix-output-monitor to speed-up your evaluation and building process.
Python
210
star
9

envfs

Fuse filesystem that returns symlinks to executables based on the PATH of the requesting process.
Rust
133
star
10

vmsh

Shell into a virtualized linux, with your own tools
Rust
132
star
11

mina-sidekiq

Tasks to deploy Sidekiq with mina.
Ruby
91
star
12

nix-build-uncached

A CI friendly wrapper around nix-build.
Go
72
star
13

zig.ko

Linux kernel module written in Zig
Makefile
70
star
14

nixos-aarch64-images

Build NixOS images for various ARM single computer boards
Python
49
star
15

x86_64-linux-cheatsheats

Plain files for syscalls, errnos, signals, registers and x86_64 instructions
Python
49
star
16

pry.py

pry.py - an interactive drop in shell for python, similar to binding.pry in ruby
Python
49
star
17

ssh-to-age

Convert SSH Ed25519 keys to age keys. This is useful for usage in sops-nix and sops
Go
47
star
18

hue-ble-ctl

Control your Phillips Hue light bulb over bluetooth
Python
33
star
19

pythonix

Eval nix code from python
C++
32
star
20

awesome-dotfiles

Configuration files of the window manager awesome
Lua
28
star
21

fast-flake-update

Update flake.lock with the latest commit of a local checkout
Python
27
star
22

nur-packages

My personal NUR repository
Nix
23
star
23

flake-linter

Find duplicate dependencies in flakes
Python
18
star
24

ansible-lxc

Ansible Connection Plugin for lxc containers (https://linuxcontainers.org/)
Python
17
star
25

iana-etc

Build /etc/protocols and /etc/services files from IANA's Assigned Internet Protocol Numbers
Python
17
star
26

lognotify

log watcher for awesome wm
Lua
14
star
27

nix-sysdig

Wrapper to debug sysdig builds
Python
13
star
28

github-tags

sinatra app to generate rss feeds with the latest git tags of a project on github
Ruby
12
star
29

nixican-standoff

Benchmark between nix, lix and tvix
Python
12
star
30

whois42d

Whois server for the dn42 registry
Go
11
star
31

ssh-to-pgp

Convert SSH RSA keys to GPG keys
Go
11
star
32

utils

A set of lua modules I use in awesome wm.
Lua
9
star
33

stockholm

Mirror of https://git.thalheim.io/Mic92/stockholm/
Nix
9
star
34

bing-gpt-server

HTML
8
star
35

flake-templates

Personal templates i like to use.
Nix
7
star
36

valauncher

A fast dmenu-like gtk3 application launcher
CMake
7
star
37

dlopen-resolver

Python
7
star
38

openvpn-ddns

Maintain dns records for connecting openvpn clients
Ruby
7
star
39

dream2nix-home-assistant

Packaging experiments with dream2nix to package home-assistant with all dependencies.
Nix
5
star
40

kvm-pirate

Attach to kvm-based VMs
Python
5
star
41

mechanical-keyboards

Configuration of my collections of keyboards
Nix
4
star
42

systemd-ta

http://c3d2.de/news/ta-systemd.html
JavaScript
4
star
43

nix-build-shell

Rust
4
star
44

robolab

Simulator for the course Robolab at TU Dresden
4
star
45

nixos-test-example

Nix
4
star
46

retiolum

Mirror of https://git.thalheim.io/Mic92/retiolum
Nix
4
star
47

server-bookings

Rust
3
star
48

Algebra-I

Das inoffizielle Skript zur Vorlesung bei Prof. Schmidt
Ruby
3
star
49

nix-fmt

abandoned in favor of https://github.com/orivej/go-nix and https://gitlab.com/jD91mZM2/rnix
OCaml
3
star
50

imap-notify

IMAP notifier using IMAP's NOTIFY SET
Python
3
star
51

docker-pid

Resolve container id/name to container's process id
Go
3
star
52

nftables

Mirror of netfilter/nftables
C
3
star
53

nixos-configuration

The content of this repo has been integrated into https://github.com/Mic92/dotfiles/
3
star
54

mpdtools

Usefull tools for MPD: mpdadd - Link and play files outside of the MPD directory to MPD. mpdmark - bookmark songs
Ruby
3
star
55

nsattach

attach to linux namespaces
C
3
star
56

bors-gen-config

Generate bors.toml for github repositories
Python
2
star
57

int3

Better debugger breakpoints
Python
2
star
58

vtune-nix

Vtune nix package
Nix
2
star
59

live-net-info

My adventures using the bubbletea framework.
Go
2
star
60

blog

Source of my blog
Shell
2
star
61

themenabend-nixos

Folien und Code zum Themenabend über Nix/Nixos
JavaScript
2
star
62

lxc-machined-start

Integrate lxc container into machined
M4
2
star
63

company-tmux

emacs auto complete with content of tmux panes
Emacs Lisp
2
star
64

pgp-verify

Verify pgp signatures of files.
Go
2
star
65

build-system-koans

C
2
star
66

SWT_And_Programming

Programs/Stubs created during exercises at university.
Java
2
star
67

mpdstated

Auto restore recent position for each podcast in mpd.
Vala
2
star
68

bme680-mqtt

Publish BME680 sensor data to home-assistant via MQTT
Python
2
star
69

container-pid

Rust crate to resolve a container names/ids to PID
Rust
2
star
70

nixcon2023-nixos-anywhere

Presentation slides for NixCon 2023 presentation on nixos-anywhere
JavaScript
2
star
71

systemd-user-units

2
star
72

nixos-wiki-redirector

JavaScript
2
star
73

disko-yubikey-demo

Nix
2
star
74

scripts

All my tiny scripts and stubs
Shell
1
star
75

lualdap

fork of https://git.zx2c4.com/lualdap/ with lua5.3 support
C
1
star
76

pcap-preload

Rust
1
star
77

nixpkgs-committers

Repository for nominating maintainers for Nixpkgs commit access
1
star
78

mic92.github.com

1
star
79

phd-website

HTML
1
star
80

ports

my freebsd ports
Makefile
1
star
81

drone-convert-nix

Go
1
star
82

webscraping-workshop

Folien und Code zum Webscraping workshop auf den Datenspuren 2015
JavaScript
1
star
83

qtile-config

Python
1
star
84

semeion

A DynDNS Server interface in Haskell and Yesod
Haskell
1
star
85

hadoop-exercise

MapReduce Assignment 2015 at System Engineering II (TU Dresden)
Java
1
star
86

xfstests-cntr

Fork of xfstests with support for cntr
Shell
1
star
87

fuidshift

Move Filesystem ownership into other subordinated uid ranges
Go
1
star
88

android-notifier

Automatically exported from code.google.com/p/android-notifier
Java
1
star
89

fft

FFT Implementation for Tensilica DSP Processors
C
1
star
90

userborn-with-impermanence

Nix
1
star
91

drone-nix-scheduler

Schedule nix jobsets in drone ci
Python
1
star
92

arch-package-feed

bottle.py based project to provide a more advanced arch package feed.
Python
1
star
93

PKGBUILDs

My PKGBUILDs of Packages I maintain in Archlinux AUR
Shell
1
star
94

tracedump

System service to dump Intel processor trace + memory after a crash.
Python
1
star
95

Japanese-sum-solver

A solver for the logic game Japsum
JavaScript
1
star
96

clusterssh

wrapper arround go-ssh to execute commands in a cluster
Go
1
star