Merge pull request #141 from n8henrie/nix-darwin-support
feature: try to add nix-darwin support
This commit is contained in:
commit
b7ffcfe77f
6 changed files with 182 additions and 50 deletions
5
.github/workflows/ci.yaml
vendored
5
.github/workflows/ci.yaml
vendored
|
@ -23,3 +23,8 @@ jobs:
|
|||
- run: nix build
|
||||
- run: nix fmt . -- --check
|
||||
- run: nix flake check
|
||||
- run: |
|
||||
system=$(nix build --no-link --print-out-paths .#checks.x86_64-darwin.integration)
|
||||
${system}/activate-user
|
||||
sudo ${system}/activate
|
||||
- run: sudo /run/current-system/sw/bin/agenix-integration
|
||||
|
|
22
flake.lock
22
flake.lock
|
@ -1,5 +1,26 @@
|
|||
{
|
||||
"nodes": {
|
||||
"darwin": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1673295039,
|
||||
"narHash": "sha256-AsdYgE8/GPwcelGgrntlijMg4t3hLFJFCRF3tL5WVjA=",
|
||||
"owner": "lnl7",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "87b9d090ad39b25b2400029c64825fc2a8868943",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "lnl7",
|
||||
"ref": "master",
|
||||
"repo": "nix-darwin",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1674641431,
|
||||
|
@ -18,6 +39,7 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"darwin": "darwin",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
|
|
26
flake.nix
26
flake.nix
|
@ -1,17 +1,27 @@
|
|||
{
|
||||
description = "Secret management with age";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
darwin = {
|
||||
url = "github:lnl7/nix-darwin/master";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
darwin,
|
||||
}: let
|
||||
agenix = system: nixpkgs.legacyPackages.${system}.callPackage ./pkgs/agenix.nix {};
|
||||
in {
|
||||
nixosModules.age = import ./modules/age.nix;
|
||||
nixosModules.default = self.nixosModules.age;
|
||||
|
||||
darwinModules.age = import ./modules/age.nix;
|
||||
darwinModules.default = self.darwinModules.age;
|
||||
|
||||
overlays.default = import ./overlay.nix;
|
||||
|
||||
formatter.x86_64-darwin = nixpkgs.legacyPackages.x86_64-darwin.alejandra;
|
||||
|
@ -38,5 +48,19 @@
|
|||
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||
system = "x86_64-linux";
|
||||
};
|
||||
checks."aarch64-darwin".integration =
|
||||
(darwin.lib.darwinSystem {
|
||||
system = "aarch64-darwin";
|
||||
modules = [./test/integration_darwin.nix "${darwin.outPath}/pkgs/darwin-installer/installer.nix"];
|
||||
})
|
||||
.system;
|
||||
checks."x86_64-darwin".integration =
|
||||
(darwin.lib.darwinSystem {
|
||||
system = "x86_64-darwin";
|
||||
modules = [./test/integration_darwin.nix "${darwin.outPath}/pkgs/darwin-installer/installer.nix"];
|
||||
})
|
||||
.system;
|
||||
|
||||
darwinConfigurations.integration.system = self.checks."x86_64-darwin".integration;
|
||||
};
|
||||
}
|
||||
|
|
145
modules/age.nix
145
modules/age.nix
|
@ -8,6 +8,8 @@
|
|||
with lib; let
|
||||
cfg = config.age;
|
||||
|
||||
isDarwin = builtins.hasAttr "darwinConfig" options.environment;
|
||||
|
||||
# we need at least rage 0.5.0 to support ssh keys
|
||||
rage =
|
||||
if lib.versionOlder pkgs.rage.version "0.5.0"
|
||||
|
@ -17,17 +19,41 @@ with lib; let
|
|||
|
||||
users = config.users.users;
|
||||
|
||||
mountCommand =
|
||||
if isDarwin
|
||||
then ''
|
||||
if ! diskutil info "${cfg.secretsMountPoint}" &> /dev/null; then
|
||||
num_sectors=1048576
|
||||
dev=$(hdiutil attach -nomount ram://"$num_sectors" | sed 's/[[:space:]]*$//')
|
||||
newfs_hfs -v agenix "$dev"
|
||||
mount -t hfs -o nobrowse,nodev,nosuid,-m=0751 "$dev" "${cfg.secretsMountPoint}"
|
||||
fi
|
||||
''
|
||||
else ''
|
||||
grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts ||
|
||||
mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751
|
||||
'';
|
||||
newGeneration = ''
|
||||
_agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)"
|
||||
(( ++_agenix_generation ))
|
||||
echo "[agenix] creating new generation in ${cfg.secretsMountPoint}/$_agenix_generation"
|
||||
mkdir -p "${cfg.secretsMountPoint}"
|
||||
chmod 0751 "${cfg.secretsMountPoint}"
|
||||
grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts || mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751
|
||||
${mountCommand}
|
||||
mkdir -p "${cfg.secretsMountPoint}/$_agenix_generation"
|
||||
chmod 0751 "${cfg.secretsMountPoint}/$_agenix_generation"
|
||||
'';
|
||||
|
||||
chownGroup =
|
||||
if isDarwin
|
||||
then "admin"
|
||||
else "keys";
|
||||
# chown the secrets mountpoint and the current generation to the keys group
|
||||
# instead of leaving it root:root.
|
||||
chownMountPoint = ''
|
||||
chown :${chownGroup} "${cfg.secretsMountPoint}" "${cfg.secretsMountPoint}/$_agenix_generation"
|
||||
'';
|
||||
|
||||
identities = builtins.concatStringsSep " " (map (path: "-i ${path}") cfg.identityPaths);
|
||||
|
||||
setTruePath = secretType: ''
|
||||
|
@ -52,7 +78,7 @@ with lib; let
|
|||
umask u=r,g=,o=
|
||||
test -f "${secretType.file}" || echo '[agenix] WARNING: encrypted file ${secretType.file} does not exist!'
|
||||
test -d "$(dirname "$TMP_FILE")" || echo "[agenix] WARNING: $(dirname "$TMP_FILE") does not exist!"
|
||||
LANG=${config.i18n.defaultLocale} ${ageBin} --decrypt ${identities} -o "$TMP_FILE" "${secretType.file}"
|
||||
LANG=${config.i18n.defaultLocale or "C"} ${ageBin} --decrypt ${identities} -o "$TMP_FILE" "${secretType.file}"
|
||||
)
|
||||
chmod ${secretType.mode} "$TMP_FILE"
|
||||
mv -f "$TMP_FILE" "$_truePath"
|
||||
|
@ -63,7 +89,8 @@ with lib; let
|
|||
'';
|
||||
|
||||
testIdentities =
|
||||
map (path: ''
|
||||
map
|
||||
(path: ''
|
||||
test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!'
|
||||
'')
|
||||
cfg.identityPaths;
|
||||
|
@ -92,12 +119,6 @@ with lib; let
|
|||
chown ${secretType.owner}:${secretType.group} "$_truePath"
|
||||
'';
|
||||
|
||||
# chown the secrets mountpoint and the current generation to the keys group
|
||||
# instead of leaving it root:root.
|
||||
chownMountPoint = ''
|
||||
chown :keys "${cfg.secretsMountPoint}" "${cfg.secretsMountPoint}/$_agenix_generation"
|
||||
'';
|
||||
|
||||
chownSecrets = builtins.concatStringsSep "\n" (
|
||||
["echo '[agenix] chowning...'"]
|
||||
++ [chownMountPoint]
|
||||
|
@ -194,8 +215,13 @@ in {
|
|||
identityPaths = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default =
|
||||
if config.services.openssh.enable
|
||||
if (config.services.openssh.enable or false)
|
||||
then map (e: e.path) (lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys)
|
||||
else if isDarwin
|
||||
then [
|
||||
"/etc/ssh/ssh_host_ed25519_key"
|
||||
"/etc/ssh/ssh_host_rsa_key"
|
||||
]
|
||||
else [];
|
||||
description = ''
|
||||
Path to SSH keys to be used as identities in age decryption.
|
||||
|
@ -203,48 +229,69 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.secrets != {}) {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.identityPaths != [];
|
||||
message = "age.identityPaths must be set.";
|
||||
}
|
||||
];
|
||||
|
||||
# Create a new directory full of secrets for symlinking (this helps
|
||||
# ensure removed secrets are actually removed, or at least become
|
||||
# invalid symlinks).
|
||||
system.activationScripts.agenixNewGeneration = {
|
||||
text = newGeneration;
|
||||
deps = [
|
||||
"specialfs"
|
||||
config = mkIf (cfg.secrets != {}) (mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.identityPaths != [];
|
||||
message = "age.identityPaths must be set.";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
system.activationScripts.agenixInstall = {
|
||||
text = installSecrets;
|
||||
deps = [
|
||||
"agenixNewGeneration"
|
||||
"specialfs"
|
||||
];
|
||||
};
|
||||
(optionalAttrs (!isDarwin) {
|
||||
# Create a new directory full of secrets for symlinking (this helps
|
||||
# ensure removed secrets are actually removed, or at least become
|
||||
# invalid symlinks).
|
||||
system.activationScripts.agenixNewGeneration = {
|
||||
text = newGeneration;
|
||||
deps = [
|
||||
"specialfs"
|
||||
];
|
||||
};
|
||||
|
||||
# So user passwords can be encrypted.
|
||||
system.activationScripts.users.deps = ["agenixInstall"];
|
||||
system.activationScripts.agenixInstall = {
|
||||
text = installSecrets;
|
||||
deps = [
|
||||
"agenixNewGeneration"
|
||||
"specialfs"
|
||||
];
|
||||
};
|
||||
|
||||
# Change ownership and group after users and groups are made.
|
||||
system.activationScripts.agenixChown = {
|
||||
text = chownSecrets;
|
||||
deps = [
|
||||
"users"
|
||||
"groups"
|
||||
];
|
||||
};
|
||||
# So user passwords can be encrypted.
|
||||
system.activationScripts.users.deps = ["agenixInstall"];
|
||||
|
||||
# So other activation scripts can depend on agenix being done.
|
||||
system.activationScripts.agenix = {
|
||||
text = "";
|
||||
deps = ["agenixChown"];
|
||||
};
|
||||
};
|
||||
# Change ownership and group after users and groups are made.
|
||||
system.activationScripts.agenixChown = {
|
||||
text = chownSecrets;
|
||||
deps = [
|
||||
"users"
|
||||
"groups"
|
||||
];
|
||||
};
|
||||
|
||||
# So other activation scripts can depend on agenix being done.
|
||||
system.activationScripts.agenix = {
|
||||
text = "";
|
||||
deps = ["agenixChown"];
|
||||
};
|
||||
})
|
||||
(optionalAttrs isDarwin {
|
||||
launchd.daemons.activate-agenix = {
|
||||
script = ''
|
||||
set -e
|
||||
set -o pipefail
|
||||
export PATH="${pkgs.gnugrep}/bin:${pkgs.coreutils}/bin:@out@/sw/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
${newGeneration}
|
||||
${installSecrets}
|
||||
${chownSecrets}
|
||||
exit 0
|
||||
'';
|
||||
serviceConfig = {
|
||||
RunAtLoad = true;
|
||||
KeepAlive.SuccessfulExit = false;
|
||||
};
|
||||
};
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
|
10
test/install_ssh_host_keys_darwin.nix
Normal file
10
test/install_ssh_host_keys_darwin.nix
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Do not copy this! It is insecure. This is only okay because we are testing.
|
||||
{
|
||||
system.activationScripts.extraUserActivation.text = ''
|
||||
echo "Installing SSH host key"
|
||||
sudo cp ${../example_keys/system1.pub} /etc/ssh/ssh_host_ed25519_key.pub
|
||||
sudo cp ${../example_keys/system1} /etc/ssh/ssh_host_ed25519_key
|
||||
sudo chmod 644 /etc/ssh/ssh_host_ed25519_key.pub
|
||||
sudo chmod 600 /etc/ssh/ssh_host_ed25519_key
|
||||
'';
|
||||
}
|
24
test/integration_darwin.nix
Normal file
24
test/integration_darwin.nix
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
secret = "hello";
|
||||
testScript = pkgs.writeShellApplication {
|
||||
name = "agenix-integration";
|
||||
text = ''
|
||||
grep ${secret} ${config.age.secrets.secret1.path}
|
||||
'';
|
||||
};
|
||||
in {
|
||||
imports = [
|
||||
./install_ssh_host_keys_darwin.nix
|
||||
../modules/age.nix
|
||||
];
|
||||
|
||||
services.nix-daemon.enable = true;
|
||||
|
||||
age.secrets.secret1.file = ../example/secret1.age;
|
||||
|
||||
environment.systemPackages = [testScript];
|
||||
}
|
Loading…
Reference in a new issue