xin/services/ssh-fido-agent.nix
2023-06-14 10:44:57 -06:00

69 lines
2.4 KiB
Nix

{ config, lib, pkgs, ... }:
let
perl = "${pkgs.perl}/bin/perl";
sshAdd = "${pkgs.openssh}/bin/ssh-add";
pKill = "${pkgs.procps}/bin/pkill";
awk = "${pkgs.gawk}/bin/awk";
# fido-add-device is started by a systemd unit. It runs continuously waiting for a USR1 signal
# that is triggered by inserting a Yubikey. Once it receives the signal, it executes 'ssh-add -K'
# which when run without a terminal will use SSH_ASKPASS to prompt the user for the unlock
# phrase for their YK FIDO setup.
fidoAddDevice = pkgs.writeScriptBin "fido-add-device" ''
#!${perl}
use strict;
use warnings;
$ENV{'SSH_AUTH_SOCK'} = "$ENV{'XDG_RUNTIME_DIR'}/ssh-agent";
$ENV{'DISPLAY'} = `systemctl --user show-environment | ${awk} -F= '/^DISPLAY/ {print \$NF}'`;
$SIG{USR1} = sub { system("${sshAdd}", "-K") };
while (1) {
sleep;
}
'';
# fido-send-sig is called by a udev rule when a YK is attached. It sends SIGUSR1 to fido-add-device.
fidoSendSig = pkgs.writeScriptBin "fido-send-sig" ''
#! ${pkgs.runtimeShell} -e
${pKill} -USR1 -xf "${perl} ${fidoAddDevice}/bin/fido-add-device"
'';
# my-ssh-askpass-wrapper wraps programs.ssh.askPassword in order to supply user-specific environment
# variables.
# TODO: replace this with makeWrapper
askPassWrapper = pkgs.writeScript "my-ssh-askpass-wrapper" ''
#! ${pkgs.runtimeShell} -e
export DISPLAY="$(systemctl --user show-environment | ${awk} -F= '/^DISPLAY/ {print $NF}')"
export SSH_AUTH_SOCK="$(echo $XDG_RUNTIME_DIR/ssh-agent)";
exec ${config.programs.ssh.askPassword} "$@"
'';
in {
options = {
sshFidoAgent = {
enable = lib.mkEnableOption "Add FIDO keys to ssh-agent when attached.";
};
};
config = lib.mkIf config.sshFidoAgent.enable {
environment.systemPackages = with pkgs; [ fidoAddDevice ];
systemd.user.services.sshfidoagent = {
script = ''
${fidoAddDevice}/bin/fido-add-device
'';
wantedBy = [ "graphical-session.target" ];
partOf = [ "graphical-session.target" ];
after = [ "graphical-session.target" ];
environment.DISPLAY = "fake";
environment.SSH_ASKPASS = askPassWrapper;
#serviceConfig = { Restart = "on-failure"; };
};
services.udev.extraRules = ''
SUBSYSTEM=="hidraw", ACTION=="add", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0407|0402", RUN+="${fidoSendSig}/bin/fido-send-sig"
'';
};
}