Posts Use Fastlane for iOS without messing with your Enterprise certificates
Post
Cancel

Use Fastlane for iOS without messing with your Enterprise certificates

You can do it by yourself

Every project has this moment where you need to put it on your favorite CI/CD systems. In the case of mobile applications, it’s usually it’s done by DevOps engineer or maybe by some senior developer in your team.

As iOS developers, we know the pain of dealing with certificates, provisioning profiles, and other boring stuff related to Apple platforms. It’s especially tedious and not easy when you have to automate the process of building and signing the iOS app and then deliver it in the proper place.

There is a tool to help you out. It’s called Fastlane

It will save you lots of time and headaches with the mentioned problems, but you need to understand how it works and what are you trying to accomplish.

In this article, I’d like to focus on a particular case that is quite common in commercial projects. That is: delivering various builds of the same iOS app using different Enterprise certificates.

Example project: MyAwesomeApp

Let’s imagine that your project is named MyAwesomeApp. You have 3 different environments configured for your app. Such environments could be on Firebase, AWS, or maybe some custom back-end implementation:

  • Development (Dev)
  • Testing (QA)
  • Production (Prod)

To keep things organized and clean, you split your App into different bundle IDs like so:

  • com.mycompany.myawesomeapp.dev  -  for Dev
  • com.mycompany.myawesomeapp.qa  -  for QA
  • com.mycompany.myawesomeapp  -  for Prod

That split requires 3 different Enterprise provisioning profiles (because each provisioning profile is binded with 1 concrete app bundle ID).

1
2
3
4
5
6
7
8
9
10
11
12
13
Mobile app: My Awesome App

# development build
Bundle ID: com.mycompany.myawesomeapp.dev
Provisioning profile: My Awesome App DEV

# qa build
Bundle ID: com.mycompany.myawesomeapp.qa
Provisioning profile: My Awesome App QA

# production build
Bundle ID: com.mycompany.myawesomeapp
Provisioning profile: My Awesome App

Looks familiar, right? Let’s move on…

Main tool for the job: fastlane match

We’re interested in match — which is a part of fastlane’s toolkit. Match will handle all your stuff from the Apple Developer portal but there is a catch.

By default, match is destructive for your Apple Developer account which means, it’s eager to remove all certificates and provisioning profiles and replace them with versions created by match.

That maybe not the case in your company. Someone with Account Holder credentials created all the necessary stuff on the Apple Developer portal and such data should not be modified or removed.

Is there a way to tell fastlane to just import all data for codesign without touching anything on your portal? There is!

Import data from Apple Developer Portal

I assume that you prepared you private git repository just for fastlane match purposes. This is how you feed it with data from Apple Developer portal:

1
fastlane match import --username <your Apple ID> --git_url <your Git repo to store certificates and profiles> --team_id <ID of your team> --git_branch <your branch> --app_identifier <your bundle ID> --type enterprise

Let me explain part by part of that command:

  • --username  -  here you specify your Apple ID to allow match to login into Apple Developer portal and download certificates, profiles, etc. It’s will be used only once for this import

  • --git_url  -  this is URL to your Git repository, usually private repo on your GitHub account. That’s the place where your certs and profiles are stored and encrypted. Only Fastlane match can encrypt your data when it’s needed

  • --team_id  -  that’s the ID of your team on Apple Portal (the team that your Apple ID account is a member of)

  • --git_branch  -  this is a very important parameter in our scenario. I’ll explain why later but we will use the different branch for each of environments (bundle IDs)

  • --app_identifier  -  your bundle ID. As mentioned earlier, we will have 3 of them

  • --type enterprise - there are several types defined in Fastlane but all of our app variants are going to be distributed with Enterprise certificates

That’s being said, we’re going to call fastlane match import 3 times like this (yes, those “\” will work in your command line 😉):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fastlane match import \
    --username myaccount@company.com \
    --git_url https://my-secret-repo.com \
    --git_branch dev \
    --app_identifier com.mycompany.myawesomeapp.dev \
    --team_id TEAM1234 \
    --type enterprise

fastlane match import \
    --username myaccount@company.com \
    --git_url https://my-secret-repo.com \
    --git_branch qa \
    --app_identifier com.mycompany.myawesomeapp.qa \
    --team_id TEAM1234 \
    --type enterprise

fastlane match import \
    --username myaccount@company.com \
    --git_url https://my-secret-repo.com \
    --git_branch prod \
    --app_identifier com.mycompany.myawesomeapp \
    --team_id TEAM1234 \
    --type enterprise

That’s a lots so far. Read it slowly, enjoy your bevereage and let this knowledge sink in.

Certificate and private key for fastlane match

Every time you call Fastlane match import you will be asked to provide 3 additional things:

  • mandatory: your distribution certificate ( cer file )
  • mandatory: your distribution private key ( p12 file)
  • optional: your provisioning profile ( mobileprovision file)

fastlane is very eager to generate a new distribution certificate for you but as we mentioned earlier — we don’t want to let it generate anything on Apple Portal.

Note \ While you can download a distribution certificate from Apple Portal, you cannot download the private key from there. It’s up to your company how the distribution certificate is handled, just make sure you obtained both certificate and private key in your local machine in a known location.

Tip \ Put both cer and p12 files into the folder where you going to run match . That will save you lots of typing when match will ask for those files!

The last thing is a provisioning profile. Fastlane should be able to download right one for your bundle ID. However if you happen to have more that 1 Enterprise certificate - let’s say - for com.mycompany.myawesomeapp.dev then you should manually download correct provisioning profile from Apple Developer Portal and pass it to match

Last ingredient - provisioning profile

The last thing is the provisioning profile. fastlane should be able to download the right one for your bundle ID. However, if you happen to have more than 1 Enterprise certificate — let’s say — for com.mycompany.myawesomeapp.dev then you should manually download correct provisioning profile from Apple Developer Portal and pass it to match.

Secret repository for your secret data

When you successfully import all data into your git repository, you can git clone it (will be helpful later on). There should be following structure:

1
2
3
4
5
6
7
8
9
10
your-repo-name
├── README.md
├── certs
│ └── enterprise
│   ├── 12345ABCDE.cer
│   └── 12345ABCDE.p12
├── match_version.txt
  └── profiles
    └── enterprise
      └── Enterprise_com.mycompany.myawesomeapp.dev.mobileprovision

Tip \ You can switch to branches dev, prod and qa. The structure will be almost the same. You will see that every branch has the same cer and p12 files.

Unfortunately, that duplication of the same data every branch is a drawback of this method.

You see, officially Fastlane encourages you to use the different branch for different teams from your Apple Developer Portal, that means different distribution certificates and keys.

However this way you cannot import multiple Enterprise certificates because it expects to be only 1 of that kind per branch. Maybe this will change in the future releases of fastlane but the workaround is to use branching not for different teams but for different environments - like in our case: branches dev, prod, qa or whatever you need ( demo ? uat?).

Final workaround for fastlane

We almost finished, but we’re not done yet. If you run your fastlane (with a --readonly flag !) to get certificates and provisioning for one of your builds then fastlane match will fail with message similar to this one:

1
2
3
4
5
6
7
8
9
10
11
12
ERROR [2020-04-05 12:14:55.43]: No matching provisioning profiles found for 'InHouse_com.mycompany.myawesomeapp.qa'

ERROR [2020-04-05 12:14:55.43]: A new one cannot be created because you enabled `readonly`

ERROR [2020-04-05 12:14:55.43]: Provisioning profiles in your repo for type `enterprise`:

ERROR [2020-04-05 12:14:55.43]: - 'Enterprise_com.mycompany.myawesomeapp.qa '

ERROR [2020-04-05 12:14:55.43]: If you are certain that a profile should exist, double-check the recent changes to your match repository

WARN [2020-04-05 12:14:55.44]: Lane Context:
INFO [2020-04-05 12:14:55.44]: {:DEFAULT_PLATFORM=>:android, :PLATFORM_NAME=>:ios, :LANE_NAME=>"ios qa"}

At the moment of writing this article, there is an open issue on Fastlane (https://github.com/fastlane/fastlane/issues/16243).

Apparently, when fastlane match is importing provisioning profiles, it stores them under a different name than it is expected to be.

To fix that you need to manually check out your git repository and do the following commands:

1
2
3
4
5
cd profiles/enterprise
mv Enterprise_com.mycompany.myawesomeapp.qa InHouse_com.mycompany.myawesomeapp.qa
git add .
git commit
git push

Do this for every branch (yeah, I know… hope they will fix it) and now calling fastlane matchwith --readonly should work flawlessly.

It’s important to add --readonlyflag to make sure that fastlane is not trying to connect to Apple Developer Portal and mess something.

Now you can use it locally but most likely you went all through that to automate code signing on your CI and plug CD as well, don’t you? 😉

Integration with CI

Here is part of my Fastfiles that I use them locally or on the CI system (like Travis or Circle CI):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# this is needed just for CI, you don't need it localy
keychain_name = "ios-build-tmp-keychain"
keychain_password = "TmpPassword"
# this is needed just for CI, you don't need it localy
create_keychain(
    name: keychain_name,
    password: keychain_password,
    default_keychain: true,
    unlock: true,
    timeout: false
 )

match(
    type: "enterprise",
    app_identifier: "com.mycompany.myawesomeapp.qa",
    git_branch: "qa",
    readonly: true,
    verbose: true,
    keychain_name: keychain_name, # you can remove this and bottom parameter on your local machine
    keychain_password: keychain_password
 )

Summary

We’re giving up on some core features of fastlane, which is taking care of your certificates and provisioning profiles automatically. However, in many (larger) companies this cannot be done because there are designated rules, policies, and persons to manage such data.

And yet you would like to automate codesign on your CI machine and that may be the easiest way to do it.

An alternative solution is to use fastlane cert and fastlane sigh but you still need to let your CI know what is the private key for distribution certificate. By using fastlane match you don’t have to deal with that because it’s stored safely in your private repo, encrypted but ready to be downloaded and used.

I hope that would solve some of your “iOS-DevOps” issues that you may encounter while coding native or Flutter/Xamaring/ReactNative/whatever iOS apps.

Some steps are quite tedious like fixing the mentioned bug in fastlane but all this sorcery is going to happen only once or twice in your project and it’s really worth doing it.

As Lance Armstrong once said: Pain is temporary. Quitting lasts forever.

Extra resources

This post is licensed under CC BY 4.0 by the author.