Xamarin プロジェクトにおける API key など秘密情報管理のベスト?プラクティス

Xamarin Advent Calendar 2016 19日目です。 API key や SecretKey はハードコードダメ、git リポジトリでの管理もダメ、というわけでどうしたもんかと。

Android アプリ開発では、

のような仕組みで、システム環境変数をビルド時の変数に inject してくれる仕組みがあるのでいいですね。

Xamarin(というか .NET アプリケーション、MSBuild)のエコシステムには、そういう仕組みがなさそう(見つからなかった…)なので、自力でやるしかなさそうです。

ケース1:Google Maps の API キー

私は Xamarin.Forms.GoogleMaps というライブラリを開発していて、それの サンプルアプリケーションのコードも 公開しています。

Android や iOS で Google Maps を使うには、各々の API Key を入手して、ライブラリに設定する必要があるわけで、サンプルアプリケーションでそれをやっているのですが、API key をソースコードに直書きしてしまうと、それをうっかりコミットしてしまう危険があるし、これを Fork してくれる人にもそのような間違いを起こして欲しくありません。

STEP1:APIキーの定義を分離する

まずは APIキーを Static なクラスに追い出します。

// Variables.cs
public static class Variables
{
    // https://developers.google.com/maps/documentation/android-api/signup
    public const string GOOGLE_MAPS_ANDROID_API_KEY = "your_google_maps_android_api_v2_api_key";

    // https://developers.google.com/maps/documentation/ios-sdk/start#step_4_get_an_api_key
    public const string GOOGLE_MAPS_IOS_API_KEY = "your_google_maps_sdk_for_ios_api_key";

    // https://msdn.microsoft.com/windows/uwp/maps-and-location/authentication-key
    public const string BING_MAPS_UWP_API_KEY = "your_bing_maps_apikey";
}

static readonly なプロパティにせず const にしているのは、Android での API Key の指定が属性になっている からです。

STEP2:Variables.cs を .gitignore に追加する

https://github.com/amay077/Xamarin.Forms.GoogleMaps/blob/master/XFGoogleMapSample/.gitignore

Variables.cs を .gitignore に追加して、 git で管理しないようにします。

これで Variables.cs に正しいAPIKeyを設定しても、コミット対象にならないので、間違えて API Key が流出することが防げます。

ただ、開発者がどんな Variables.cs を用意すればいいか分からないので、Variables.csVariables_sample.cs という名前でコミットしておきます。もちろんこちらには正しい APIKey は記述しません。

これで、 README.md などに、 「Variable_sample.csVariable.cs にリネームして、あなたのAPIKeyを設定して使ってください」とでも記述しておけば、大抵の開発者は理解できると思います。

STEP3:ビルドイベントで Variables_sample.csVariables.cs にコピーする

このままでもまあよいのですが、git clone してすぐビルドしてエラーになるのは悪いような良いような…。

Visual Studio、Xamarin Studio のビルドシステムには「ビルドイベント」という、ビルド前後などに任意のコマンドを実行する機能があるので、それを使って Variables_sample.csVariables.cs にコピーできます。

.csproj に次のように追加します。全ソースは こちら

// XFGoogleMapSample.csproj
(前略)
  </ProjectExtensions>
  <PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
    <PreBuildEvent>if not exist "$(ProjectDir)Variables.cs" copy "$(ProjectDir)Variables_sample.cs" "$(ProjectDir)Variables.cs" >nul</PreBuildEvent>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(OS)' != 'Windows_NT' ">
    <PreBuildEvent>rsync -u "$(ProjectDir)Variables_sample.cs" "$(ProjectDir)Variables.cs"</PreBuildEvent>
  </PropertyGroup>
</Project>

Windows と Mac 両方に対応しないといけないのが面倒な点1。 これは Condition=" '$(OS)' == 'Windows_NT' "Condition=" '$(OS)' != 'Windows_NT' " で行っています。

しかも、Visual Studio でビルドイベントを設定すると Condition が設定できないし、Xamarin Studio と Visual Studio で .csproj に記述される書式が違うしで、結局 .csproj に手書きするしかありませんでした。

次に、「Variables.cs が既に存在していたらコピーしない」としたいが、

  • Windows の copy コマンドは「上書きしない」オプションがないらしく、 if not exist を使う羽目に…
  • Mac の cp コマンドは「上書きしない」オプションはあるものの、上書きしない場合にエラーコードを返すらしく、それを MSBuild が検知してビルド失敗してしまうため、 rsync を使う羽目に…

と、それぞれ回りくどいことが必要なのが面倒な点2。 ここまでしてなんとか実現できました。

これで、「ソースを取得してすぐビルドできるけど、 Variables.cs は git では管理されない」ようになりました。

ケース2:CI(継続的インテグレーション)への対応

Visual Studio Team Services(VSTS)などの CI サービスで自動ビルドすることを考えると、CIサービスの機能で Variables.cs に、正しい APIKey を設定してやる必要があります。

冒頭で紹介した Android でのビルドの事例は、 gradle を使い、システム環境変数を AndroidManifest.xml に伝搬させています(manifestPlaceholders という仕組みなのかな?)。

.NET のビルドシステムである MSBuild に同類の機能があることを確認できなかったので、CIサービスが提供する一般的な機能を使って実現するしかなさそうです。

  • (本番用のAPIKeyが記述された) Variables.cs をコピーする
  • Variables.cs の内容を置換して本番用のAPIKeyを注入する

などです。

の例では、Perl を使って AndroidManifest.xml のプレースホルダを置換してAPIKeyを差し込んでいるようです。

Visual Studio Mobile Center ではどうよ?

Connect(); で発表され、今はプレビュー版を誰でも試せるようになっている

にも、github などからソースを取得して自動ビルドできる機能があります。 が、今のところ、VSTS や他のCIサービスほど機能が用意されていない為か、ビルド前に任意のコマンドを実行させることはできません、今のところ(期待を込めて2回言った)。

求む!ベストプラクティス

というわけで、泥臭い方法をいくつか使わないと実現できないのがなんだかなあ、という感じです。

「こういうもの」なのか、「もっとスマートな方法がある」のか、情報をお待ちしております。

やばい