mirror of
https://github.com/moons-14/dotfiles.git
synced 2026-10-06 06:28:11 +09:00
259 lines
6.4 KiB
Nix
259 lines
6.4 KiB
Nix
{
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.my.applications.ssh;
|
|
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" ''
|
|
${pkgs.libfido2}/bin/fido2-token -L 2>/dev/null \
|
|
| ${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 { });
|
|
|
|
userMatchBlocks = lib.mapAttrs (
|
|
_name: value: lib.hm.dag.entryBefore [ "my-github" "my-default" ] value
|
|
) (cfg.matchBlocks or { });
|
|
|
|
githubBlock = {
|
|
header = "Host github.com";
|
|
|
|
HostName = "github.com";
|
|
User = "git";
|
|
|
|
IdentityAgent = "SSH_AUTH_SOCK";
|
|
IdentitiesOnly = false;
|
|
ForwardAgent = false;
|
|
|
|
AddKeysToAgent = cfg.addKeysToAgent;
|
|
}
|
|
// lib.optionalAttrs (cfg.githubIdentityFiles != [ ]) {
|
|
IdentityFile = cfg.githubIdentityFiles;
|
|
};
|
|
|
|
in
|
|
{
|
|
options.my.applications.ssh.homeManager = {
|
|
enable = lib.mkEnableOption "SSH home-manager configuration";
|
|
};
|
|
|
|
config.home-manager.sharedModules = [
|
|
(
|
|
{ lib, ... }:
|
|
{
|
|
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 = {
|
|
enable = true;
|
|
enableDefaultConfig = false;
|
|
|
|
settings = userMatchBlocks // {
|
|
"my-local-fido-sk-rk" =
|
|
lib.hm.dag.entryBefore
|
|
(
|
|
[
|
|
"my-github"
|
|
"my-default"
|
|
]
|
|
++ userMatchBlockNames
|
|
)
|
|
{
|
|
header = ''Match exec "${hasFidoDevice}"'';
|
|
|
|
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-default" =
|
|
lib.hm.dag.entryAfter
|
|
(
|
|
[
|
|
"my-local-fido-sk-rk"
|
|
"my-github"
|
|
]
|
|
++ userMatchBlockNames
|
|
)
|
|
{
|
|
header = "Host *";
|
|
|
|
IdentityAgent = "SSH_AUTH_SOCK";
|
|
IdentitiesOnly = false;
|
|
|
|
# default deny。
|
|
# agent forwarding したい host だけ cfg.matchBlocks 側で true にする。
|
|
ForwardAgent = false;
|
|
|
|
IdentityFile = cfg.defaultIdentityFiles;
|
|
|
|
AddKeysToAgent = cfg.addKeysToAgent;
|
|
|
|
SetEnv = {
|
|
TERM = "xterm";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|
|
)
|
|
];
|
|
}
|