Introduction
A few months back I wrote about how we use Apache Maven for Continuous Delivery/Deployment . In that example, we are publishing to CloudBees' internal repository. One of the major powers behind Apache Maven has been its associated central repository as a means of exchanging artifacts between organizations and open source projects.
One of the current requirements for artifact hosting on Maven Central is that the artifacts have an associated GPG signature from a published key.
Now while we could argue about the utility of this key:
- How would you know which keys were the correct keys to check each artifact against?
- Does anyone even check that the downloaded artifacts match the key signature?
- Etc.
The reality is that right now you need a GPG signature to get your artifact into Maven Central.
When you are manually releasing artifacts for just your own projects to Maven Central, meeting the GPG signature requirements should not be overly difficult:
- Generate the GPG key on your machine
-
Configure the pom.xml to use that key
<plugin> <artifactId>maven-gpg-plugin</artifactId> <configuration> ... <keyname>...</keyname> ... </configuration> </plugin>
- Publish the key to an appropriate key registry
- Success!
If you have any issues, my experience has been that Sonatype has been only too happy to help.
But what happens if you want to have your favorite CI/CD server run the deployments?
Well, when that happens you will run headlong straight into all the protections that the GPG project have added to prevent automated tools from signing things with GPG keys!
You might feel like giving up at that stage... well I've got your back. Here are two techniques you can use to automate a deployment to Maven Central.
Option 1: Don't use the GPG tooling
GPG signatures follow a specification (just don't ask me for the link to that specification... I'm reliably informed that there is a specification... I'm hoping that the specification is not "the code")
If the GPG tooling itself is being hostile to automation, perhaps we could use different tooling.
That is exactly the approach my colleague Kohsuke came up with when he wrote pgp-maven-plugin .
To use this plugin you just:
-
Disable the Maven GPG plugin in your pom.xml (by removing it or by configuring it to skip)
<plugin> <artifactId>maven-gpg-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin>
-
Add the PGP Maven plugin to your pom.xml
<plugin> <groupId>org.kohsuke</groupId> <artifactId>pgp-maven-plugin</artifactId> <executions> <execution> <goals> <goal>sign</goal> </goals> </execution> </executions> </plugin>
- Have your build server set the PGP_SECRETKEY and PGP_PASSPHRASE environment variables to keyfile:path/to/key .asc and literal:your-key-secret-passphrase respectively
- Run Maven
The main issue I have with this approach is that either your automated builds are now different from manual builds or you need to set the appropriate environment variables locally. If - like me - you have multiple projects that each use different keys, this becomes really annoying
Option 2: Configure GPG to allow headless signing
The GPG project does not make it impossible to have headless signing... it just makes it non-obvious because they want to ensure it is something you really want to do.
What you need to do is create an archive file with the content of the GPG HOME directory that has just the configuration files and only the keys you require
To start, you're going to need the following gpg.conf file which tells GPG to use an agent and enables loopback pin entry
use-agent pinentry-mode loopback
Next we configure GPG Agent to allow loopback pin entry with the gpg-agent.conf file
allow-loopback-pinentry
Here's a worked example of setting up the key
This assumes that the identifier of the key you want to use is stored in $KEY_ID
-
Start by changing to a temporary directory (as we do not want to affect your normal ~/.gnupg home directory:
cd /tmp
-
Make a directory called .gnupg to make extracting the archive easier:
mkdir .gnupg
-
Change the permissions so that GPG will allow you to use it as a home:
chmod 700 .gnupg
-
Create the gpg.conf file:
cat > gpg.conf << EOF use-agent pinentry-mode loopback EOF
-
Create the gpg-agent.conf file:
cat > gpg-agent.conf << EOF allow-loopback-pinentry EOF
-
Export the secret key from your ~/.gnupg home and import it into the home that the CI/CD server will use:
gpg --export-secret-key -a $KEY_ID | GNUPGHOME=/tmp/.gnupg/ gpg --allow-secret-key-import --import
-
Modify the newly imported key to be trusted and use a different passphrase:
GNUPGHOME=/tmp/.gnupg/ gpg --edit-key $KEY_ID trust 5 y passwd secret-passphrase secret-passphrase quit
-
Create the archive of only the required files:
tar -czvf gnupg.home.tar.gz -C .. .gnupg/gpg.conf .gnupg/gpg-agent.conf .gnupg/trustdb.gpg .gnupg/private-keys-v1.d
Once you have this archive you can upload both it and the secret passphrase you used as secrets for your CI/CD server. For example, in Jenkins, you would upload use the passphrase as a secret text and the file as a secret file.
Then you can use this file like so:
export GNUPGHOME=... gpgconf --kill gpg-agent || true rm -rf "${GNUPGHOME}" mkdir -p "$(dirname "${GNUPGHOME}")" tar -xzvof "${GNUPGHOME_ARCHIVE}" -d "$(dirname "${GNUPGHOME}")" gpgconf --launch gpg-agent ... mvn release:prepare release:perform ... gpgconf --kill gpg-agent || true rm -rf "${GNUPGHOME}"
You will also need to configure the Maven GPG plugin to use the agent
<plugin> <artifactId>maven-gpg-plugin</artifactId> <configuration> <useAgent>true</useAgent> <passphrase>${env.GPG_PASSPHRASE}</passphrase> <gpgArguments> <arg>--batch</arg> <arg>--pinentry-mode</arg> <arg>loopback</arg> </gpgArguments> </configuration> </plugin>
If you want a real-life example, you can see this Jenkinsfile and pom.xml I use for one of my side-projects - which combines the GPG archive with the continuous deployment from my older blog post.
Security concerns
Irrespective of which option you pick, you are going to be storing the GPG key on your CI/CD server, so you should take appropriate steps to ensure that the secrets are suitably secure. For example, the Jenkins instance I use for deployment - while hosted on a third party cloud - is only accessible behind a VPN as opposed to being accessible from the public internet.
Stephen Connolly has over 25 years experience in software development. He is involved in a number of open source projects, including Jenkins . Stephen was one of the first non-Sun committers to the Jenkins project and developed the weather icons. Stephen lives in Dublin, Ireland - where the weather icons are particularly useful. Follow Stephen on Twitter , GitHub and on his blog .