UPDATE 4/7/2018 – I’ve come up with a simpler and more secure approach that doesn’t require Dropbox.
I don’t have to tell you that CircleCI is an amazing CI platform, but it does have it’s pain points, and if you’re trying to setup an Android build job, getting your APK artifact(s) signed might be one of them.
In most other CI environments, particularly those that are hosted in-house there are numerous ways to securely store sensitive files for use at build time. When it comes to CircleCI no such facilities currently exist. CircleCI’s own advice on the subject while straightforward is not exactly step by step instructions: download what you need from a “secure location” during the build using secure environment variables.
There are many ways to do this, but I wanted a method that was simple but moderately secure. The approach I settled on is to use a private share link to the signing keystore on Dropbox to download the private key at build time. I initially attempted with Google Drive but could not successfully download Google Drive share links with wget or curl. It’s theoretically possible for someone someone to intercept or guess the randomly generated download URI, the likelihood is low enough my bar of “moderate” security. If you are worried, these instructions can be adapted to a work with a secure remote host and SCP instead of Dropbox.
Here are the basic steps to get things working with the Dropbox method:
- Upload your private key to Dropbox and generate a share link.
- Add environment variables to your CircleCI project: SIGNING_KEY_URI = <dropbox link uri>
- Create a download shell script in your project. You can put the keystore file wherever you want. For this example let’s create it in misc/download_keystore.sh with this content:
# use curl to download a keystore from $KEYSTORE_URI, if set, # to the path/filename set in $KEYSTORE. if [[ $KEYSTORE && ${KEYSTORE} && $KEYSTORE_URI && ${KEYSTORE_URI} ]] then echo "Keystore detected - downloading..." # we're using curl instead of wget because it will not # expose the sensitive uri in the build logs: curl -L -o ${KEYSTORE} ${KEYSTORE_URI} else echo "Keystore uri not set. .APK artifact will not be signed." fi
- Add the $KEYSTORE environment variable to your CircleCI config:
machine: environment: KEYSTORE: ${HOME}/${CIRCLE_PROJECT_REPONAME}/signing.keystore
- Call the download script from your CircleCI config:
dependencies: pre: - bash ./misc/download_keystore.sh
- Use the $KEYSTORE environment var in the signingConfig section of your build.gradle:
signingConfigs { release { storeFile file(System.getenv("KEYSTORE")) storePassword System.getenv("KEYSTORE_PASSWORD") keyAlias System.getenv("KEY_ALIAS") keyPassword System.getenv("KEY_PASSWORD") } }
- Profit!
For a fully functional, real-world example of a project using this configuration, check out Androidplot on Github.
Thanks!
Thanks for writing this up! Are you not concerned about checking in the secret URL to your source control? This would give all developers access to download the keystore. You could alternatively serialize it in to a circleci environment variable, limiting access to only authorized buildmasters.
For production builds, I would recommend downloading the unsigned apk from your CI environment and signing it in a secure local environment rather than letting a 3rd party sign it.
Thx for the reply! Unless I’m misunderstanding, youur suggestion about using a circle env var instead of checking in the secret URL is exactly what this example does already.
As far as signing APK’s locally goes, thats not an efficient or scaleable solution IMHO, and really only works for very early stage startups. Aside from the ongoing wasted effort of signing every release build, you can also grant build access to individuals without handing over a copy of the private key & password.
Thank you for your proposed solution. I suggest avoiding the dependency on another server like dropbox. You can host a small file on CircleCi in a somewhat non straight forward way.
You can host the contents of the keystore file using CircleCi environment variables.
ENV_VAR=”$(cat keystoreFilePath | base64)”
While CircleCi running the file can be retrieve using :
echo $ENV_VAR | base64 –decode > keystoreFilePath
Hey Islam, apologies, I just read your comment today. Coincidentally, I wound up writing a followup post about what’s essentially the same approach before seeing the comment. Your comment predates the followup post I made so I just wanted to leave a note acknowledging that fact!
Cheers!