iOSアプリのリポジトリ内の機密情報(API Key/Secrets etc.)のハードコーディングをなくす [Arkana]

# はじめに

iOSアプリにおいて、サードパーティSDKなどから提供されるAPI KeyやSecretsなど、機密情報をどのようにセキュアに管理するといいでしょうか。
単純に実装するとハードコーディングや、設定ファイルなどに記述し、リポジトリにpushするようになってしまうと思いますが、セキュリティの観点からはアプリと同じリポジトリにpushするのはよろしくありません。

例えばリポジトリに閲覧権限を持っている場合、API Keyを使ってAPIサービスの実行ができてしまったり、[CIツールなどでソースコードを外部からアクセスさせている場合、CIツールへ不正アクセスされ漏洩する可能性](https://about.mercari.com/press/news/articles/20210521_incident_report/)や、[ipaファイルからリバースエンジニアリングツールを使って文字列を抽出される可能性](https://nshipster.com/secrets/)もあります。
そのため、例えGitHubのプライベートリポジトリだったとしても、機密情報の扱い方を考える必要性があります。

:::note warn
注意
この手法は、あくまで難読化やリポジトリから除外することで、漏洩リスクを下げる方法で、完全にセキュアというわけではありません。
完全にセキュアにするためには、そもそもアプリには埋め込まないことです。
:::

# cocoapods-keys

これまでiOSアプリでは、[cocoapods-keys](https://github.com/orta/cocoapods-keys)が使われることが多かったと思います。

https://github.com/orta/cocoapods-keys

cocoapods-keysはCocoapodsのプラグインとしてGemで作られています。

しかしながら、昨今のiOSアプリではSwift Package Managerの登場により、CocoaPodsからなるべく依存を減らそうという流れが強くなっています。

# Arkana

そこで、2022/6に新たな代替えライブラリとして「[Arkana(アルカナ)](https://github.com/rogerluan/arkana)」が登場しています。

https://github.com/rogerluan/arkana

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92539/1d19121c-3fb6-4548-f050-723726baba4c.png)

## Arkanaの特徴

- Gem
- Swift Package Manager/CocoaPods対応
- `.env`ファイルからSwiftコード自動生成
- 将来的にAndroidも対応予定?(2022/7時点ではまだAndroid未対応)

## 使い方

### インストール

通常のGemインストールになります。READMEにも[bundler](https://bundler.io/)を使うことが推奨されていますので、以降もbundlerを使用した前提で記載します。

```ruby:Gemfile
gem 'arkana', '~> 1.1.1'
```

```sh
$ bundle install
```

ちなみに[Rubyは2.7.0以上が対応](https://github.com/rogerluan/arkana/blob/main/arkana.gemspec#L13)となっています。

### `.arkana.yml`作成

次に、`.arkana.yml`を作成します。[template.yml](https://github.com/rogerluan/arkana/blob/main/template.yml)が用意されていますので、それに従ってルートディレクトリに配置します。

日本語訳を載せておきます。

```yml
import_name: 'ArkanaKeys' # オプション。インポートするフレームワークの名前。デフォルトはArkanaKeys。
namespace: 'Keys' # オプション。コードベース内のシークレットにアクセスするために使用する名前空間。デフォルトは ArkanaKeys です。
pod_name: 'ArkanaKeys' # オプション。Podfileで宣言するPodの名前。デフォルトはArkanaKeysです。
result_path: 'dependencies' # オプション。この設定ファイルのパスに関連する、生成されたコードのデスティネーションパス。デフォルトはArkanaKeys。
flavors: # オプション。 Flavorsは、環境変数から読み込む際に、すべてのシークレットに接頭辞として追加されるキーワードです。これは、例えばホワイトレーベルのプロジェクトで便利です。詳しくはREADMEの「使い方」のセクションをご覧ください。
  - FrostedFlakes
  - FrootLoops
swift_declaration_strategy: let # オプション。lazy var, var, letから選ぶ。デフォルトはlet。
should_generate_unit_tests: true # オプション。true, falseから選ぶ。デフォルトはtrue。
package_manager: cocoapods # オプション。spm, cocoapodsから選ぶ. 両方使う場合は、cocoapodsを宣言してください。デフォルトはspmです。
environments: # オプション。環境間で異なるキーがある場合 (例: debug/staging/prod) にsecret keyを生成するために使用される環境のリストです。デフォルトは空です。
  - Debug
  - Release
global_secrets: # オプション。どの環境を構築するかに関係なく同じであるsecretのリスト。デフォルトは空です。
  - MySecretAPIKey
  - AppStoreAppID
  - BackendDomain
environment_secrets: # オプション。このリストの各エントリに対して、envの名前に対応するサフィックスを付加して1つのsecretを作成します。デフォルトは空です。
  - MyServiceAPIKey # MyServiceAPIKeyDebug と MyServiceAPIKeyRelease の環境変数を検索します (フレーバーが宣言されていない場合)。
```

`import`の名前なども細かく指定できるのは面白いなと思います。とはいえ特にこだわりがなければ、`global_secrets`だけ設定でも十分だと思いました。
`environments`や`environment_secrets`でうまく分けられればそれはそれでいいと思いますが、サービスによって2種類があるものと、3種類以上の環境で分けているものなどバラバラになる可能性があると思いますので、それにより冗長な設定をするよりはシンプルに`global_secrets`だけを使った方がいいかもしれません。

### `.env`作成

次に、`.env`ファイルを作成します。

```:.env
MySecretAPIKey=hogehoge
MySecretAPIKey2=hogehogehoge
```

`.env`ファイルは、`key=value`の形式でテキストファイルで作成します。
この`.env`ファイルをアプリのリポジトリとは別に管理する必要があります。もし社内ホスティングのgitサーバーがあれば、そちらに保管すると良いでしょう。

### コード生成

コードの生成は`arkana`コマンドを使用します。

```sh
$ bundle exec arkana -c /path/to/your/.arkana.yml -e /path/to/your/.env
```

`-c`は`.arkana.yml`のpath、`-e`は`.env`のpathで、省略すると、それぞれルートディレクトリから探されます。

実行すると、Swiftコードが生成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92539/d4700b37-e1c2-06dc-f0a1-088ed64d0db1.png)

keyはバイトコードに難読化されたコードで生成され、実行の度に変更されます。

### Package追加

`package_manager`をデフォルトのspmにしていれば、Swift Packageが同ディレクトリに生成されますので、アプリにlocal packageとして追加します。
`ArkanaKeys`と`ArkanaKeysInterfaces`の2つのPackageが生成されますが、アプリからは`ArkanaKeys`だけを参照として追加すればOKです。

`Xcode > File > Add Packages...`
![Pasted_Image_2022_07_25_21_48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92539/aefa8688-4381-19cc-cb8e-faf2a6143b89.png)

### Swiftコードからのアクセス

Swiftコードでアクセスするには、`global_secrets`を設定すると`Global()`でstructを生成し、key名でアクセスできます。

```swift
import ArkanaKeys

let mySecretAPIKey = ArkanaKeys.Global().mySecretAPIKey
```

## git監視対象から除外

関連ファイルは`.gitignore`に記載して、git監視対象から除外しましょう。

```sh:.gitignore
.env
ArkanaKeys/*
```

## Makefileでコマンドを登録

チーム開発する場合は、`.env`ファイルとArkanaの実行を自動化すると良いでしょう。
Makefileを作り、以下のようなコマンドを登録しておくと良いと思います。

```makefile:Makefile
# make env
# - 社内ホスティングサーバーから`.env`ファイルをcloneする
# - `.env`ファイルをルートディレクトリにコピー
# - cloneしたディレクトリを削除
env:
	git clone git@my-internalserver.example.com/my-secrets.git
	cp ./my-secrets/.env ./.env
	rm -rf ./my-secrets

# make secrets
# - arkana実行
secrets:
	bundle exec arkana -c ./.arkana.yml -e ./.env

```

## Bitrise上で`.env`を生成

次に、CIサービスにBitriseを使用している場合に、`.env`を生成します。GitHub Actionsでも同様の設定でできると思います。

BitriseのWorkflowには[Secrets](https://devcenter.bitrise.io/ja/builds/secrets.html)が登録できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92539/9250b337-66e2-49d3-7bf5-2f826877f722.png)

ここに、base64でエンコードした`.env`ファイルの中身を設定します。

```sh
$ base64 .env | pbcopy
```

`pbcopy`でクリップボードにコピーできますので、ここでは`ENV_FILE`とkey名を設定します。
次に、

```sh
$ echo $ENV_FILE | base64 -d > .env
```

のscriptで環境変数からbase64でデコードして、`.env`ファイルを生成して書き込みます。

```yml:bitrise.yml
- script@1:
    title: generate .env file
    inputs:
    - content: |-
      #!/usr/bin/env bash
      # fail if any commands fails
      set -e
      # debug log
      set -x
      
      echo $ENV_FILE | base64 -d > .env
```

このscriptステップを`arkana`コマンド実行前に追加すれば、CI上でもビルドすることができます。

# さいごに

今回はiOSアプリにおける機密情報の取り扱いと、新しいライブラリArkanaをご紹介させて頂きました。
お役に立てば幸いです。