Story Time
A few years ago, GitHub didn't have any protection against forking an org's private repo when the user had a free tier account, which excluded the ability to have private repos (GitHub has since changed their pricing model so that all tiers currently have the ability to have free private repos).
My org had just hired a new developer who was used to forking the main repo when contributing to a code base. They had a free tier GitHub account and as soon as they forked the main repo, our code base was instantly made public, and subsequently cloned by a bot within minutes.
The repo contained secrets.
We were made aware of the situation when a 3rd party vendor suspended our account for abuse. Someone had nefariously used the secrets that were committed to our repo.
Needless to say, we all panicked and started revoking all keys and certificates.
But what happens when you are revoking API keys used for mobile apps? Will the app stop working as expected? Are your users going to raid your app with 1 star reviews? Is user data compromised?
I'm not going to solve these headaches for you today. However, if you have secrets committed to your repositories, I would strongly recommend removing them as soon as possible.
The best thing you can do is never even commit a single secret to your repositories in the first place.
Identify Your Secrets
So how do you know if you have committed secrets to your code base?
I recommend using a tool like truffleHog or GoDaddy's tartufo (which is a fork of truffleHog).
You can run these tools on your existing code base and determine if you have any secrets that were committed at some point in the history of the repo. This will return you a report with triggering commits and context on what is triggering.
For example, running tartufo scan-local-repo .
will cause errors with svg files:
If needed, you can exclude certain files (e.g. any xcodeproj and xcworkspace files will trigger these tools). For example:
Using these types of tools in your CI pipelines and pre-commit hooks is a good idea to prevent secrets from being committed and merged in your code base.
Clean Your History
This, my friends, is the BFG9000. The (B)iggest (F)***ing (G)un you can get in the DOOM video games. The ones that slays all the demons in a single shot.
In order to clean your git history, you will need a BFG of your own. BFG is a repo cleaner. It crawls the whole history of your git repo and replaces secrets with *** REMOVED ***
and/or removes sensitive files as it rewrites the history of your repo.
I can't emphasize this enough. It rewrites the whole history of your repo.
Without getting too deep into the details of how BFG works (please check out their docs), this is a bird's eye view of how you would use it:
- fork your repo (trust me, you'll want to try a few passes at this before doing it for real)
- clone a bare version of your fork (using the
--mirror
flag when cloning) - run BFG on the bare repo
- clean git artifacts locally
- force push the rewritten repo
- run truffleHog/tartufo on the newly rewritten repo to validate all is clean
After trying this a few times (and maybe keeping a backup of your dirty repo), you can repeat these steps on the main repo with confidence.
Where should you store you secrets then?
That's the million dollar question, isn't it?
Well... anywhere else.
The Manual Method
This method is annoying but gets the job done... at least for local builds. You could simply save your secrets in a file (or files) and manually copy them where they need to be when you build your app.
For example, you might have something like this in Secrets.swift
:
You could hold a copy of the file in 1Password (or any other password manager). Or maybe you save it in a separate repository. In any case, you have added Secrets.swift
to your .gitignore
file to ensure you're not committing it accidentally, and every time you make a change to this file, you communicate this to your teammates and they manually have to update their local copy.
So what about CI? How do you get that file copied over when running builds on your favourite CI service?
The Automated Method
You need some level of automation.
In recent years, I've been taking inspiration of how fastlane has been handling code signing and I wrote my own gem that would handle this for my team. I tried to be really clever and named it sekrit. We were already heavily using Ruby in our build pipelines, so it was pretty natural to add this as a dependency.
sekrit
works by reading a configuration file containing where to save the files, and a list of all the paths to files that are sensitive:
The config file also contains a passphrase
parameter which is the environment variable it must use to encrypt and decrypt the contents of files.
You can then use the sekrit
cli commands to push and pull files as you need.
This command clones the github.com/bsarrazin/my-ios-project-secrets
repo in a temporary directory and decrypts all the files listed in the config file using the passphrase stored in the SEKRIT_PASSPHRASE_MY_IOS_PROJECT
environment variable. Then it copies the files to their destination under your project folder and deletes the temporary clone.
This command clones the github.com/bsarrazin/my-ios-project-secrets
repo in a temporary directory and copies the files from your project. It then encrypts the files using the passphrase stored in the SEKRIT_PASSPHRASE_MY_IOS_PROJECT
environment variable, commits them, and pushes them to the git_ref
branch.
With this setup, all you have to do is to set the environment variable in your CI service, and your build pipelines will have access to the secrets.
This simple tool packs a lot of flexibility. You may have noticed the --bundle_id
and the --git_ref
flags. It allows you to have multiple flavours of your app, with different bundle identifiers, using their own secrets. You could store all flavours' secrets under the same branch, or store each flavour's secrets under their own branch. You can even tag the secrets repo to ensure that v1.1 of the app uses v1.1 of the secrets, for example.
It's a great tool, one that I use daily, but it also comes with some caveats:
- You must remember to add the files listed in the configuration file to your
.gitignore
file to ensure you're not committing them accidentally. - Xcode must know about the existence of these files, so you must commit the reference to these files in your
.pbxproj
file in your main repo. - You will forget to
pull
before youpush
if you're using a complex configuration. It's not a big deal since you can simply revert that commit (or rebase and drop that commit, it's only git after all), but it could potentially create some serious issues if flavour A is using an API key that was meant for flavour B. For this reason, I normally keep the secrets repo as read only and only give write access when changes are needed.
Conclusion
At this time, most of the projects I work on are using fastlane as their build pipeline. This means there's already a Ruby runtime required and that makes the use of sekrit
trivial.
However, I have recently been given access to Xcode Cloud beta, which is pushing me to experiment with the idea of dropping fastlane
as a dependency.
Don't hesitate to reach out, tweet, or slack me. I'm curious to hear about how you're handling this problem.
Until then, tell someone you love them. Happy holidays. 🍾 🥂