How to Create a Seamless TestFlight Integration
All companies struggle with a way to distribute its products to clients. Distribution has to be safe, fast and reliable. It applies to software companies too— we need a platform to provide our digital/software products to clients. Some time ago on our blog we’ve described how we use our in-house app called Shuttle. It allows to provide builds on iOS and Android, search through previously published builds and incorporate both types of users (clients and developers) thanks to different possible access levels.
In today’s blog post we will discuss a solution for the automatic distribution of new versions of an app to a client for iOS developers through TestFlight—a platform recommended by Apple.
Our take on TestFlight
We wouldn’t be ourselves if we didn’t add some twist to the TestFlight tool. In Polidea we are all about making our work easier. The first time we had to set up TestFlight beta tests we started figuring out how to make it more automatic. In the ideal world it would be easy to maintain. Once a developer sets up a whole process he will not have to remember about having TestFlight in his build pipeline at all! That led us to think about more requirements we would like to meet. TestFlight app has to be connected to Apple Developer Program, preferably client’s program so he could manage a number of testers, date of the test releases and of course when an app is ready and can go live. That means we would have to manage client’s certificates and credentials aka the sensitive data. But how to do it safely and give the control to the client?
We came to the following conclusion. The main idea is to leave control and responsibility for protecting client’s resources on the client’s side. That is why we create new AppleID and connect it to the client’s Apple Developer Program (they can remove an account from the program at any time by denying access to resources). A client creates an account with access to the certificates repository with a setup SSH connection to Polidea (client can remove SSH connection denying us access to the repository). That leave us with hosting of certificates by the client (he/she is responsible for who has access to the repository with certificates).
But… but, how?
How to do it? First of all there are some requirements you need to have set up on the computer.
- Admin account for gitlab repository containing project
We will focus on using Gitlab and Gitlab CI only but these setup could be reproduced on any other hosting service with small modifications. First things first, we need to prepare some basics before we trouble a client with some actions on his or her side.
Generate ssh keys.
- Add a private key as a secret variable under SSH_PRIVATE_KEY name in the project’s gitlab repository.
Create a new mailing group #PRODUCTfirstname.lastname@example.org marked as a TEAM and connect all the accounts of project members with an enabled option “Allow to post emails from Internet”. This will allow everyone to respond when something happens and a person setting TestFlight would be on vacation.
Create AppleID for the created group account and save credentials in such a way they are shared securely.
Now we can ask our client to set everything up on his side.
- Ask the client to create a repository for certificates (for example “iOS-certificates”).
- Ask the client to create new gitlab account which will have read & write access level to previously created repository.
- Ask the client to add created in the the 1st step public ssh key to the newly created gitlab account.
- Ask the client to add the created in 3rd step AppleID to client’s Apple Developer Program.
- Ask the client to create new AppID (for example #PRODUCT_APPID#) on the Apple Developer Platform.
Now if everything goes well we should have access to the repository with certificates and our AppleID should be properly connected with the client’s Apple Developer Program. But as you can see the repository is empty. This is a step where fastlane and match come in handy. Right now only the client has the distribution certificate and private key stored in keychain. Because of that he has to generate certificates in the previously created repository.
fastlane match appstore --git_url "email@example.com:#PRODUCT#/iOS-certificates.git" --app_identifier "#PRODUCT_APPID#" --readonly false
Remember to save passphrase which was provided to fastlane while this command executes. Passphrase is the only password to decrypt those files. Now the script will create proper provisioning profile and distribution certificate on Apple Developer Program. It will allow us to upload builds signed with those certificates to the AppStoreConnect. Next it will save all needed files under “git_url” parameter indicated repository.
Consider the risks
Some would say it is dangerous to reside all keys, certificates and provisioning profiles in the repository and they are right. That is why we suggest using a private repo also notice that Match additionally encrypt files with SHA-256. At the end of the day remember that an attacker who has these files probably won’t do much harm. All certificates can be revoked, denying attacker from further exploit of client’s resource. Answers to more detailed questions can be found in the match manual in the section “Is this secure?”.
Wrap it up
At this point all the steps that needed to be done by the client are done. You should be able to have access through Apple Developer Platform to all the Provisioning Profiles.
- Add passphrase as a secret variable under MATCH_PASSWORD name on the project’s gitlab repository.
Note: Secret variables are injected to the Gitlab CI runners during build time. It is common to store in them private keys and passwords. Thanks to that in fastfile you can call just an environment parameter and there is no need to store password within code in repository.
- Add password to #PRODUCTfirstname.lastname@example.org AppleID as a secret variable under FASTLANE_PASSWORD so Match could login to iTunesConnect with proper account which is able to push new builds.
Create new target in your project (for example #PRODUCT#Prod).
- Set there a proper appID (“#PRODUCT_APPID#),
- Download corresponding Provisioning Profile and set it in a newly created target.
Regarding step 12, as well as a new target you can create a new scheme that would be build on CI preferably for you project management strategy. The only thing left is to set up the pipeline to automatically build new versions and upload to TestFlight. Thanks to the fastlane it’s super easy. First, we need to match certificates from the client’s repository to our account with access to the client’s Apple Developer Program.
Add fastlane match in Fastfile to match against client’s repo with certificates.
def match_#PRODUCT# ENV["MATCH_PASSWORD"] = ENV["#PRODUCT#_MATCH_PASSWORD"] match( type: "appstore", git_url: "ssh://email@example.com/#PRODUCT#/iOS-certificates.git", app_identifier: "#PRODUCT_APPID#", username: ".#PRODUCTfirstname.lastname@example.org", readonly: true ) end
Add a new lane to create TestFlight build. It needs to build the app in a newly created target so the provisioning profiles from the previous step will match. Secondly, we create fastlane’s testflight action to upload the app to TestFlight.
desc "Deploy Release via TestFlight" lane :deployToTestFlight do match_#PRODUCT# gym( scheme: "#PRODUCT#Prod", include_symbols: false, xcargs: "DEBUG_INFORMATION_FORMAT=dwarf-with-dsym ) testflight( username: "#PRODUCTemail@example.com", app_identifier: "#PRODUCT_APPID#" ) end
The last step is to provide gitlab instructions on when and how to deploy the app to TestFlight. At Polidea we publish builds per tag. This is how we will do it here. Also you must remember that the certificates are in the repository hosted by the client so CI’s machine has to know proper SSH key to connect with it.
Add new stage in .gitlab-ci.yml. before the script SSHPRIVATEKEY is added to machine so it could connect to repository with certificates via client’s account.
testflight: stage: deploy before_script: - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" >~/.ssh/#PRODUCT#_key && chmod 600 ~/.ssh/#PRODUCT#_key - ssh-add ~/.ssh/#PRODUCT#_key - ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts script: - bundle exec fastlane deployToTestFlight only: - tags
And Voila! That’s all! Next time you set a tag, a new version will be uploaded to TestFlight. The last thing is to log in to App Store Connect and publish it. The best thing about this solution is that since it is already the client’s Apple Developer Program he can autonomously decide which build provide to testers or publish for himself to check.
Where to search for more?
Fastlane is a complicated tool with many options. For more advanced usage go here: