Logo

dev-resources.site

for different kinds of informations.

Managing NixOS Secrets via SOPS, sops-nix and opsops

Published at
8/3/2024
Categories
nixos
security
sops
devops
Author
vst
Categories
4 categories in total
nixos
open
security
open
sops
open
devops
open
Author
3 person written this
vst
open
Managing NixOS Secrets via SOPS, sops-nix and opsops

Secret provisioning is a critical operation during the deployment and management of a software system. The way it is done can have significant impact on both security and operational efficiency.

In this post, I am going to discuss SOPS, sops-nix and opsops tools for managing secrets and how I and my team use it.

Imperative vs Declarative Secret Management

There are three approaches to managing secrets in a software system:

  1. Manual: Secrets are provided by the person deploying the system. This is the simplest approach, but it is also the least secure. It is difficult to audit who has access to the secrets and when they were accessed. It is also difficult to rotate the secrets.
  2. Imperative: Secrets are managed by the deployment scripts or the deployment tool. The secrets are stored in a secure location and are injected into the system at deployment time. This approach is simple and relatively easy to understand. However, it has similar drawbacks as in the manual approach.
  3. Declarative: Secrets are managed by a separate tool. The secrets are stored in a secure location and are accessed by the system when needed. This approach is more secure and easier to audit. It also makes it easier to rotate the secrets.

There are many tools available for managing secrets in a declarative way. Most of the Infrastructure as Code (IaC) tools like Terraform, Ansible, and CloudFormation have built-in support for managing secrets. However, these tools are not designed specifically for managing secrets, can be cumbersome to use or not applicable at all in certain environments.

Enter SOPS

SOPS is a simple, yet powerful tool for managing secrets. It consumes a clear secret file in various formats and encrypts values using keys. The encrypted file can be then stored in a version control system and decrypted when needed.

The recently launched SOPS Website is quite informative and provides a good overview of the tool.

But to demonstrate, let’s consider a simple example. Suppose we have a YAML file containing clear secrets secrets_clear.yaml:

# file: secrets_clear.yaml
service:
  api:
    key: "my-api-key"
    secret: "my-api-secret"
  db:
    username: "my-db-username"
    password: "my-db-password"
    database: "my-db-database"
smtp:
  host: "my-smtp-host"
  port: 587
  username: "my-smtp-username"
  password: "my-smtp-password"
Enter fullscreen mode Exit fullscreen mode

Once this file is encrypted using SOPS against some age keys generated from an SSH public key, secrets will look something like:

# file: secrets_encrypted.yaml
service:
  api:
    key: ENC[AES256_GCM,data:...]
    secret: ENC[AES256_GCM,data:...]
  db:
    username: ENC[AES256_GCM,data:...]
    password: ENC[AES256_GCM,data:...]
    database: ENC[AES256_GCM,data:...]
smtp:
  host: ENC[AES256_GCM,data:...]
  port: ENC[AES256_GCM,data:...]
  username: ENC[AES256_GCM,data:...]
  password: ENC[AES256_GCM,data:...]
Enter fullscreen mode Exit fullscreen mode

This encrypted file can be decrypted using the SSH private key corresponding to the age key used for encryption. For example: We can prepare an encrypted secrets file for 5 different servers, copy it to these servers, and decrypt it there. To repeat, only the servers that have the corresponding SSH private key can decrypt the secrets.

Nix Buddy: sops-nix

The data definition and operational model of SOPS is well suited for a Nix-powered system. sops-nix offers both NixOS and Nix Home Manager modules which provide a declarative way to manage secrets using SOPS.

The sops-nix module decrypts secrets at activation time and provides them as individual files under the secret store, usually /run/secrets. The file tree under the secret store is the same as the key tree in the encrypted file: Each encrypted value is the content of the file, its key is the file name, and its parent directories are the path to the key:

$ cat /run/secrets/service/api/key
my-api-key
Enter fullscreen mode Exit fullscreen mode

There are 2 more important aspects of sops-nix:

  1. It provides a way to manage the owner and permissions of the generated secret files.
  2. It provides a simple templating system to produce new secret files by interpolating secrets into templates.

For example, given the following Nix snippet:

{
  ## ...
  sops.templates."service.env" = {
    content = ''
      API_KEY=${config.sops.placeholder."service/api/key"}
      API_SECRET=${config.sops.placeholder."service/api/secret"}
    '';
  };
  ## ...
}
Enter fullscreen mode Exit fullscreen mode

… you will get a file /run/secrets-rendered/service.env with the following content:

$ cat /run/secrets-rendered/service.env
API_KEY=my-api-key
API_SECRET=my-api-secret
Enter fullscreen mode Exit fullscreen mode

Use it to set your Docker container environment variables file, for example.

Streamlining Secrets Generation with opsops

opsops is a CLI program that I wrote to generate the clear secrets file in a declarative way. It is agnostic to what you use it for: It can be used to generate secrets which will be deployed to NixOS or somewhere else.

My main motivations were the following:

  1. I am still not in love with the idea of storing secrets in a version control system even though they are encrypted.
  2. I do not want to edit a YAML file manually to add or update secrets.
  3. I want to know where the secrets are coming from and who else might have access to them.

So, for a quick spin, consider the following opsops configuration:

secrets:
  zamazingo:
    secret:
      type: "process"
      value:
        command: "zamazingo"
        arguments: ["--hip", "hop"]
  github:
    token:
      type: "script"
      value:
        content: 'printf "%s" "$(gh auth token)"'
  example.com:
    password:
      type: "script"
      value:
        interpreter: "python3"
        content: |
          import netrc
          import sys

          _login, _account, password = netrc.netrc().authenticators("example.com")

          sys.stdout.write("password")
  dockerhub:
    password:
      type: "op"
      value:
        account: "PAIT5BAHSH7DAPEING3EEDIE2E"
        vault: "Cloud Accounts"
        item: "yies1Ahl4ahqu1afao4nahshoo"
        field: "password"
  influxdb:
    token:
      type: "op-read"
      value:
        account: "IPAEPH0JI3REE8FICHOOVU4CHA"
        uri: "op://Devops/OokahCuZ4fo8ahphie1aiFa0ei/API Tokens/write-only"
Enter fullscreen mode Exit fullscreen mode

This file has similar structure to both desired clear secrets file and the encrypted file except that the leave values are just specifications for how to retrieve/generate the secret.

Currently, there are 4 types of sources for the secrets:

  1. process: The secret is generated by running a process.
  2. script: The secret is generated by running a script with a specific interpreter (defaults to sh).
  3. op: The secret is retrieved from 1Password using the op command.
  4. op-read: The secret is retrieved from 1Password using the op command and the URI of the secret.

I am planning to add new types of sources in the future. It is implemented in Haskell and contributions are much welcome.

The output of running opsops will be a clear secrets file that can be encrypted using SOPS:

zamazingo:
  secret: hebelehubele
github:
  token: gho_meecubier5dinohSh3tohphaekuo5Phahpei
example.com:
  password: password
dockerhub:
  password: ohbauy5eing8pheSh6iigooweeZee6ch
influxdb:
  token: mu9aephabeadi7zi8goo9peYo8yae7ge
Enter fullscreen mode Exit fullscreen mode

I am (almost) happily checking in the opsops configuration file to the version control system.

Real World Value

To summarize:

  1. Using opsops, one can refer to the configuration file to see where secrets are originally coming from and who has access to it. This is at least a starting point for auditing the secrets.
  2. Using sops-nix, the SOPS secrets become first class citizens in the NixOS configuration.

Therefore, using a multi-host NixOS deployment tool like colmena, we can manage multiple secrets files for different hosts and different environments in a familiar way. This makes auditing easier, and rotating secrets becomes a breeze.

I am managing secrets on all my personal computers, cloud and in-house servers using SOPS, sops-nix and opsops.

As a team, we are migrating our infrastructure entirely to NixOS, and we chose the same secret management approach for our development and production hosts in this process.

There are still some rough edges in the tools and the workflow. But so far, this approach did not fail me or my team.

nixos Article's
30 articles in total
Favicon
How to Set Up Kanata on NixOS: A Step-by-Step Guide
Favicon
NixOS - A Unique Linux Distribution
Favicon
NixOS Full Disk Encryption with USB/SD-Card/Password Unlock
Favicon
Choosing my distro: Ubuntu, NixOS, Elementary OS or Pop!_OS
Favicon
Nix first steps
Favicon
OpenResty on NixOS for an API Gateway
Favicon
Why Consider NixOS for Your Environment?
Favicon
Managing NixOS on DigitalOcean with Colmena
Favicon
I Changed My Mind - NixOS is NOT the Best Linux
Favicon
Backup GitHub Repositories with gidek
Favicon
Packing Custom Fonts for NixOS
Favicon
Managing NixOS Secrets via SOPS, sops-nix and opsops
Favicon
Running NixOS Guests on QEMU
Favicon
Archiving PostgreSQL Backups on NixOS
Favicon
The Ultimate NixOS Homelab Guide - Flakes, Modules and Fail2Ban w/ Cloudflare
Favicon
The radical concept of NixOS and why I love it!
Favicon
The Ultimate NixOS Homelab Guide - The Install
Favicon
The One Man Stack
Favicon
I use NixOS for my home-server, and you should too!
Favicon
This distro finally fixes Linux
Favicon
Running a Terraria Dedicated Server on NixOS
Favicon
NixOS on Raspberry Pi 4 with Encrypted Filesystem
Favicon
Day 7: Uninstalling NixOS from my Macbook Pro
Favicon
Nixpkgs: A community-maintained package lock file for all of Unix
Favicon
Writing a (Nix-Friendly) Hello World Web App in Haskell with Scotty
Favicon
Kick the Mix-ups to the Curb: Nix the Tricks and Juggle Go Projects like a Pro
Favicon
Day 6: Making rEFInd Look Good
Favicon
Day 5: NixOS & Git - version control for config.nix
Favicon
Day 4: (Re)Installing NixOS on my Macbook Pro
Favicon
Day 3: Fighting with network interfaces

Featured ones: