Tuesday, March 02, 2021

Using 1password To Seamlessly Manage Subversion Credentials

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!

No comments:

Post a Comment