Handling Secrets in iOS open source projects

Nuno Gonçalves
4 min readApr 10, 2018

--

Have you ever stored sensitive information in a remote version control repository? Sure you haven’t…

Just take a look at this link on GitHub

Does your app contain any form of secret you wouldn’t like your mom to see? So why do you put it in the web for everybody’s delight? :)

This post is not about telling you not to put your credentials on source control (but you shouldn’t) but rather, how you accomplish that on Xcode projects.

For the sake of brevity, let’s just assume you’re using GitHub from here on,

Let’s say you have a file with app/api/provider credentials … while all your development stays in your local machine, keeping the keys out of the internet’s dirty hands is a fairly easy task.

You can simply place the file outside of the project and reference it in your Xcode project. Or you can simply add it to your .gitignore file and move on. This way it will be available for your builds but not for your mom to see from another computer.

Now this task gets a bit more complicated when you start adding CI into the mix. You now want your CI machine to have access to these credentials, and at the same time keeping them out of your GitHub repo. So how can we do this?

My approach on this is rather simple, and it uses a tool that all macs have installed by default, and probably so do all CI machines (at least the ones which support Xcode projects). Plain old Ruby.

The idea is to have a script loading ENV variables, and feeding them into the Xcode project. Since we can’t accomplish that from Xcode itself, we need to fallback into manual scripts.

Let’s start:

Imagine you have this simple file with your credentials. This is something we could very well have on our regular projects.

Start by creating a shell file with your credentials. Now add it to .gitignore along with the previous swift file. The shell should look like this:

We could also simply add the credentials to our .bash-profile file, but for some reason that I’m not aware Xcode won’t load them. Maybe as a security measure?

Now add the following ruby script to your project (wherever you feel it’s best).

The contents of the file is surrounded by << - CREDS_FILE_STRING … CREDS_FILE_STRING that’s ruby syntax for multi line string.This is where you should update the code to fit your needs. Just remember, the ENV keys must match the ones on your env-vars.sh file.

Now simply add the following run script to Build Phases:

This script will attempt to run env-vars.sh file which loads our credentials. This file exists in our local machine, but not on the CI machine. There, they are set in configurations and available directly. Then the ruby script does the rest. You need to put the right path to both files.

That’s it. Let me just explain what we did.

We are creating a build phase that must precede “Compile Sources” so your project will compile without problems. This includes a script that creates a Swift file (with credentials populated), which you’d normally do manually, and update the env vars from your shell file.

When the compiler runs, the file is already present and finishes his job normally.

Whenever we commit to the remote repo, these files won’t be there because they are ignored, thus not exposing our precious credentials.

For the final step, you need to add the ENV variables to your CI provider. For Travis you have a settings area where you can do so. Your script will pick up the ENV vars, create the file and everything should work properly. Don’t forget that the keys must match!!!

Hopefully this guide will make you feel more comfortable when adding secrets to your project, and not add them to GitHub by default. That’s really a bad practice that should be avoided even from private repositories.

UPDATE: For Xcode 10 and the new build system you need to take a few extra things into consideration. I’m not going to explain it all here, but rather, point to this post that explains what changed.

You now need to add the ruby script file to the input files list and the generated swift file to output files. If you don’t, since the new build system might be running scripts in paralell, it might starting compiling without the swift file created, which in turn will result in a compilation error.

Thanks to Tim Roesner to pointing that out.

Thanks Robin Malhotra for inspiring me in this solution. He used stencil and sourcery, which I though was too much as dependencies, and thought it might be useful to have a slightly simpler approach for somebody else too.

Let me know what you think about it in the comments bellow or https://twitter.com/goncalvescmnuno

--

--