This commit is contained in:
2026-06-25 15:46:22 +09:00
parent c68a4fe735
commit 5cb560f3d1
+155 -2
View File
@@ -9,11 +9,119 @@ let
cfg = config.my.applications.ssh; cfg = config.my.applications.ssh;
hmCfg = config.my.applications.ssh.homeManager; hmCfg = config.my.applications.ssh.homeManager;
shellPathExpr =
path:
if lib.hasPrefix "~/" path then
''"$HOME/${lib.removePrefix "~/" path}"''
else
lib.escapeShellArg path;
fidoIdentityExpr = shellPathExpr cfg.fidoIdentityFile;
hasFidoDevice = pkgs.writeShellScript "ssh-has-fido-device" '' hasFidoDevice = pkgs.writeShellScript "ssh-has-fido-device" ''
${pkgs.libfido2}/bin/fido2-token -L 2>/dev/null \ ${pkgs.libfido2}/bin/fido2-token -L 2>/dev/null \
| ${pkgs.gnugrep}/bin/grep -q . | ${pkgs.gnugrep}/bin/grep -q .
''; '';
sshYubikeyAgentSync = pkgs.writeShellApplication {
name = "ssh-yubikey-agent-sync";
runtimeInputs = with pkgs; [
openssh
libfido2
coreutils
gnugrep
gawk
];
text = ''
set -u
force=0
case "''${1:-}" in
"")
;;
"--force")
force=1
;;
*)
printf '%s\n' "usage: ssh-yubikey-agent-sync [--force]" >&2
exit 2
;;
esac
if [ -z "''${XDG_RUNTIME_DIR:-}" ]; then
exit 0
fi
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent"
if [ ! -S "$SSH_AUTH_SOCK" ]; then
exit 0
fi
fido_identity=${fidoIdentityExpr}
fido_public_key="$fido_identity.pub"
state_dir="$XDG_RUNTIME_DIR/ssh-yubikey-agent-sync"
state_file="$state_dir/fido-device-state"
mkdir -p "$state_dir"
fido_present() {
fido2-token -L 2>/dev/null | grep -q .
}
fido_state() {
fido2-token -L 2>/dev/null | sha256sum | awk '{ print $1 }'
}
pub_fingerprint() {
[ -r "$1" ] || return 1
ssh-keygen -lf "$1" -E sha256 2>/dev/null | awk '{ print $2 }'
}
agent_has_public_key() {
public_key_file="$1"
fingerprint="$(pub_fingerprint "$public_key_file")" || return 1
ssh-add -l -E sha256 2>/dev/null \
| grep -Fq "$fingerprint"
}
remove_fido_from_agent() {
ssh-add -d "$fido_identity" >/dev/null 2>&1 || true
ssh-add -d "$fido_public_key" >/dev/null 2>&1 || true
}
add_fido_to_agent() {
[ -r "$fido_identity" ] || exit 0
[ -r "$fido_public_key" ] || exit 0
remove_fido_from_agent
ssh-add -q -t "''${SSH_YUBIKEY_AGENT_LIFETIME:-24h}" "$fido_identity" >/dev/null 2>&1 || exit 0
}
if fido_present; then
new_state="$(fido_state)"
old_state="$(cat "$state_file" 2>/dev/null || true)"
if [ "$force" -eq 1 ] \
|| ! agent_has_public_key "$fido_public_key" \
|| [ "$new_state" != "$old_state" ]; then
add_fido_to_agent
printf '%s\n' "$new_state" > "$state_file"
fi
else
remove_fido_from_agent
rm -f "$state_file"
fi
'';
};
userMatchBlockNames = lib.attrNames (cfg.matchBlocks or { }); userMatchBlockNames = lib.attrNames (cfg.matchBlocks or { });
userMatchBlocks = lib.mapAttrs ( userMatchBlocks = lib.mapAttrs (
@@ -27,9 +135,7 @@ let
User = "git"; User = "git";
IdentityAgent = "SSH_AUTH_SOCK"; IdentityAgent = "SSH_AUTH_SOCK";
IdentitiesOnly = false; IdentitiesOnly = false;
ForwardAgent = false; ForwardAgent = false;
AddKeysToAgent = cfg.addKeysToAgent; AddKeysToAgent = cfg.addKeysToAgent;
@@ -49,6 +155,44 @@ in
{ lib, ... }: { lib, ... }:
{ {
config = lib.mkIf hmCfg.enable { config = lib.mkIf hmCfg.enable {
home.packages = [
sshYubikeyAgentSync
];
systemd.user.services.ssh-yubikey-agent-sync = {
Unit = {
Description = "Synchronize YubiKey SSH key with ssh-agent";
};
Service = {
Type = "oneshot";
# NixOS programs.ssh.startAgent の socket。
Environment = [
"SSH_AUTH_SOCK=%t/ssh-agent"
"SSH_YUBIKEY_AGENT_LIFETIME=24h"
];
ExecStart = "${sshYubikeyAgentSync}/bin/ssh-yubikey-agent-sync";
};
};
systemd.user.timers.ssh-yubikey-agent-sync = {
Unit = {
Description = "Periodically synchronize YubiKey SSH key with ssh-agent";
};
Timer = {
OnBootSec = "5s";
OnUnitActiveSec = "10s";
AccuracySec = "2s";
Unit = "ssh-yubikey-agent-sync.service";
};
Install = {
WantedBy = [ "timers.target" ];
};
};
programs.ssh = { programs.ssh = {
enable = true; enable = true;
@@ -66,7 +210,14 @@ in
) )
{ {
header = ''Match exec "${hasFidoDevice}"''; header = ''Match exec "${hasFidoDevice}"'';
IdentityFile = cfg.fidoIdentityFile; IdentityFile = cfg.fidoIdentityFile;
# FIDO key の agent 登録は ssh-yubikey-agent-sync に任せる。
#
# ここで AddKeysToAgent を有効にすると、
# YubiKey 抜き差し後に stale な agent entry が残りやすい。
AddKeysToAgent = "no";
}; };
"my-github" = lib.hm.dag.entryBefore [ "my-default" ] githubBlock; "my-github" = lib.hm.dag.entryBefore [ "my-default" ] githubBlock;
@@ -86,6 +237,8 @@ in
IdentityAgent = "SSH_AUTH_SOCK"; IdentityAgent = "SSH_AUTH_SOCK";
IdentitiesOnly = false; IdentitiesOnly = false;
# default deny。
# agent forwarding したい host だけ cfg.matchBlocks 側で true にする。
ForwardAgent = false; ForwardAgent = false;
IdentityFile = cfg.defaultIdentityFiles; IdentityFile = cfg.defaultIdentityFiles;