Skip to content

timewave-computer/sopsidy

Repository files navigation

Sopsidy

Sopsidy is an extension for sops-nix that automates the creation of sops files based on configured collection scripts per secret. These scripts typically collect secrets from password managers like Bitwarden or 1Password.

Overview

Sopsidy provides two main exports: a NixOS module and a buildSecretsCollector function to build a single secrets collector for a collection of hosts.

The NixOS module adds a new section of options: sops.secrets.*.collect. Docs for these options can be found in docs/flake-module.md. The main option is sops.secrets.*.collect.script that must be set for every secret defining how to find the secret. There are additional plugins (only the rbw one so far) that can add more options and set the script for you. For example, the rbw plugin adds a collect.id option and will set the script to get the password of the entry with the id in your bitwarden vault, provided rbw has been setup correctly.

Then the buildSecretsCollector function will use all the collect.script settings for all secrets across all hosts and create the secrets collector script. Running this script in the repository will run all the collect scripts and fill their outputs into a json blob which is passed to sops along with the related host's age pubkeys to encrypt all the repository's sops files.

The NixOS module also adds the global option sops.hostPubKey which allows the secrets collector to manage the age keys for each sops file. This removes the need for the sops config file, .sops.yaml, and it should actually be removed because it interferes with the secret collector script.

Usage

Sopsidy is designed to completely take over the sops files in the repository, so if sops-nix is already being used be sure to have backups of all existing sops files before setting up sopsidy. Also as mentioned before, move .sops.yaml out of the repository as it interferes with sopsidy's native management of age keys for each sops file - based on the sops.hostPubKey option provided by the sopsidy nixos module.

First import both the sops-nix and sopsdiy NixOS modules in the NixOS system. Then use the sopsidy tool buildSecretsCollector to build the collect-secrets script for all the hosts. The script should then be exported as a flake output or included in a devshell. A flake parts module is available to set up the script and make it available to be exported or added to a devshell. Documentation for the flake-parts module options can be found at docs/flake-module.md

Below are examples for setting up sopsidy with and without flake-parts. Usage without flakes is not currently supported.

With flake-parts (recommended method)
{
  inputs = {
    flake-parts.url = "github:hercules-ci/flake-parts";
    sops-nix.url = "github:Mic92/sops-nix";
    sopsidy.url = "github:timewave-computer/sopsidy";
    sopsidy.inputs.nixpkgs.follows = "nixpkgs";
  };
  outputs = inputs@{self, flake-parts, ... }:
    flake-parts.lib.mkFlake { inherit inputs; } {
      imports = [
        inputs.sopsidy.flakeModule
      ];
      perSystem = { config, ... }: {
        sopsidy.hosts = self.nixosConfigurations;
        packages.collect-secrets = config.sopsidy.package;
      };
      flake.nixosConfigurations = {
        nixos = nixpkgs.lib.nixosSystem {
          modules = [
            inputs.sops-nix.nixosModules.default
            inputs.sopsidy.nixosModules.default
            ./configuration.nix
          ];
        };
      };
    };
}
Without flake-parts
{
  inputs = {
    sops-nix.url = "github:Mic92/sops-nix";
    sopsidy.url = "github:timewave-computer/sopsidy";
    sopsidy.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs = inputs@{self, nixpkgs, ...}:
    let pkgs = import nixpkgs { system = "x86_64-linux"; }; in
    {
      nixosConfigurations = {
        nixos = nixpkgs.lib.nixosSystem {
          modules = [
            inputs.sops-nix.nixosModules.default
            inputs.sopsidy.nixosModules.default
            ./configuration.nix
          ];
        };
      };
      packages.x86_64-linux.collect-secrets = sopsidy.lib.buildSecretsCollector {
        inherit pkgs;
        hosts = self.nixosConfigurations;
      };
    };
}

Once the NixOS module as been imported and the collect-secrets package has been setup, set the sops.hostPubKey with the host's age pubkey so that the secret collector (collect-secrets script) will know what age key to pass to sops. For remote systems this can be found with:

nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 <server-domain> | tail -n 1 | ssh-to-age'

If you are already on the system, it can be found with:

nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'

Then in the host's configuration.nix set the required sops-nix and sopsidy settings.

{
  sops.hostPubKey = "<age pubkey>";
  sops.defaultSopsFile = ./secrets.yaml;
  sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
}

Now secrets can be added for any services. All thats needed is to define the secret in a NixOS module just like with sops-nix except with an additional collect section.

{
  sops.secrets."service/admin-password" = {
    collect.script = ''
      rbw get <id>
    '';
  };
}

Using the rbw plugin (which is included by default), this is equivalent to:

{
  sops.secrets."service/admin-password" = {
    collect = {
      rbw.id = "<id>";
    };
  };
}

To then create the sops file secrets.yaml run:

nix run .#collect-secrets

Confirm that secrets.yaml has a service.admin-password entry. Then the host can be deployed with the secret.

Security

Most of the work is done within sops and sops-nix. The secret collector script passes all secrets through pipes, so sensitive data is never exposed into environment variables or process IDs. It is of course possible for the collect scripts themselves to leak sensitive data. Avoid using command subsitution ($(command outputting secret)) within collect scripts because that data can be found by watching process ids.

The script will encrypt each sops file with only the age keys of hosts that have secrets defined for that file. If there are secrets that shouldn't be shared between hosts, make sure to use the sops.secrets.*.sopsFile from sops-nix to use different sops files for groups of secrets.

Credits

Sopsidy is just a wrapper around sops-nix and sops, so most of the credit goes to those two projects for doing the heavy lifting.

The inspiration for this project comes from opsops. Opsops requires creating a separate yaml file describing how secrets are collected. Sopsidy lets you instead put the collection scipts alongside the NixOS sops definitions for them.

agenix-rekey was also helpful for both inspiration and an example of how to inject new options into attrsOf submodule types.

About

Script based generation of sops files for sops-nix

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages