この記事は,フラー株式会社 Advent Calendar 2021 の 18 日目の記事です.
17 日目の記事は @kousuke_sumiyasu さんで「 CoreMLとCreateMLを使って犬の分類器を作ってみた! 」でした.
CoreML と CreateML ,結局リリースされてから一度も触れていないんですよね.ちなみに再学習とかってできるんでしょうか?わたし,気になります.
MobileNetV2.mlmodel を見て MobileNetV2 の論文を読みながら電車に乗っていた記憶が蘇りました.
はじめに
みなさんは, Xcode プロジェクトで例えば iOS アプリ開発をしているときに API キー等などの秘密情報を扱う機会はありませんか?
私はよくあります.例えば GoogleMap の API キーとか Twitter の API キーとかシークレットとか GitHub のアクセストークンとか etc...
上記に上げたものであれば基本的に Info.plist に差し込むなどはない実装になるかと思います.
もしくはそもそも上記で挙げた例だと通信時にパケットキャプチャされてしまえば見られてしまう情報だからハードコードしてしまうなどあるかとも思います.
しかし,たまたまどうしても業務でどうしても絶対にプライベートであっても GitHub のリポジトリやプロジェクトに関係ない人には知られてはいけないと言われてしまった文書を組み込まなくてはならなくなった場合どうしたらよいかということで今回考えてみた対応策です.*1 *2
開発環境
- Xcode12 以上
- Swift
ソースコードに埋め込めるようにするには
まず,今回のようなパターンで Swift や Objective-C のソースコードに乗せるためには cocoapods-keys
というプラグインがあります.
ただ CocoaPods 依存ではないプロジェクトには新しく CocoaPods を依存させる必要があります.
gem "cocoapods-keys"
私は RubyGems を入れるときは bundler を使うのでこれを Gemfile
に追加します.
今回は本題とそれるので参考記事だけで実際の実装は省略します.
Swift のソースコードなどには簡単に埋め込むことができます.データ自体は Base64 でエンコードされた String
で定義されていたはずです.*3
本題
さて本題です.ハードコードができずに環境変数として Xcode プロジェクトに追加するのはどうしたらよいでしょう.
答えは,前章で書いた cocoapods-keys
の仕組みから得られました.
みんな大好きキーチェーンを使った仕組みです.
キーチェーン
macOS にも iOS にも iPadOS にもキーチェーンってありますよね.あれの macOS 版を使います.
1. 追加
$ security add-generic-password -a ${ACCOUNT_NAME} -w ${PASSWORD} -s ${SERVICE_NAME}
- サンプル
$ security add-generic-password -a NO_HARD_CODE -w very-totemo-secret-value -s App-xcodeproj
very-totemo-secret-value
というハードコードが禁止されたとても大事な値をキーチェーンに保存してみました.
という感じでキーチェーンに保存されていることが確認できました.
2. 取得
$ security find-generic-password -a ${ACCOUNT_NAME} -s ${SERVICE_NAME} -w
- サンプル
$ security find-generic-password -a NO_HARD_CODE -s App-xcodeproj -w
とすることで very-totemo-secret-value
が標準出力で得られます.
今回は,2パターンの方法を紹介します.
PlistBuddy を使うパターン
Info.plist
を代表とする Property List ファイルの操作を可能とするコマンドラインツールです.
macOS を使っているということは新しく何かをインストールする必要なく使うことが可能です!
/usr/libexec/PlistBuddy
で設置されています.
例えば BundleID を取得しようとした場合は
$ /usr/libexec/PlistBuddy -c "Print:CFBundleIdentifier" /path/to/Info.plist
とすることで標準出力が得られます.
組み合わせて Info.plist に打ち込む
1. 下準備
今回は,サンプルとして iOS プロジェクトを作成しました.
また,デモで作成しているプロジェクトなので Info.plist の LSEnvironment
に差し込んでみようと思います.
こんな感じで追加しました.
こうすることでプログラムから Info.plist に書き込めているかの確認ができますね.
func logNoHardCodeValue() { guard let environment = Bundle.main.object(forInfoDictionaryKey: "LSEnvironment") as? [String: String], let noHardCode = environment["NO_HARD_CODE"], !noHardCode.isEmpty else { print("empty") return } print(noHardCode) }
取得できたら very-totemo-secret-value
がコンソール上に表示されるはずです!
2. Info.plist の書き換え
PlistBuddy で Info.plist 書き換えします!
となると実態が書き換わってしまうので普通にハードコードになってしまいます.
なので Xcode の Preprocess Info.plist File
というビルド設定を有効にします.
App.xcodeproj
である場合
TARGETS
> App
> Build Settings
> INFOPLIST_PREPROCESS
の値を NO から YES に変更します.
もちろん, Configuration Settings File ( xcconfig
)で置き換えしまっても大丈夫です.
こうすると, Copy Bundle Resources
の Phase ぐらいのときにいい感じに ${TEMP_DIR}/Preprocessed-Info.plist
がバンドルされ Info.plist が置き換わる仕組みになっているらしいです.
なので Copy Bundle Resources
より前に Run Script Phase で PlistBuddy を実行して置き換えをしてあげる必要があります.
export NO_HARD_CODE="$(security find-generic-password -a NO_HARD_CODE -s App-xcodeproj -w)" /usr/libexec/PlistBuddy -c "Set:LSEnvironment:NO_HARD_CODE ${NO_HARD_CODE}" "${TEMP_DIR}/Preprocessed-Info.plist"
スクリプト自体はこんな感じにしました!
しかしながら, Xcode10 から New Build System の恩恵のおかげで今のところ値が変わらないことがあります.
なので Input Files
に $(TEMP_DIR)/Preprocessed-Info.plist
を追加する必要があります.
これで実行してあげると
うまくいきました\( ‘ω’)/
Info.plist の方にも反映書き換えた値は反映されずにバンドルのみ含まれています.
XcodeGen を使う
いろんなところで名前を聞いたことがある人はいるかと思いますが,一応紹介だけ書いておくと,
Xcode プロジェクトの人間が読むものじゃない代表である *.xcodeproj/project.pbxproj
を含む *.xcodeproj
を生成するコマンドラインツールです.
導入コストはちょっと高いですが, *.xcodeproj/project.pbxproj
のコンフリクトを解消するコストを考えると導入したほうがよいのではという感じで導入する機会が個人的には多いです.
また, iOSDC Japan 2021 では SwiftPM によって脱 XcodeGen という話も取り上げられました.
1. project.yml
を書いて
はい.頑張って project.yml
を書いてください.
2. 環境変数を組み込む
XcodeGen には xcodegen
コマンドを実行されたタイミングで環境変数を読み込んで *.xcodeproj/project.pbxproj
に組み込ませる機能(順序は逆だと思う)が組み込まれています.( v2.25.0 現在)
You can also use environment variables in your configuration file, by using ${SOME_VARIABLE} in a string.
引用 : https://github.com/yonaskolb/XcodeGen/blob/2.25.0/Docs/ProjectSpec.md#general
targets: App: type: application platform: iOS sources: - path: App name: App settings: base: NO_HARD_CODE: ${NO_HARD_CODE}
こうしておくことで,
$ export NO_HARD_CODE=$(security find-generic-password -a NO_HARD_CODE -s App-xcodeproj -w) $ xcodegen
とすると前節で紹介した PlistBuddy と同じ結果が得られるようになります.
もちろんここでも Info.plist に同様の追加が必要です.
おまけ
macOS のキーチェーンとの対話に security
コマンドを使いましたが,途中で使用した cocoapods-keys
でも同様な処理が可能になっています.
また,せっかく AEXML
の不具合に PR 出してコントリビュート *4 して tuist/XcodeProj
-> yonaskolb/XcodeGen
と PR 出して XcodeGen v2.25.0 で対応までこぎつけたので Scheme の pre-action で環境変数読み込ませられないかなって格闘してみたのですがちょっと時間的な制限的にもできなかったので機会があったら試してみようと思います.
最後に
今回は,偉い人から強い力(圧力ではない)によって絶対にハードコードをしてはいけない値を2パターンの実装方法を使って Info.plist に差し込む実装をしてみました.
実際には2パターンとも実際には Info.plist には値は差し込まない絶対にハードコードをしない実装を実現することができました.
しかしながら,どちらともやっぱりビルドされた ipa
ファイルの中にある Info.plist を見てみると very-totemo-secret-value
が見えてしまうオチがあります.
お後がよろしいようで
明日は id:namaninotiteti1026 さんで「『コーディングしない』という不安に対してマネージャーはどう向き合うか」です!お楽しみに〜ノシ
参考
*1:どんな情報だよそんなのとか野暮なことは聞かないでください
*2:どっちしてもバイナリを解剖したりリバースエンジニアリングしたらわかるやん.みたいなやつはなしでお願いします!
*3:もう覚えてない