Greetings, earthlings.
This is a blog post explaining how to run Emacs, GPG Agent, and SSH
Agent as user-level daemons, mostly using systemd. I do so on Arch
Linux, but in principle, this setup should work fine on any Linux
distribution that uses systemd.
Prerequisites
This post assumes an “I run Linux on my computer” level of tech-savviness. The portions of this post that pertain to Emacs also assume that you are an Emacs user. If you don’t know what any of those words mean, or if this post employs concepts that you don’t understand but would like to, please feel free to get in touch with me and I’ll be happy to write a follow-up post explaining whatever you’d like to know about.
Background
I rely on public key authentication for my day to day. SSH is the primary means by which my 11 computers + 1 smartphone communicate with each other, and it is also the means by which I authenticate to GitHub. In addition, I make routine use of GPG, which, despite criticisms that have emerged in recent years, is still the only mature, general-purpose encryption utility that allows you to encrypt any data you want.
If you decide to create a private key, you should certainly also password protect it; that way, if one of the bad guys manages to get their hands on it, your password will serve as an additional impediment. But sadly, although password protecting your private key is an effective way to annoy the bad guys, it can be an equally effective way to annoy yourself, particularly when it forces you to sit there typing a long, error prone password many times per day. Therefore, to avoid being my own worst enemy, I run an agent process in the background that remembers my private key password until some reasonable amount of time has passed.
A further wrinkle, which I have decided to address in this blog post,
is that I run Emacs in server mode on all my machines, and that I
would like my Emacs server to be aware of whether I recently typed my
SSH or GPG passwords at all times. This was somewhat of a hassle for
me to set up the first time, but let me tell you: once it is working,
it is hard to live without. Emacs will transparently decrypt any file
with the .gpg extension upon open, and transparently encrypt it upon
save. Incredibly convenient.
Set up GPG Agent
Let’s start with the easy part, which you don’t even need systemd to
do. Create a configuration file located at ~/.gnupg/gpg-agent.conf
with the following contents:
default-cache-ttl 34560000
max-cache-ttl 34560000
I basically never want my GPG Agent to reset unless I reboot, since I reboot often enough, so I have set the expiry time to the above number, which has no reason for being the number that it is, other than that I want it be large. If you want it to forget your password sooner than that, just change the above two settings to whatever interval you prefer; the number is in seconds.
That’s it. Can you believe you’re done configuring gpg-agent
already? I can’t. Here’s how it works, out of the box. You boot up.
You go about your business. You decide to decrypt a GPG-encrypted
file for the first time. Now the following happens:
- the GPG Agent launches in the background, as a daemon that will stay alive pretty readily, including if you log out and log back in
- the GPG Agent checks to see whether you recently typed your password in
- if you have, it immediately decrypts the file
- if not, it prompts you for your password, remembering it afterwards for the time interval you selected
- you’re done
Brief aside about prompting you for your password. By default, gpg
commits the unforgivable atrocity of popping up a graphical window for
you to type your password in. We can’t have that. Alternatively, in
a terminal-only envrionment, it will pop up a TUI window prompt for
your password, which is fine if you happen to be seated at your
computer running individual commands in your shell, but which will
Completely Break Everything if run in other contexts. We can’t have
that either.
To get gpg to ask you for your password in the only sensible
way—via the terminal—make sure to pass it the command line option
--pinentry-mode loopback:
$ gpg -d --pinentry-mode loopback file-to-be-decrypted.gpg
There are lots of different ways you may or may not be invoking GPG,
so hopefully the above example is adaptable to your circumstances.
One other such circumstance I’ll mention is Emacs, which lets you set
its epg-pinentry-mode variable to 'loopback, in which case gpg
will prompt you plaintext-ily for your password, as the good lord
intended:
(setq epg-pinentry-mode 'loopback)
If, like me, you get a special thrill out of using GPG with Emacs, then I recommend sticking that in your Emacs init file.
Set up SSH Agent
SSH Agent is going to require a little more work than GPG Agent did,
but we aren’t going let that harsh our vibes. My first recommendation
is to add the following configuration option to your ~/.ssh/config:
AddKeysToAgent yes
This option means that the first time you type your password in, your public key gets added to the SSH Agent for the standard amount of time, and as long as the application you’re running is in touch with the SSH Agent, no password retyping will be necessary. Your SSH Agent should remain aware of your password until it restarts, which normally means until reboot.
Next, start and enable the SSH Agent using systemd:
$ systemctl --user start ssh-agent
$ systemctl --user enable ssh-agent
This will set systemd to start your SSH Agent automatically upon
login. I know what you’re thinking: surely we want to run the
ssh-agent as a system-wide systemd service. But astonishingly,
running ssh-agent as a user-level serice works perfectly. It will
persist when you log out and log back in and is accessible from
multiple distinct login sessions. Thanx, systemd!
Unfortunately, we aren’t quite done yet. SSH Agent still isn’t going
to be able to convey its ineffable private key insight to any of your
running apps, unless the app in question knows how to find the SSH
Agent’s socket. For that to happen, every application you’d like to
stay in touch with the agent needs to be run in an environment with
the SSH_AUTH_SOCK variable set, and that the value of that
environment variable needs to be the full path to your SSH Agent
socket.
Conveniently, when you log in, systemd puts the location of that
socket for you in an environment variable:
$ echo $XDG_RUNTIME_DIR
/run/user/1234
The path to the location of the socket on your machine will look like
that, except that the 1234 part at the end will be your UID, which
is the numeric identifier that UNIX refers to your user account by.
On Arch Linux, the socket is called ssh-agent.socket, making the
full path to the socket the following:
/run/user/1234/ssh-agent.socket
On some Linux distributions, the name of the socket might be something
different, like openssh_agent. If you aren’t sure, do an ls $XDG_RUNTIME_DIR
and look for a filename in there that mentions
something to do with the ssh agent.
To get the majority of the applications you run to be aware of the
agent, you just need to set the SSH_AUTH_SOCK environment in your
login shell’s config. In my case, that login shell is zsh:
$ echo $SHELL
/usr/bin/zsh
Assuming your login shell is also zsh and the name of your SSH Agent
socket is ssh-agent.socket, add this to your ~/.zshrc:
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket"
If you use a different login shell, you probably know how to do the
equivalent of the above in that shell’s config, in which case you
should do that instead. Because zsh is my login shell, and beacuse
I start my graphical session from my login shell, my graphical session
will inherit the SSH_AUTH_SOCK envrionment variable, meaning that
any application I launch using a launcher or from within a terminal
will also inherit it.
Set up Emacs
Are we done yet? You aren’t done if you want to run Emacs the way I
do: i.e. always as a server, launched by systemd. As with
ssh-agent, I like to run Emacs as a user-level service. You can set
systemd up to start your Emacs server automatically, when you log
in, with these commnads:
$ systemctl --user start emacs
$ systemctl --user enable emacs
Because Emacs (in this setup) is launched by systemd, Emacs will not
necessarily learn what it needs to learn about the SSH Agent socket
simply by inheriting the SSH_AUTH_SOCK variable from your login
shell. Depending on the rest of your setup, you may need to be
proactive about telling it where the SSH Agent socket is.
User service environment
I recently discovered a new method for being proactive, so let’s start
with that. systemd will consult the directory
~/.config/environment.d before starting any user-level service. If
it finds a file there called *.conf, it will add any environment
variable settings in finds in that file to the environment of any
user-level service it starts up. The *.conf files are sourced in
the alphabetic/numerical order that you’d expect. So, assuming your
UID is 1234, create a file called
~/.config/environment.d/emacs.conf and add this to it:
SSH_AUTH_SOCK=/run/user/1234/ssh-agent.socket
I warn you that these environment.d files are a total drag to
refresh. As far as I know, the only failsafe way to get systemd to
use the most recent edits to these *.conf files when launching new
services is to reboot. Oh, well. At least I don’t anticipate having
to update it very often.
An alternate option: use a custom unit file
I have no issue with the user service environment option, because I
don’t run that many user-level systemd services and I don’t see any
disadvantage to putting them all in touch with my running SSH Agent.
If you would rather micromanage which of your user-level systemd
services know the location of the SSH Agent socket on a per-service
basis, another option is to edit the default unit file that comes with
the Emacs package. Should you decide to go that route, you can start
by running systemctl cat to peek at the default unit file:
$ systemctl --user cat emacs
# /usr/lib/systemd/user/emacs.service
[unit]
Description=Emacs text editor
Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
[Service]
Type=notify
ExecStart=/usr/bin/emacs --fg-daemon
# Emacs will exit with status 15 after having received SIGTERM, which
# is the default "KillSignal" value systemd uses to stop services.
SuccessExitStatus=15
# The location of the SSH auth socket varies by distribution, and some
# set it from PAM, so don't override by default.
# Environment=SSH_AUTH_SOCK=%t/keyring/ssh
Restart=on-failure
[Install]
WantedBy=default.target
To put SSH_AUTH_SOCK into Emacs’ environment when the server starts
up, you add one line to the [Service] section, which uses a special
%t shortcut for finding your SSH Agent socket. The default unit
file already has it written up in a comment for you, so all you have
to do is uncomment it and change it to
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket.
systemd rudely expects you to edit unit files using the edit
subcommand, which will open an $EDITOR window in your terminal and
invite you to contribute the new line, commit message-style. The
edit subcommand writes your change to a separate file, which
systemd knows how to include in/overlay over the real unit file when
it loads it:
$ systemctl --user edit emacs
It is woefully incosiderate of systemd, IMO, to assume that everyone
will want to make edits this way. You can do it the way systemd
expects, if you want. Alternatively, if you find systemd’s
assumption as cringe as I do, you can create this file:
# /usr/lib/systemd/user/emacs.service
[Unit]
Description=Emacs text editor
Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
[Service]
Type=notify
ExecStart=/usr/bin/emacs --fg-daemon
# Emacs will exit with status 15 after having received SIGTERM, which
# is the default "KillSignal" value systemd uses to stop services.
SuccessExitStatus=15
# The location of the SSH auth socket varies by distribution, and some
# set it from PAM, so don't override by default.
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
Restart=on-failure
[Install]
WantedBy=default.target
If that file is called new-emacs-service, then you can install it
with this command, in which you forfeit the power of making
incremental edits but gain the power of being scriptable:
$ systemctl --user edit --full --stdin emacs < new-emacs-service
Suck on that, $EDITOR!
Set up Cron
Since we’ve come this far, I might as well mention that some of my
cron jobs also require awareness of the SSH Agent, because they
involve git pull-s and such. cron doesn’t allow you to execute
arbitrary shell code in your crontab, but it does allow you to add
hardcoded values to your shell environment. So if your jobs need to
be brought into alignment with the agent, you can make that happen by
adding the same line to the top of your crontab:
SSH_AUTH_SOCK=/run/user/1234/ssh-agent.socket
Without that, any of your cron jobs that require SSH key
authentication will stall out waiting for your SSH key password.
Conclusion
The result of all this is a pretty sweet setup, from my point of view.
The SSH Agent and the Emacs server launch upon login, but are not
meaningfully tied to your login session—they persist when you log
out and log back in, and can be accessed from multiple simultaneous
login sessions. The Emacs server can be killed and started up again
at will, but systemd establishes the connection with the SSH Agent
afresh each time via the SSH_AUTH_SOCK environment variable. The
SSH Agent prompts the user for a password the first time they try to
use their SSH keys post-reboot, then remembers the password until the
next reboot. And the GPG Agent does all that too, with no special
config necessary.
Nothing left to do now but kick back and enjoy yourself.
Matt Teichman