Subversion Authentication is Painful. Let's Fix That.
Subversion authentication is a pain. For
years, I got by on plain text storage, but that's insecure and deprecated. On some platforms, like Mac OS,
it's possible to use the system keychain to manage access. However,
this becomes both a source of inconsistency and frustration as I
work across multiple devices and operating systems.
What I wanted was to have subversion pull my credentials out of
1password. But how would I even begin to add this support to
subversion?
Inspired by the 1password
command line tool and this
recipe for managing ssh keys in 1password, I found a way. This
post is my attempt to document that process.
Assemble the Pieces
Convincing subversion to use 1password for authentication requires
three properly configured components. If any of the three tools
isn't setup right, the chain fails and authentication is a no
go. Let's go through this step by step.
Tool 1: op
The op command line
tool gives you shell script access to 1password. Make sure you
can sign in and pull down the password for the
relevant subversion accounts. Here's how that may look:
# The First Time
$ op signin allmysecrets.1password.com contact.ben.simon@gmail.com
# After the account is setup
$ eval $(op signin allmysecrets)
# Print the password for your subversion account.
$ op get item 'master svn account' | \
jq -r '.details.fields[] | select(.designation=="password").value'
Tool 2: gpg-agent
gpg-agent is the glue that holds our
solution together. Subversion, as we'll see in a moment, can
be convinced to consult gpg-agent for credentials. Using
gpg-agent's ability to preset a passphrase, it's possible to
programmatically insert credentials into gpg-agent. To configure this,
make sure your gpg-agent config file,
~/.gnupg/gpg-agent.conf, has the following settings:
allow-loopback-pinentry
allow-preset-passphrase
default-cache-ttl 34560000
max-cache-ttl 34560000
The allow-preset-passphrase setting is key. It's what
allows gpg-agent to accept passphrases from an external source like
1password. The high values for default-cache-ttl and
max-cache-ttl ensure that once I store credentials in
gpg-agent they won't time out. This is personal preference, and if
you wished, you could lower this value.
Having a properly configured gpg-agent gets you most of the way
there. The last challenge to setting up gpg-agent is finding the
location of the gpg-preset-passphrase command on your
system. In every version of Linux I use, I find it's in a different
location. For example, I'm composing this blog post on a Windows
Subsystem for Linux Ubuntu instance, and
gpg-preset-passphrase is found in
/usr/lib/gnupg/.
With the config setup and gpg-preset-passphrase found, it's
time to try this out.
# Confirming our test creds aren't there.
$ echo "GET_PASSPHRASE --no-ask --data passphrasetest1 a b c" | gpg-connect-agent
ERR 67108922 No data <GPG Agent>
# Store the credentials
$ /usr/lib/gnupg/gpg-preset-passphrase -c -P "ShhhItsASecret" passphrasetest1
# Confirm they are stored
$ echo "GET_PASSPHRASE --no-ask --data passphrasetest1 a b c" | gpg-connect-agent
D ShhhItsASecret
OK
Tool 3: subversion
First off, your version of subversion
needs to be built with gpg-agent support. Check this by
running svn --version:
$ svn --version
svn, version 1.13.0 (r1867053)
compiled Mar 24 2020, 12:33:36 on x86_64-pc-linux-gnu
...
The following authentication credential caches are available:
* Gnome Keyring
* GPG-Agent
* KWallet (KDE)
If GPG-Agent isn't listed under available authentication
credential caches then you need to build or download a version of
subversion that does have this support. It'll be worth it, I
promise.
Next, update ~/.subversion/config so that it uses the
gpg-agent authentication cache:
### Section for authentication and authorization customizations.
[auth]
### Set password stores used by Subversion. They should be
### delimited by spaces or commas. The order of values determines
...
# password-stores = gpg-agent,gnome-keyring,kwallet
### To disable all password stores, use an empty list:
password-store = gpg-agent
Next, perform an operation that requires subversion authentication,
like say 'svn up.' If all goes well, svn should prompt you for your
password by using your system's gpg-agent pin-entry program. You can
cancel out of this.
Finally, determine a number of important details with how subversion
is interacting with gpg-agent. Do this by looking in the subversion
auth directory, ~/.subversion/auth/svn.simple/. You
should see files named like so:
$ cd ~/.subversion/auth/svn.simple
$ ls -1
3f08d77847ea42f0b7b1ccd66fd14138
a7c22b9f8eabdc93df040f5954967d2b
7f0a2d6c2e00dc758c9385fe821f07cb
There should be one file for each subversion domain that you
access. Peeking inside one of these files should show you something
like the following:
$ cat 3f08d77847ea42f0b7b1ccd66fd14138
K 8
passtype
V 9
gpg-agent
K 15
svn:realmstring
V 39
<https://master.securerepo.com:443> SVN
K 8
username
V 3
dev
END
This config file tells subversion that whenever it wishes to access
repositories hosted on master.securerepo.com, it should do
so using the username 'dev' and consulting gpg-agent for
the password.
Let's Do This
Before we tie all this together, let's look at how this supposed to
work. Suppose you enter the command:
$ svn checkout https://master.securerepo.com/projects/borken/trunk
Subversion will look for the configuration file that correspond to
the realm string <https://master.securerepo.com:443 SVN>. In our example above, it will find this in the file
named 3f08d77847ea42f0b7b1ccd66fd14138. It will then use
this filename to ask gpg-agent for the credentials matching the keygrip
3f08d77847ea42f0b7b1ccd66fd14138. Our goal, therefore, is to
to preset our passphrase for this hex value using the password found in
1password.
Here's one solution to accomplish this:
# sign in to 1password
$ eval $(op signin allmysecrets)
# store the svn repo password in a shell variable
$ svn_password=$(op get item 'master svn account' | \
jq -r '.details.fields[] | select(.designation=="password").value')
# store the password in gpg-agent with the correct hex key
$ /usr/lib/gnupg/gpg-preset-passphrase -c -P "$svn_password" 3f08d77847ea42f0b7b1ccd66fd14138
# And we're done! svn should find the credentials in gpg-agent and
# not bother asking us
$ svn checkout https://master.securerepo.com/projects/borken/trunk src
$ cd src
$ svn switch ^/branches/feature-x
I have a shell script that contains a mapping of svn domains to
1password uuid's. This let's me run a single command to authenticate all svn domains in one go. Every time I run it I feel a bit of joy; my passwords are securely stored in 1password and they are seamlessly available to svn.
It was a long journey to get this all sorted, but it was so worth it!