Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add agenix support #579

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ By default, [`configuration.nix`](configuration.nix) enables `bitcoind` and `cli
nix-bitcoin configuration to it using [krops](https://github.com/krebs/krops).\
Requires: [Nix](https://nixos.org/nix/), Linux

- [`./flakes-agenix/deploy.sh`](./flakes-agenix/deploy.sh) shows how to deploy a
nix-bitcoin node flake using [agenix](https://github.com/ryantm/agenix) secrets encryption.\
agenix allows repo-defined secrets that can be deployed with any deployment scheme.\
The node is deployed in a container.\
Requires: [Nix](https://nixos.org/), a systemd-based Linux distro and root privileges

- [`./deploy-container-minimal.sh`](deploy-container-minimal.sh) creates a
container defined by [importable-configuration.nix](importable-configuration.nix).\
You can copy and import this file to use nix-bitcoin in an existing NixOS configuration.\
Expand All @@ -63,3 +69,5 @@ The commands in `shell.nix` allow you to locally run the node in a VM or contain

Flakes make it easy to include `nix-bitcoin` in an existing NixOS config.
The [flakes example](./flakes/flake.nix) shows how to use `nix-bitcoin` as an input to a system flake.

To use [agenix](https://github.com/ryantm/agenix), which allows committing secrets to the node repo, see [`./flakes-agenix`](./flakes-agenix).
96 changes: 96 additions & 0 deletions examples/flakes-agenix/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
set -euo pipefail

# Strategy:
# 1. Copy the node flake (./flake.nix) to a temporary dir and create a Git repo.
# 2. Generate age-encrypted secrets by running a package defined by the node flake.
# Commit the secrets.
# 3. Start the node in a container and run test commands.
# The container is destroyed afterwards.
#
# Run this script with arg `-i` or `--interactive` to start an
# interactive shell in the container.

# You can use ./flake.nix as a template for a real deployment.

#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# 0. Prelude
interactive=
case "${1:-}" in
-i|--interactive)
interactive=1
;;
esac

cd "${BASH_SOURCE[0]%/*}"
scriptDir=$PWD
nixBitcoin="$scriptDir/../.."

tmpDir=$(mktemp -d /tmp/nix-bitcoin-agenix.XXX)
trap 'rm -rf $tmpDir' EXIT

#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# 1. Create a flake repo in a tmp dir
rsync -a ./ --exclude deploy.sh ./ "$tmpDir"

cd "$tmpDir"

git init
git add .
git commit -a -m init

#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# 2. Generate age-encrypted secrets

# Use this nix-bitcoin repo as the flake input when generating secrets,
# so that this script can be used for automated testing.
nix run --override-input nix-bitcoin "$nixBitcoin" .#generateAgeSecrets
#
# In a real deployment, you can simply run the following from the deployment repo root:
# nix run .#generateAgeSecrets
#
# or, if you don't define the `generateAgeSecrets` helper package:
# nix run .#nixosConfigurations.demo-node.config.nix-bitcoin.age.generateSecretsScript
#
# Show help
# nix run .#generateAgeSecrets --help

echo
echo "Encrypted secrets:"
ls -al secrets
echo

# Commit age-encrypted secrets
git add ./secrets
git commit -a -m 'add secrets'

# Success!
# This node flake can now be deployed with any deployment method.

#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# 3. Run node in container

if [[ $interactive ]]; then
# Start interactive container shell
runCmd=()
else
runCmd=(
--run c bash -c '
echo
echo "Unencrypted secrets in /run/agenix:"
ls -alH /run/agenix
echo
systemctl status bitcoind
'
)
fi

runContainer=(
nix run --override-input nix-bitcoin "$nixBitcoin" .#container -- "${runCmd[@]}"
)
nix shell --inputs-from "$nixBitcoin" extra-container -c "${runContainer[@]}"

#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# Debug helper: Build flake outputs
# nix build --no-link -L --print-out-paths --override-input nix-bitcoin "$nixBitcoin" .#container
# nix build --no-link -L --print-out-paths --override-input nix-bitcoin "$nixBitcoin" .#generateAgeSecrets
32 changes: 32 additions & 0 deletions examples/flakes-agenix/flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 108 additions & 0 deletions examples/flakes-agenix/flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# When using this file as the base for a real deployment,
# make sure to check all lines marked by 'FIXME:'

# This file is used by ./deploy.sh to deploy a container with
# age-encrypted secrets.

{
inputs.nix-bitcoin.url = "github:fort-nix/nix-bitcoin/release";
inputs.agenix.url = "github:ryantm/agenix";
inputs.agenix.inputs.nixpkgs.follows = "nix-bitcoin/nixpkgs";

inputs.flake-utils.follows = "nix-bitcoin/flake-utils";

outputs = { self, nix-bitcoin, agenix, flake-utils }: {
modules = {
demoNode = { config, lib, ... }: {
imports = [
# TODO-EXTERNAL:
# Set this to `agenix.nixosModules.default` when
# https://github.com/ryantm/agenix/pull/126 is merged
agenix.nixosModules.age
nix-bitcoin.nixosModules.default
(nix-bitcoin + "/modules/secrets/age.nix")
];

# Use age-encrypted secrets
nix-bitcoin.age = {
enable = true;

# The local secrets dir and its contents can be created with the
# `generateAgeSecrets` flake package (defined below).
# Use it like so:
# nix run .#generateAgeSecrets
# and commit the newly created ./secrets dir afterwards.
#
# This script must be rerun when adding node services that
# require new secrets.
#
# For a real-life example, see ./deploy.sh
secretsSourceDir = ./secrets;

# FIXME:
# Set this to a public SSH host key of your node (preferably key type `ed25519`).
# You can query host keys with command `ssh-keyscan <node address>`.
# The keys defined here are used to age-encrypt the secrets.
publicKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDoAaEMk8jMbg5MnvKDApWC6EpUHRJTzavy/wU2EtgtU"
];
};

# Enable services.
# See ../configuration.nix for all available features.
services.bitcoind.enable = true;
#
# See ../flakes/flake.nix for more settings useful for production nodes.


# WARNING:
# FIXME:
# Remove the following `age.identityPaths` setting in a real deployment.
# This copies a private key to the (publicly readable) Nix store,
# which allows ./deploy.sh to start a age-based container in
# a single deployment step.
#
# In a real deployment, just leave `age.identityPaths` undefined.
# In this case, agenix uses the auto-generated SSH host key.
age.identityPaths = [ ./host-key ];
};
};

nixosConfigurations.demoNode = nix-bitcoin.inputs.nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ self.modules.demoNode ];
};
}
// (nix-bitcoin.inputs.nixpkgs.lib.recursiveUpdate

# Allow runnning this node as a container, used by ./deploy.sh
(flake-utils.lib.eachSystem nix-bitcoin.lib.supportedSystems (system: {
packages = {
container = nix-bitcoin.inputs.extra-container.lib.buildContainers {
inherit system;
config.containers.nb-agenix = {
privateNetwork = true;
config.imports = [ self.modules.demoNode ];
};
# Set this when running on a NixOS container host with `system.stateVersion` <22.05
# legacyInstallDirs = true;
};
};
}))

# This allows generating age-encrypted secrets on systems
# that differ from the target node.
# E.g. manage a `x86_64-linux` node from macOS (`aarch64-darwin`)
(flake-utils.lib.eachDefaultSystem (system: {
packages = {
generateAgeSecrets = let
nodeSystem = nix-bitcoin.inputs.nixpkgs.lib.nixosSystem {
inherit system;
modules = [ self.modules.demoNode ];
};
in
nodeSystem.config.nix-bitcoin.age.generateSecretsScript;
};
}))
);
}
7 changes: 7 additions & 0 deletions examples/flakes-agenix/host-key
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACA6AGhDJPIzG4OTJ7ygwKVguhKVB0SU82r8v8FNhLYLVAAAAIhEbEOERGxD
hAAAAAtzc2gtZWQyNTUxOQAAACA6AGhDJPIzG4OTJ7ygwKVguhKVB0SU82r8v8FNhLYLVA
AAAECBS5tCD9AcaOcNzPYlreA4BVrsy2f0FaGgEoJfBQzMqzoAaEMk8jMbg5MnvKDApWC6
EpUHRJTzavy/wU2EtgtUAAAABG5vbmUB
-----END OPENSSH PRIVATE KEY-----
2 changes: 1 addition & 1 deletion helper/makeShell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pkgs.stdenv.mkDerivation {
config="${cfgDir}/configuration.nix"
fi
genSecrets=$(nix-build --no-out-link -I nixos-config="$config" \
'<nixpkgs/nixos>' -A config.nix-bitcoin.generateSecretsScript)
'<nixpkgs/nixos>' -A config.nix-bitcoin.generateSecretsScriptImpl)
mkdir -p "${cfgDir}/secrets"
(cd "${cfgDir}/secrets"; $genSecrets)
)}
Expand Down
93 changes: 93 additions & 0 deletions modules/secrets/age.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{ config, pkgs, lib, ... }:

with lib;
let
options.nix-bitcoin.age = {
enable = mkOption {
type = types.bool;
default = false;
description = mdDoc ''
Enable age-encrypted secrets.

This requires that the [agenix](https://github.com/ryantm/agenix)
module is included in your config.
'';
};

secretsSourceDir = mkOption {
type = types.path;
example = literalExpression "./secrets";
description = mdDoc ''
The directory where age-encrypted secrets are stored.
'';
};

publicKeys = mkOption {
type = with types; listOf str;
example = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH" ];
description = mdDoc ''
Public keys for encrypting the secrets.

A sensible default is to set this to the ed25519 public SSH host key of your node.
You can query host keys with command {command}`ssh-keyscan <node address>`.

When not using a SSH host key, make sure to set the corresponding agenix
option {option}`age.identityPaths`.
'';
};

generateSecretsScript = mkOption {
readOnly = true;

description = mdDoc secretsScriptLib.scriptHelp;

default = pkgs.writers.writeBashBin "generate-secrets" ''
${secretsScriptLib.gotoDestDir}

tmpSecretsDir=$(mktemp -d)
trap 'rm -rf $tmpSecretsDir' EXIT

# 1. Generate secrets to a tmp dir
pushd "$tmpSecretsDir" >/dev/null
${config.nix-bitcoin.generateSecretsScriptImpl}
popd >/dev/null

# 2. Age-encrypt each secret to $PWD/$name.age
encrypt() {
${getExe pkgs.rage} "$tmpSecretsDir/$1" -o "$1.age" ${
concatMapStringsSep " " (pubkey:
"--recipient ${escapeShellArg pubkey}"
) cfg.publicKeys
}
}
${
concatMapStrings (name: ''
encrypt "${name}"
'') (builtins.attrNames config.nix-bitcoin.secrets)
}
'';
};
};

cfg = config.nix-bitcoin.age;
inherit (config.nix-bitcoin) secretsScriptLib;
in {
inherit options;

config = {
nix-bitcoin.secretsSetupMethod = "age";

nix-bitcoin.secretsDir = config.age.secretsDir;

# The `nix-bitcoin-secrets` target has no dependencies,
# because agenix runs via the activation script, before systemd.
systemd.targets.nix-bitcoin-secrets = {};

age.secrets = mapAttrs (name: value: {
owner = value.user;
group = value.group;
mode = value.permissions;
file = (cfg.secretsSourceDir + "/${name}.age");
}) config.nix-bitcoin.secrets;
};
}
Loading