diff --git a/modules/modules.nix b/modules/modules.nix index 5fd4394aa3b1..156094c85fce 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -424,6 +424,7 @@ let ./services/xscreensaver.nix ./services/xsettingsd.nix ./services/xsuspender.nix + ./services/yubikey-agent.nix ./systemd.nix ./targets/darwin ./targets/generic-linux.nix diff --git a/modules/services/yubikey-agent.nix b/modules/services/yubikey-agent.nix new file mode 100644 index 000000000000..5e1379a38aff --- /dev/null +++ b/modules/services/yubikey-agent.nix @@ -0,0 +1,92 @@ +{ config, lib, pkgs, ... }: + +let + inherit (lib) mkIf; + cfg = config.services.yubikey-agent; + +in { + meta.maintainers = [ lib.maintainers.cmacrae ]; + + options.services.yubikey-agent = { + enable = lib.mkEnableOption "Seamless ssh-agent for YubiKeys"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.yubikey-agent; + defaultText = lib.literalExpression "pkgs.yubikey-agent"; + description = "The yubikey-agent package to use."; + }; + }; + + config = mkIf cfg.enable (lib.mkMerge [ + { home.packages = [ cfg.package ]; } + + (mkIf pkgs.stdenv.isLinux { + systemd.user.services.yubikey-agent = { + Unit = { + Description = "Seamless ssh-agent for YubiKeys"; + Documentation = "/~https://github.com/FiloSottile/yubikey-agent"; + Requires = "yubikey-agent.socket"; + After = "yubikey-agent.socket"; + RefuseManualStart = true; + }; + + Service = { + ExecStart = + "${cfg.package}/bin/yubikey-agent -l %t/yubikey-agent/yubikey-agent.sock"; + Type = "simple"; + # /run/user/$UID for the socket + ReadWritePaths = [ "%t" ]; + }; + }; + + systemd.user.sockets.yubikey-agent = { + Unit = { + Description = "Unix domain socket for Yubikey SSH agent"; + Documentation = "/~https://github.com/FiloSottile/yubikey-agent"; + }; + + Socket = { + ListenStream = "%t/yubikey-agent/yubikey-agent.sock"; + RuntimeDirectory = "yubikey-agent"; + SocketMode = "0600"; + DirectoryMode = "0700"; + }; + + Install = { WantedBy = [ "sockets.target" ]; }; + }; + + home.sessionVariables = { + SSH_AUTH_SOCK = + "\${XDG_RUNTIME_DIR:-/run/user/$UID}/yubikey-agent/yubikey-agent.sock"; + }; + }) + + (mkIf pkgs.stdenv.isDarwin { + launchd.agents.yubikey-agent = { + enable = true; + config = { + ProgramArguments = [ + "${cfg.package}/bin/yubikey-agent" + "-l" + "/tmp/yubikey-agent.sock" + ]; + + KeepAlive = { + Crashed = true; + SuccessfulExit = false; + }; + ProcessType = "Background"; + Sockets = { + Listener = { + SockPathName = "/tmp/yubikey-agent.sock"; + SockPathMode = 384; # 0600 in decimal + }; + }; + }; + }; + + home.sessionVariables = { SSH_AUTH_SOCK = "/tmp/yubikey-agent.sock"; }; + }) + ]); +} diff --git a/tests/default.nix b/tests/default.nix index 96625abec396..5fba00801c21 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -263,6 +263,7 @@ in import nmtSrc { ./modules/services/imapnotify-darwin ./modules/services/nix-gc-darwin ./modules/services/ollama/darwin + ./modules/services/yubikey-agent-darwin ./modules/targets-darwin ] ++ lib.optionals isLinux [ ./modules/config/i18n @@ -389,6 +390,7 @@ in import nmtSrc { ./modules/services/wlsunset ./modules/services/wob ./modules/services/xsettingsd + ./modules/services/yubikey-agent ./modules/systemd ./modules/targets-linux ]); diff --git a/tests/modules/services/yubikey-agent-darwin/default.nix b/tests/modules/services/yubikey-agent-darwin/default.nix new file mode 100644 index 000000000000..5594aa8e8721 --- /dev/null +++ b/tests/modules/services/yubikey-agent-darwin/default.nix @@ -0,0 +1 @@ +{ yubikey-agent-darwin = ./service.nix; } diff --git a/tests/modules/services/yubikey-agent-darwin/service.nix b/tests/modules/services/yubikey-agent-darwin/service.nix new file mode 100644 index 000000000000..aa09695d019c --- /dev/null +++ b/tests/modules/services/yubikey-agent-darwin/service.nix @@ -0,0 +1,50 @@ +{ config, ... }: + +{ + services.yubikey-agent = { + enable = true; + package = config.lib.test.mkStubPackage { outPath = "@yubikey-agent@"; }; + }; + + nmt.script = '' + serviceFile=LaunchAgents/org.nix-community.home.yubikey-agent.plist + assertFileExists "$serviceFile" + assertFileContent "$serviceFile" ${ + builtins.toFile "expected-agent.plist" '' + + + + + KeepAlive + + Crashed + + SuccessfulExit + + + Label + org.nix-community.home.yubikey-agent + ProcessType + Background + ProgramArguments + + @yubikey-agent@/bin/yubikey-agent + -l + /tmp/yubikey-agent.sock + + Sockets + + Listener + + SockPathMode + 384 + SockPathName + /tmp/yubikey-agent.sock + + + + + '' + } + ''; +} diff --git a/tests/modules/services/yubikey-agent/default.nix b/tests/modules/services/yubikey-agent/default.nix new file mode 100644 index 000000000000..d2ff5a353d54 --- /dev/null +++ b/tests/modules/services/yubikey-agent/default.nix @@ -0,0 +1 @@ +{ yubikey-agent = ./service.nix; } diff --git a/tests/modules/services/yubikey-agent/service.nix b/tests/modules/services/yubikey-agent/service.nix new file mode 100644 index 000000000000..90ff5cc72d16 --- /dev/null +++ b/tests/modules/services/yubikey-agent/service.nix @@ -0,0 +1,49 @@ +{ config, ... }: + +{ + services.yubikey-agent = { + enable = true; + package = config.lib.test.mkStubPackage { outPath = "@yubikey-agent@"; }; + }; + + nmt.script = '' + serviceFile=home-files/.config/systemd/user/yubikey-agent.service + socketFile=home-files/.config/systemd/user/yubikey-agent.socket + + assertFileExists $serviceFile + assertFileExists $socketFile + + assertFileContent $serviceFile ${ + builtins.toFile "expected-service" '' + [Service] + ExecStart=@yubikey-agent@/bin/yubikey-agent -l %t/yubikey-agent/yubikey-agent.sock + ReadWritePaths=%t + Type=simple + + [Unit] + After=yubikey-agent.socket + Description=Seamless ssh-agent for YubiKeys + Documentation=/~https://github.com/FiloSottile/yubikey-agent + RefuseManualStart=true + Requires=yubikey-agent.socket + '' + } + + assertFileContent $socketFile ${ + builtins.toFile "expected-socket" '' + [Install] + WantedBy=sockets.target + + [Socket] + DirectoryMode=0700 + ListenStream=%t/yubikey-agent/yubikey-agent.sock + RuntimeDirectory=yubikey-agent + SocketMode=0600 + + [Unit] + Description=Unix domain socket for Yubikey SSH agent + Documentation=/~https://github.com/FiloSottile/yubikey-agent + '' + } + ''; +}