fix(deploy): set up 'colmena' wrapper for hardware-backed SSH keys
This commit is contained in:
@@ -45,6 +45,7 @@
|
||||
taskwarrior.enable = true;
|
||||
audio.enable = true;
|
||||
ssh.enable = true;
|
||||
deploy.enable = true;
|
||||
music.enable = true;
|
||||
terminal.enable = true;
|
||||
devenv.enable = true;
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
bash.addBinToPath = true;
|
||||
};
|
||||
ssh.enable = true;
|
||||
deploy.enable = true;
|
||||
taskwarrior.enable = true;
|
||||
terminal.enable = true;
|
||||
reference-manager.enable = true;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# `colmena` wrapper script that opens a short-lived SSH master process for
|
||||
# `colmena` to use so it works with hardware-backed key touch+PIN. assumes
|
||||
# ControlPath ~/.ssh/socket-%r@%h:%p (see ./default.nix)
|
||||
|
||||
selector=""
|
||||
want_on=0
|
||||
on_given=0
|
||||
deploy_cmd=0
|
||||
nodes=()
|
||||
on_nodes=()
|
||||
master_processes=()
|
||||
|
||||
# build node list from `--on` value, a comma-separated string (e.g. "host-01,host-02")
|
||||
for arg in "$@"; do
|
||||
if ((want_on)); then
|
||||
selector="$arg"
|
||||
want_on=0
|
||||
continue
|
||||
fi
|
||||
case "$arg" in
|
||||
--on=*)
|
||||
selector="${arg#--on=}"
|
||||
on_given=1
|
||||
;;
|
||||
--on) want_on=1 on_given=1 ;;
|
||||
apply | exec | upload-keys) deploy_cmd=1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
if ((deploy_cmd)) && ! ((on_given)); then
|
||||
on_nodes=("${!_colmena_node_tags[@]}")
|
||||
else
|
||||
IFS=',' read -ra nodes <<< "$selector"
|
||||
for node in "${nodes[@]}"; do
|
||||
for node_tag in "${!_colmena_node_tags[@]}"; do
|
||||
if [[ "$node" == @* ]]; then
|
||||
for tag in ${_colmena_node_tags[$node_tag]}; do
|
||||
# shellcheck disable=SC2053
|
||||
[[ "$tag" == ${node#@} ]] && on_nodes+=("$node_tag")
|
||||
done
|
||||
else
|
||||
# shellcheck disable=SC2053
|
||||
[[ "$node_tag" == $node ]] && on_nodes+=("$node_tag")
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
for node in "${on_nodes[@]}"; do
|
||||
# check if master process is running (see `man ssh`)
|
||||
ssh -O check "$node" 2>/dev/null && continue
|
||||
|
||||
# place ssh client into "master" mode for connection sharing (see `man ssh`)
|
||||
if ssh -fNM "$node"; then
|
||||
master_processes+=("$node")
|
||||
fi
|
||||
done
|
||||
|
||||
rc=0
|
||||
@colmena@ "$@" || rc=$?
|
||||
|
||||
# request master processes to exit (see `man ssh`)
|
||||
for process in "${master_processes[@]}"; do
|
||||
ssh -O exit "$process" 2>/dev/null || true
|
||||
done
|
||||
|
||||
exit "$rc"
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
inputs,
|
||||
myUtils,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.deploy;
|
||||
|
||||
hostDir = ../../../hosts;
|
||||
hostNames = myUtils.dirNames hostDir;
|
||||
hostsWithKeys = lib.filter (
|
||||
hostname: builtins.pathExists (hostDir + "/${hostname}/ssh_host.pub")
|
||||
) hostNames;
|
||||
|
||||
colmena = inputs.colmena.packages.${pkgs.stdenv.hostPlatform.system}.colmena;
|
||||
|
||||
hostTags = hostname: (myUtils.hostMeta (hostDir + "/${hostname}")).tags;
|
||||
remoteHostsWithKeys = lib.filter (
|
||||
hostname: !(builtins.elem "local" (hostTags hostname))
|
||||
) hostsWithKeys;
|
||||
nodeTagsDecl = ''
|
||||
declare -A _colmena_node_tags=(
|
||||
${lib.concatMapStringsSep "\n" (
|
||||
hostname: " [${hostname}]=${lib.escapeShellArg (lib.concatStringsSep " " (hostTags hostname))}"
|
||||
) remoteHostsWithKeys}
|
||||
)
|
||||
'';
|
||||
|
||||
colmenaWrapped = pkgs.writeShellApplication {
|
||||
name = "colmena";
|
||||
runtimeInputs = [ pkgs.openssh ];
|
||||
text =
|
||||
nodeTagsDecl
|
||||
+ lib.replaceStrings [ "@colmena@" ] [ "${colmena}/bin/colmena" ] (
|
||||
builtins.readFile ./colmena-wrapper.bash
|
||||
);
|
||||
};
|
||||
in
|
||||
{
|
||||
options.deploy.enable = lib.mkEnableOption "deploy";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.ssh.enable;
|
||||
message = "'home/modules/deploy' requires 'home/modules/ssh'";
|
||||
}
|
||||
];
|
||||
|
||||
home.packages = [ colmenaWrapped ];
|
||||
programs.ssh.settings = lib.genAttrs hostsWithKeys (
|
||||
hostname:
|
||||
let
|
||||
meta = myUtils.hostMeta (hostDir + "/${hostname}");
|
||||
isLocal = builtins.elem "local" meta.tags;
|
||||
in
|
||||
{
|
||||
User = meta.host.username;
|
||||
}
|
||||
// lib.optionalAttrs (!isLocal) {
|
||||
HostName = hostname;
|
||||
ControlPath = "~/.ssh/socket-%r@%h:%p";
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -2,17 +2,11 @@
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
myUtils,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.ssh;
|
||||
hostDir = ../../hosts;
|
||||
hostNames = myUtils.dirNames hostDir;
|
||||
hostsWithKeys = lib.filter (
|
||||
hostname: builtins.pathExists (hostDir + "/${hostname}/ssh_host.pub")
|
||||
) hostNames;
|
||||
in
|
||||
{
|
||||
options.ssh.enable = lib.mkEnableOption "ssh";
|
||||
@@ -24,30 +18,16 @@ in
|
||||
enable = true;
|
||||
enableDefaultConfig = false;
|
||||
|
||||
settings =
|
||||
lib.genAttrs hostsWithKeys (
|
||||
hostname:
|
||||
let
|
||||
meta = myUtils.hostMeta (hostDir + "/${hostname}");
|
||||
isLocal = builtins.elem "local" meta.tags;
|
||||
in
|
||||
{
|
||||
User = meta.host.username;
|
||||
}
|
||||
// lib.optionalAttrs (!isLocal) {
|
||||
HostName = hostname;
|
||||
}
|
||||
)
|
||||
// {
|
||||
"*" = {
|
||||
AddKeysToAgent = "yes";
|
||||
ForwardAgent = false;
|
||||
identityFile = [
|
||||
"~/.ssh/id_ed25519_sk"
|
||||
"~/.ssh/id_ed25519_sk_bak"
|
||||
];
|
||||
};
|
||||
settings = {
|
||||
"*" = {
|
||||
AddKeysToAgent = "yes";
|
||||
ForwardAgent = false;
|
||||
identityFile = [
|
||||
"~/.ssh/id_ed25519_sk"
|
||||
"~/.ssh/id_ed25519_sk_bak"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user