Fastlaneを使ったiOSの証明書／プロファイル管理

![Fastlaneを使ってiOSの証明書を管理する.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110131/bd91553e-cba5-de72-7df7-632fade5719d.png)

# 前置き
iOSアプリの複数人開発で一番厄介なのが「証明書」周りですよね。
新しい開発メンバーがJoinしてくると、最小限でも以下の作業が発生してきます。

* AppleDeveloperアカウントの新規作成
* 新メンバーをDeveloperPortalへ招待
* 証明書・プロビジョニングファイルの作成・更新

基本的には、AutomaticSigningによって自動的に証明書・プロビジョニングファイル周りが設定されますが、厳密なプロジェクトだと証明書を新規作成してはならない。などの制約がある場合があります。

また、CI/CDを運用している場合、どうやってCI上で証明書・プロビジョニングファイルを扱うかといった問題も発生してきます。

そこで、今回は `fastlane` の `match` を使って証明書を管理するようにして、簡単に証明書・プロビジョニングファイルの共有・更新を行えるようにします。

# Fastlane
FastlaneとはiOS/Androidで必要な様々な作業を自動化してくれる便利ツールです。

https://fastlane.tools/

* ipa/apkファイルの作成
* AppStoreConnect/GooglePlayへアプリのアップロード
* AppStoreConnect/GooglePlayへのメタデータ登録・更新
* テスト実行
* iOSの証明書・プロビジョニングファイル管理
* AppDistribution/TestFlightなどへのアップロード

# Fastlane match
Fastlaneのmatchとは、開発チームで共有の証明書・プロビジョニングファイル作成や更新を行い、プライベートリポジトリに格納して、全メンバーで共有できるようにするものです。

メンバーが追加されたら、fastlane matchを使って、管理されている証明書・プロビジョニングファイルを自動で登録・更新してくれるようになるため、AppleDeveloperアカウントを作成したりなどの、面倒な作業をする必要がなくなります。

ざっとしたフローは以下のような感じになります。

![fastlane_match.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110131/aa94f815-eb31-c1ad-ee4c-1e188cd8f119.png)

:::note info
フロー図では、makeコマンドを実行していますが、この記事ではfastlaneコマンド実行用のmakeファイルを作成しているので、記事に合わせたコマンドとなっています。
:::

# 導入手順
fastlane導入には、homebrewやrubyGemを使う方法など、いくつかありますが、今回はrubyGemを利用する方法とします。

また、各バージョン管理には `asdf` を利用していますので、各環境に合わせて読み替えください🙇‍♀

:::note info
個人的には asdf のみで様々なツールのVersion管理ができるので、この際に入れてみるのも良いかと思います。
:::

https://asdf-vm.com/

## 証明書管理用のリポジトリ作成
fastlaneでは証明書をリポジトリで管理するため、Privateの専用リポジトリを先に準備しておく必要があります。

## Ruby, Bundlerの設定・インストール
rubyは `2.7.2` を使用します。

システムで利用するrubyのバージョンを変更する必要はないので、local（ワーキングディレクトリ直下のみ適用）で2.7.2を動かすようにします。
```
$ asdf install ruby 2.7.2
$ asdf local ruby 2.7.2
```

rubyの設定が終わったら、bundlerをインストールします。
```
$ gem install bundler
```

## fastlaneのインストール
fastlaneをrubyGem経由でインストールするため、Gemfileを作成します。以下の内容をコピペしてもらえば大丈夫です。

Gemfileは、`<project_name>.xcodeproj` があるディレクトリに作成してください。
```
source "https://rubygems.org"

gem "fastlane"

plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
```

Gemfileを作成したら、以下のコマンドを実行して、fastlaneをインストールします。
```
$ asdf exec bundle update
```

## fastlaneの初期設定
以下のコマンドでfastlaneを使用するための初期設定を行います。

コマンドを実行すると、どのようにfastlaneを初期設定するかの選択肢が表示されますので、「4」の手動設定を選択しておきます。
```
$ asdf exec bundle exec fastlane init
[✔] 🚀 
[✔] Looking for iOS and Android projects in current directory...
[02:25:58]: Created new folder './fastlane'.
[02:25:58]: Detected an iOS/macOS project in the current directory: '<project_name>.xcodeproj'
[02:25:58]: -----------------------------
[02:25:58]: --- Welcome to fastlane 🚀 ---
[02:25:58]: -----------------------------
[02:25:58]: fastlane can help you with all kinds of automation for your mobile app
[02:25:58]: We recommend automating one task first, and then gradually automating more over time
[02:25:58]: What would you like to use fastlane for?
1. 📸  Automate screenshots
2. 👩‍✈️  Automate beta distribution to TestFlight
3. 🚀  Automate App Store distribution
4. 🛠  Manual setup - manually setup your project to automate your tasks
? 4
```

## fastlane matchの初期設定
証明書管理はfastlaneのmatchを使って行うため、fastlaneの設定が終わったら、matchの初期設定を行います。

どの環境で証明書を管理するか聞かれるため、「1」のGitを選択します。
```
$ asdf exec bundle exec fastlane match init
[✔] 🚀 
[02:31:47]: fastlane match supports multiple storage modes, please select the one you want to use:
1. git
2. google_cloud
3. s3
4. gitlab_secure_files
?  1
```

選択すると、リポジトリのURLを聞かれるので、https or sshのコピペします。
```
[02:32:05]: Please create a new, private git repository to store the certificates and profiles there
[02:32:05]: URL of the Git Repo: <https or sshのリポジトリURL>

[02:36:51]: Successfully created './fastlane/Matchfile'. You can open the file using a code editor.
[02:36:51]: You can now run `fastlane match development`, `fastlane match adhoc`, `fastlane match enterprise` and `fastlane match appstore`
[02:36:51]: On the first run for each environment it will create the provisioning profiles and
[02:36:51]: certificates for you. From then on, it will automatically import the existing profiles.
[02:36:51]: For more information visit https://docs.fastlane.tools/actions/match/
```

## AppStoreConnect APIの設定
fastlaneのみで証明書・プロビジョニングファイルを作成・削除・更新ができるわけではありません。

AppStoreConnectのAPIを利用することで、TestFlight配信・ユーザーの追加などを行うことができます。

App Store Connect にアクセスし、「ユーザとアクセス」から「キー」タブをクリックします。

:::note warn
権限がない場合「キー」タブは表示されていません
:::

新しいAPIキーを生成する場合には、確認画面が表示されるので、内容を確認して「提出」ボタンをクリックすると、上記画面に遷移します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110131/7072731b-dad4-5be4-a6fc-675fcca90bdc.png)

「APIキーを生成」をクリックすると、以下のAPIキー登録画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110131/a8b2f427-9d2e-8bd3-bee8-a74d1918f139.png)

「名前」にはどこで利用されているAPIキーなのかなど、識別しやすい名前を入力します。

「アクセス」では、APIキーに付与する権限を設定します。fastlaneでは「App Manager」の権限を付与します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110131/8acfdd50-50de-9ae6-522e-ac64c7e99cda.png)

APIキーが生成されると、APIへ接続するために必要な情報が表示されます。APIキーをダウンロードをクリックして、キーをダウンロードします。

<div class="warn-box">
「APIキーをダウンロード」は1度しかできないため注意しましょう。
</div>

また、

* Issuer ID
* キーID

は、fastlaneの処理を記述する際に必要になるため、メモしておきます。

## envファイル作成の前準備
fastlaneでは、APIに接続するために必要な情報を変数に渡す必要があります。

各接続情報をハードコードしてしまうと、Githubに掲載されてしまうため、万が一のときにインシデントになってしまう場合があります。

そのため、接続情報などは全てenvファイルで管理するようにします。

先程ダウンロードした、キー情報もenvファイルに記述する必要がありますが、そのままの内容を記述するわけではなく、base64化したものをenvファイルへ記述します。

以下のコマンドを実行して、APIキー情報をbase64化します。出力された結果をメモしておきます。

```
$ cat AuthKey_KEYID.p8 | base64
```

## envファイルの作成
作成するファイルは2つです。

* .env
* .env.skel

`.env` は実際に値が入っているもの、`.env.skel` はどのようなキーを設定する必要があるのかのSkeletonファイルとなります。

ひとまず `.env.skel` を作成します。
```
### 以下を .env.skel にコピー
ASC_KEY_ID=
ASC_ISSUER_ID=
ASC_KEY_CONTENT=
MATCH_PASSWORD=
FASTLANE_USER=
```

そして `.env` ファイルも作成して、AppStoreConnect APIの情報（ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_CONTENT）を含む設定値を設定します。

`MATCH_PASSWORD` は証明書に設定するPasswordになりますので、各自任意で設定してもらって大丈夫です。

`FASTLANE_USER` はAppleIdになります。


また `.env` は Github で管理しませんが、`.env.skel` は管理したいため、以下の内容を `.gitignore` ファイルに追記します。
```
.env*
!.env.skel
```

## Fastfileの修正
fastlaneフォルダにある、Fastfileにfastlaneで実行したいlaneを記述します。

今回のFastfileでは、以下のことが実行できるようになっています。

|できること|実行コマンド|
|:-|:-|
|Development証明書・プロビジョニングファイルの作成・更新| make match_force_development |
|Development証明書・プロビジョニングファイルの取得| make fetch_development_profiles |
|Development証明書・プロビジョニングファイルの削除| make delete_development_profiles |
|Distribution証明書・プロビジョニングファイルの作成・更新| make match_force_appstore |
|Distribution証明書・プロビジョニングファイルの取得| make fetch_appstore_profiles |
|Distribution証明書・プロビジョニングファイルの削除| make delete_appstore_profiles |

以下をFastfileへコピペします。初期化後の既存で書かれている内容は削除して問題ありません。

* appstore_identifier（本番環境用AppID）
* dev_identifier（検証環境用AppID）

の2項目については、各自プロジェクトに合わせてください。

```
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
#     https://docs.fastlane.tools/plugins/available-plugins
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

platform :ios do
  def asc_api_key
    app_store_connect_api_key(
      key_id: ENV['ASC_KEY_ID'],
      issuer_id: ENV['ASC_ISSUER_ID'],
      key_content: ENV['ASC_KEY_CONTENT'],
      is_key_content_base64: true
    )
  end

  def appstore_identifier
    "your.appstore.identifier"
  end

  def dev_identifier
    "your.development.identifier"
  end

  def dev_identifiers
    [appstore_identifier, dev_identifier]
  end

  def appstore_identifiers
    [appstore_identifier]
  end

  desc "create development cert and profiles"
  lane :match_force_development do
    api_key = asc_api_key
    match(
      api_key: api_key,
      type: "development",
      app_identifier: dev_identifiers,
      force: true
    )
  end

  desc "create appstore cert and profiles"
  lane :match_force_appstore do
    api_key = asc_api_key
    match(
      api_key: api_key,
      type: "appstore",
      app_identifier: appstore_identifiers,
      force: true
    )
  end

  desc "fetch development profiles and cert"
  lane :fetch_development_profiles do
    api_key = asc_api_key
    match(
      api_key: api_key,
      type: "development",
      app_identifier: dev_identifiers,
      readonly: true
    )
  end

  desc "fetch appstore profiles and cert"
  lane :fetch_appstore_profiles do
    api_key = asc_api_key
    match(
      api_key: api_key,
      type: "appstore",
      app_identifier: appstore_identifiers,
      readonly: true
    )
  end

  desc "delete development profiles and cert"
  lane :delete_development_profiles do
    api_key = asc_api_key
    match_nuke(
      api_key: api_key,
      type: "development",
      app_identifier: dev_identifiers
    )
  end

  desc "delete appstore profiles and cert"
  lane :delete_appstore_profiles do
    api_key = asc_api_key
    match_nuke(
      api_key: api_key,
      type: "appstore",
      app_identifier: appstore_identifiers
    )
  end
end
```

## Makefileの作成
毎回fastlaneコマンドを打つのは面倒。かつ、様々な処理を実行した後にfastlaneを実行したい。という場合を想定して、Makefileを作成します。

MakefileはXcodeプロジェクトファイルと同じ階層に配置します。（Gemfileとかと同じ階層）

基本的には、makeコマンドを実行してもらうこととなります。

```
FASTLANE := bundle exec fastlane

match_force_development:
	$(FASTLANE) ios match_force_development

match_force_appstore:
	$(FASTLANE) ios match_force_appstore

fetch_development_profiles:
	$(FASTLANE) ios fetch_development_profiles

fetch_appstore_profiles:
	$(FASTLANE) ios fetch_appstore_profiles

delete_development_profiles:
	$(FASTLANE) ios delete_development_profiles

delete_appstore_profiles:
	$(FASTLANE) ios delete_appstore_profiles
```

# おわりに
以上で、iOSプロジェクトでのFastlane組み込みが終わりました。

全てのプロジェクトでFastlaneを導入して、ストレスフリーな開発環境を作りましょう！