Managing secrets effortlessly and securely in Fastlane

Mar 28, 2023

Tómas Pálsson

So, you've embarked on the exciting journey of CI/CD with Fastlane? As you navigate the labyrinth of app development, you'll find yourself juggling secrets like passwords and API keys. But watch your step, dear friends, for a data leak is as treacherous as accidentally stepping on a LEGO piece in the dark!

Let us turn back the clock to a cautionary tale of a developer who, through a bug in the Github extension for Visual Studio, inadvertently made their private repo public. Unfortunately this exposed their AWS credentials stored in the repo, and before they knew it, a group of malicious Bitcoin miners took advantage, spending a staggering $6,500 from the developer's account. Here's a Wayback machine link to the article.

Hiding your secrets

To shield our valuable secrets from the prying eyes of digital desperados, we'll be saddling up with Parameter store from AWS SSM as our trusty sidekick. Parameter Store not only conceals our secrets but also helps us adhere to the squeaky-clean config system inspired by the infamous 12-factor app guideline. To ensure your secrets are safely hidden, always keep in mind this valuable litmus test from their site:

A litmus test for whether an app has all config correctly factored out of the code is whether the codebase could be made open source at any moment, without compromising any credentials.

Additionally, avoid clustering variables under "environments" like development, test, and production. Instead, keep each variable separate and independent of others.

With these golden nuggets of wisdom, we're ready to roll!

The basics

By default, Fastlane incorporates dotenv, allowing us to seamlessly integrate variables into our lanes. We'll be adding a three-tier system of .env files:

  • .env.aws: This stores our AWS credentials which should NEVER be checked into version control.  On my computer I have this stored under ~/.fastlane/.env.aws and have it loaded in a before_all lane within Fastlane. Just a friendly reminder that these values are required by the SSM SDK so they can't be fetched from there. 😉

  • .env.default: Houses variables common to all deployments, such as our Apple ID, match password or Slack error webhook.

  • .env.development and .env.production: Contains variables specific to each deployment, like API url, App ID or XCode target name.

When running a lane with Fastlane you can specify which environment to run it with via the --env argument:

You can then access the values of these variables within the Fastfile via the global ENV object:

Now, we still haven't addressed fetching them from SSM, but we've already spotted another pesky problem. It's all too easy to lose track of these variables. I bet you didn't even notice that SENTRY_RELEASE was misspelled as SENTRY_RELEESE in one place!

I've lost count of the times when a lack of type-safety led to a build spending 15+ minutes on iOS compilation, only for some tasks to be silently skipped because a value was empty.

We can see it in action by running the following lane:

Let's fix this mess!

To keep track of which variables should be fetched we simply prefix them with ssm:/ followed by the name of the parameter within SSM. This enables us to safely commit our .env.development and .env.production into version control without risking anything!

We can then have each variable as a getter class method on a class, Stack::Env, fulfilling our type-safety requirement. Each getter can then call a central get_env method which does the actual fetching from SSM if the variable value starts with the SSM prefix. 

By adding a pinch of caching (no one likes fetching the same parameter twice) and a dash of error handling (to keep those pesky nil values at bay), we arrive at the following implementation:

The only modification we need in our Fastfile is a small require_relative ‘env’ at the top to import our Stack::Env class, so our Fastfile looks like this:

We even get inspections catching our typos when using a compatible IDE:

Now, let's see the results by running our test lane:

Conclusion

We've successfully crafted a user-friendly, maintainable, type-safe, and secure approach for handling secrets within Fastlane (and, indeed, within Ruby in general, if necessary). One of the most significant advantages is that we can commit nearly all our .env files to version control, as long as the content solely references SSM variables. Adding more variables is a breeze since it only requires updating the appropriate .env file(s) and incorporating them into the Stack::Env class. Over time, the class may become cluttered with too many variables, but fret not! It can be divided into smaller classes, each housing a distinct set of variables.

References

Checkout the full example on GitHub.