automatically import secrets into nixos

This commit is contained in:
Jörg Thalheim 2023-08-23 11:58:12 +02:00
parent ed1d28f20b
commit 63bb9395fd
17 changed files with 196 additions and 7 deletions

View File

@ -1,5 +1,21 @@
{
imports = [
./schema.nix
];
{ self, ... }: {
perSystem = { pkgs, ... }: {
checks =
let
nixosTestArgs = {
# reference to nixpkgs for the current system
inherit pkgs;
# this gives us a reference to our flake but also all flake inputs
inherit self;
};
nixosTests = {
# import our test
secrets = import ./secrets nixosTestArgs;
};
schemaTests = pkgs.callPackages ./schemas.nix {
inherit self;
};
in
nixosTests // schemaTests;
};
}

18
checks/lib/test-base.nix Normal file
View File

@ -0,0 +1,18 @@
test:
{ pkgs
, self
, ...
}:
let
inherit (pkgs) lib;
nixos-lib = import (pkgs.path + "/nixos/lib") { };
in
(nixos-lib.runTest {
hostPkgs = pkgs;
# speed-up evaluation
defaults.documentation.enable = lib.mkDefault false;
# to accept external dependencies such as disko
node.specialArgs.self = self;
imports = [ test ];
}).config.result

34
checks/schemas.nix Normal file
View File

@ -0,0 +1,34 @@
{ self, runCommand, check-jsonschema, pkgs, lib, ... }:
let
clanModules = self.clanModules;
baseModule = {
imports =
(import (pkgs.path + "/nixos/modules/module-list.nix"))
++ [{
nixpkgs.hostPlatform = "x86_64-linux";
}];
};
optionsFromModule = module:
let
evaled = lib.evalModules {
modules = [ module baseModule ];
};
in
evaled.options.clan.networking;
clanModuleSchemas = lib.mapAttrs (_: module: self.lib.jsonschema.parseOptions (optionsFromModule module)) clanModules;
mkTest = name: schema: runCommand "schema-${name}" { } ''
${check-jsonschema}/bin/check-jsonschema \
--check-metaschema ${builtins.toFile "schema-${name}" (builtins.toJSON schema)}
touch $out
'';
in
lib.mapAttrs'
(name: schema: {
name = "schema-${name}";
value = mkTest name schema;
})
clanModuleSchemas

View File

6
checks/secrets/clan-secrets Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -eux -o pipefail
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
export SOPS_AGE_KEY_FILE="${SCRIPT_DIR}/key.age"
nix run .# -- secrets "$@"

View File

@ -0,0 +1,16 @@
(import ../lib/test-base.nix) {
name = "secrets";
nodes.machine = { self, config, ... }: {
imports = [
self.nixosModules.secrets
];
environment.etc."secret".source = config.sops.secrets.foo.path;
sops.age.keyFile = ./key.age;
clan.sops.sopsDirectory = ./sops;
networking.hostName = "machine";
};
testScript = ''
machine.succeed("cat /etc/secret >&2")
'';
}

1
checks/secrets/key.age Normal file
View File

@ -0,0 +1 @@
AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX

View File

@ -0,0 +1,4 @@
{
"publickey": "age15x8u838dwqflr3t6csf4tlghxm4tx77y379ncqxav7y2n8qp7yzqgrwt00",
"type": "age"
}

View File

@ -0,0 +1 @@
../../../machines/machine

View File

@ -0,0 +1,20 @@
{
"data": "ENC[AES256_GCM,data:bhxF,iv:iNs+IfSU/7EwssZ0GVTF2raxJkVlddfQEPGIBeUYAy8=,tag:JMOKTMW3/ic3UTj9eT9YFQ==,type:str]",
"sops": {
"kms": null,
"gcp_kms": null,
"azure_kv": null,
"hc_vault": null,
"age": [
{
"recipient": "age15x8u838dwqflr3t6csf4tlghxm4tx77y379ncqxav7y2n8qp7yzqgrwt00",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxS0g4TEt4S09LQnFKdCtk\nZTlUQWhNUHZmcmZqdGtuZkhhTkMzZDVaWWdNCi9vNnZQeklNaFBBU2x0ditlUDR0\nNGJlRmFFb09WSUFGdEh5TGViTWtacFEKLS0tIE1OMWdQMHhGeFBwSlVEamtHUkcy\ndzI1VHRkZ1o4SStpekVNZmpQSnRkeUkKYmPS9sR6U0NHxd55DjRk29LNFINysOl6\nEM2MTrntLxOHFWZ1QgNx34l4rYIIXx97ONvR0SRpxN0ECL9VonQeZg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2023-08-23T09:11:08Z",
"mac": "ENC[AES256_GCM,data:8z819mP4FJXE/ExWM1+/dhaXIXzCglhBuZwE6ikl/jNLUAnv3jYL9c9vPrPFl2by3wXSNzqB4AOiTKDQoxDx2SBQKxeWaUnOajD6hbzskoLqCCBfVx7qOHrk/BULcBvMSxBca4RnzXXoMFTwKs2A1fXqAPvSQd1X4gX6Xm9VXWM=,iv:3YxZX+gaEcRKDN0Kuf9y1oWL+sT/J5B/5CtCf4iur9Y=,tag:0dwyjpvjCqbm9vIrz6WSWQ==,type:str]",
"pgp": null,
"unencrypted_suffix": "_unencrypted",
"version": "3.7.3"
}
}

View File

@ -0,0 +1 @@
../../../users/admin

View File

@ -0,0 +1,4 @@
{
"publickey": "age15x8u838dwqflr3t6csf4tlghxm4tx77y379ncqxav7y2n8qp7yzqgrwt00",
"type": "age"
}

View File

@ -119,9 +119,31 @@
"floco": "floco",
"nixos-generators": "nixos-generators",
"nixpkgs": "nixpkgs",
"sops-nix": "sops-nix",
"treefmt-nix": "treefmt-nix"
}
},
"sops-nix": {
"inputs": {
"nixpkgs": [
"sops-nix"
],
"nixpkgs-stable": []
},
"locked": {
"lastModified": 1692500916,
"narHash": "sha256-iKADqEOHmyi+LCJ5LzWcM2zH0DP3WHFETjX98blH0tE=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "4f0f113b7dbcb92edb9c901515fcab0b91c6def7",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [

View File

@ -7,6 +7,9 @@
floco.inputs.nixpkgs.follows = "nixpkgs";
disko.url = "github:nix-community/disko";
disko.inputs.nixpkgs.follows = "nixpkgs";
sops-nix.url = "github:Mic92/sops-nix";
sops-nix.inputs.nixpkgs.follows = "sops-nix";
sops-nix.inputs.nixpkgs-stable.follows = "";
nixos-generators.url = "github:nix-community/nixos-generators";
nixos-generators.inputs.nixpkgs.follows = "nixpkgs";
flake-parts.url = "github:hercules-ci/flake-parts";

View File

@ -1,6 +1,10 @@
{ ... }: {
{ inputs, ... }: {
flake.nixosModules = {
hidden-ssh-announce.imports = [ ./hidden-ssh-announce.nix ];
installer.imports = [ ./installer.nix ];
installer.imports = [ ./installer ];
secrets.imports = [
inputs.sops-nix.nixosModules.sops
./secrets
];
};
}

View File

@ -0,0 +1,39 @@
{ lib, config, ... }:
let
encryptedForThisMachine = name: type:
let
symlink = config.clan.sops.sopsDirectory + "/secrets/${name}/machines/${config.clan.sops.machineName}";
in
# WTF, nix bug, my symlink is in the nixos module detected as a directory also it works in the repl
type == "directory" && (builtins.readFileType symlink == "directory" || builtins.readFileType symlink == "symlink");
secrets = lib.filterAttrs encryptedForThisMachine (builtins.readDir (config.clan.sops.sopsDirectory + "/secrets"));
in
{
imports = [
];
options = {
clan.sops = {
machineName = lib.mkOption {
type = lib.types.str;
default = config.networking.hostName;
description = ''
Machine used to lookup secrets in the sops directory.
'';
};
sopsDirectory = lib.mkOption {
type = lib.types.path;
description = ''
Sops toplevel directory that stores users, machines, groups and secrets.
'';
};
};
};
config = {
sops.secrets = builtins.mapAttrs
(name: _: {
sopsFile = config.clan.sops.sopsDirectory + "/secrets/${name}/secret";
format = "binary";
})
secrets;
};
}

View File

@ -43,7 +43,7 @@ def get_user_name(user: str) -> str:
"""Ask the user for their name until a unique one is provided."""
while True:
name = input(
f"Enter your user name for which your sops key will be stored in the repository [default: {user}]: "
f"Your key is not yet added to the repository. Enter your user name for which your sops key will be stored in the repository [default: {user}]: "
)
if name:
user = name