diff options
author | aszlig <aszlig@redmoonstudios.org> | 2016-04-02 11:13:27 +0200 |
---|---|---|
committer | aszlig <aszlig@redmoonstudios.org> | 2016-04-02 14:08:51 +0200 |
commit | 8db1803b5d9865b2355fabdb6bb974d879ce57cc (patch) | |
tree | 7bac4a2bcf3fa1773df040fb113901e64d2625b7 /modules/programs/gpg-agent/default.nix | |
parent | 157f16889edab556dd54cf9f367a021119019ed4 (diff) |
Add a new module and test for gpg-agent
Since NixOS/nixpkgs@5391882 there no longer is the option to start the agent during X session startup, which prompted me to write this module. I was unhappy how GnuPG is handled in NixOS since a long time and wanted to OCD all the configuration files directly into the module. Unfortunately, this is something I eventually gave up because GnuPG's design makes it very hard to preseed configuration. My first attempt was to provide default configuration files in /etc/gnupg, but that wasn't properly picked up by GnuPG. Another way would have been to change the default configuration files, but that would have the downside that we could only override those configurations using command line options for each individual GnuPG component. The approach I tried to go for was to patch GnuPG so that all the defaults are directly set in the source code using a giant sed expression. It turned out that this approach doesn't work very well, because every component has implemented its own ways how to handle commandline arguments versus (default) configuration files. In the end I gave up trying to OCD anything related to GnuPG configuration and concentrated just on the agent. And that's another beast, which unfortunately doesn't work very well with systemd. While searching the net for existing patches I stumbled upon one done by @shlevy: https://lists.gnupg.org/pipermail/gnupg-devel/2014-November/029092.html Unfortunately, the upstream author seems to be quite anti-systemd and didn't want to accept that into the upstream project. Because of this I went for using LD_PRELOAD to pick up the file descriptors provided by the systemd sockets, because in the end I don't want to constantly catch up with upstream and rebase the patch on every new release. Apart from just wrapping the agent to be socket activated, we also wrap the pinentry program, so that we can inject a _CLIENT_PID environment variable from the LD_PRELOAD wrapper that is picked up by the pinentry wrapper to determine the TTY and/or display of the client communicating with the agent. The wrapper uses the proc filesystem to get all the relevant information and passes it to the real pinentry. The advantage of this is that we don't need to do things such as "gpg-connect-agent updatestartuptty /bye" or any other workarounds and even if we connect via SSH the agent should be able to correctly pick up the TTY and/or display. Signed-off-by: aszlig <aszlig@redmoonstudios.org>
Diffstat (limited to 'modules/programs/gpg-agent/default.nix')
-rw-r--r-- | modules/programs/gpg-agent/default.nix | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/modules/programs/gpg-agent/default.nix b/modules/programs/gpg-agent/default.nix new file mode 100644 index 00000000..81a113f7 --- /dev/null +++ b/modules/programs/gpg-agent/default.nix @@ -0,0 +1,164 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.vuizvui.programs.gpg-agent; + + pinentryWrapper = pkgs.runCommand "pinentry-wrapper" { + pinentryProgram = cfg.pinentry.program; + } '' + cc -Wall -std=gnu11 -DPINENTRY_PROGRAM=\"$pinentryProgram\" \ + "${./pinentry-wrapper.c}" -o "$out" + ''; + + scdaemonRedirector = pkgs.writeScript "scdaemon-redirector" '' + #!${pkgs.stdenv.shell} + exec "${pkgs.socat}/bin/socat" - \ + UNIX-CONNECT:"$HOME/${cfg.homeDir}/S.scdaemon" + ''; + + agentWrapper = pkgs.runCommand "gpg-agent-wrapper" { + buildInputs = with pkgs; [ pkgconfig systemd ]; + inherit pinentryWrapper; + } '' + cc -Wall -shared -std=c11 \ + $(pkg-config --cflags --libs libsystemd) -ldl \ + -DPINENTRY_WRAPPER=\"$pinentryWrapper\" \ + "${./agent-wrapper.c}" -o "$out" -fPIC + ''; + + agentSocketConfig = name: { + FileDescriptorName = name; + Service = "gpg-agent.service"; + SocketMode = "0600"; + DirectoryMode = "0700"; + }; + +in { + options.vuizvui.programs.gpg-agent = { + enable = mkEnableOption "support for GnuPG agent"; + + homeDir = mkOption { + type = types.addCheck types.str (d: builtins.substring 0 1 d != "/"); + default = ".gnupg"; + description = '' + The directory where GnuPG keeps its state files and configuration files, + relative to the user's home directory. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.gnupg; + defaultText = "pkgs.gnupg"; + example = literalExample "pkgs.gnupg21"; + description = "The GnuPG package to use for running the agent."; + }; + + pinentry.program = mkOption { + type = types.path; + default = "${pkgs.pinentry}/bin/pinentry"; + defaultText = "\${pkgs.pinentry}/bin/pinentry"; + example = literalExample "\${pkgs.pinentry_qt5}/bin/pinentry"; + description = "The pinentry program to use to ask for passphrases."; + }; + + sshSupport = mkEnableOption "GnuPG agent support for SSH"; + + scdaemon = { + enable = mkEnableOption "GnuPG agent with Smartcard daemon"; + + program = mkOption { + type = types.path; + default = "${cfg.package}/libexec/scdaemon"; + defaultText = let + configPath = "config.vuizvui.programs.gpg-agent"; + in "\${${configPath}.package}/libexec/scdaemon"; + example = literalExample "\${pkgs.my_shiny_scdaemon}/bin/scdaemon"; + description = "The program to use for the Smartcard daemon"; + }; + }; + }; + + config = mkMerge [ + (mkIf cfg.enable { + vuizvui.requiresTests = singleton ["vuizvui" "programs" "gpg-agent"]; + environment.systemPackages = [ cfg.package ]; + + systemd.user.services.gpg-agent = { + description = "GnuPG Agent"; + environment.LD_PRELOAD = agentWrapper; + environment.GNUPGHOME = "~/${cfg.homeDir}"; + + serviceConfig.ExecStart = toString ([ + "${cfg.package}/bin/gpg-agent" + "--pinentry-program=${pinentryWrapper}" + (if cfg.scdaemon.enable + then "--scdaemon-program=${scdaemonRedirector}" + else "--disable-scdaemon") + "--no-detach" + "--daemon" + ] ++ optional cfg.sshSupport "--enable-ssh-support"); + + serviceConfig.ExecReload = toString [ + "${cfg.package}/bin/gpg-connect-agent" + "RELOADAGENT" + "/bye" + ]; + }; + + systemd.user.sockets.gpg-agent-main = { + wantedBy = [ "sockets.target" ]; + description = "Main Socket For GnuPG Agent"; + listenStreams = [ "%h/${cfg.homeDir}/S.gpg-agent" ]; + socketConfig = agentSocketConfig "main"; + }; + }) + (mkIf (cfg.enable && cfg.scdaemon.enable) { + systemd.user.sockets.gnupg-scdaemon = { + wantedBy = [ "sockets.target" ]; + description = "GnuPG Smartcard Daemon Socket"; + listenStreams = [ "%h/${cfg.homeDir}/S.scdaemon" ]; + socketConfig = { + FileDescriptorName = "scdaemon"; + SocketMode = "0600"; + DirectoryMode = "0700"; + }; + }; + + systemd.user.services.gnupg-scdaemon = { + description = "GnuPG Smartcard Daemon"; + environment.LD_PRELOAD = agentWrapper; + environment.GNUPGHOME = "~/${cfg.homeDir}"; + + serviceConfig.ExecStart = toString [ + "${cfg.scdaemon.program}" + "--no-detach" + "--daemon" + ]; + }; + }) + (mkIf (cfg.enable && cfg.sshSupport) { + environment.variables.SSH_AUTH_SOCK = + "$HOME/${cfg.homeDir}/S.gpg-agent.ssh"; + + systemd.user.sockets.gpg-agent-ssh = { + wantedBy = [ "sockets.target" ]; + description = "SSH Socket For GnuPG Agent"; + listenStreams = [ "%h/${cfg.homeDir}/S.gpg-agent.ssh" ]; + socketConfig = agentSocketConfig "ssh"; + }; + + assertions = singleton { + assertion = !config.programs.ssh.startAgent; + message = toString [ + "You cannot use the GnuPG agent with SSH support in addition to the" + "SSH agent, please either disable" + "`vuizvui.programs.gpg-agent.sshSupport' or disable" + "`programs.ssh.startAgent'." + ]; + }; + }) + ]; +} |