アプリケーション監視サービスの API キー
先日、以下の記事を投稿しました。
上記のようなアプリケーション監視サービス (APM) を利用するには、API キー (Sentry では DSN、Data Source Name が相当) を指定してそれら監視サービスクライアント SDK を初期化する必要があります。
Blazor WebAssembly + Raygun であれば、wwwroot/appsettings.json に以下のように API キーを記述しますし、
{
"Raygun": {
"ApiKey": "*** ここに API キーを記入 ***"
}
}
同じく Sentry では Program.cs でクライアント SDK の初期化時に DSN を指定します。
...
builder.UseSentry(options =>
{
options.Dsn = "*** ここに DSN を記入 ***";
});
...
なお、Sentry の場合であっても、アプリケーション構成ファイル wwwroot/appsettings.json に DSN を記述することもできます。
{
"Sentry": {
"Dsn": "*** ここに DSN を記入 ***",
}
}
監視サービスの API キーは "機密情報" ではない
ちなみに Blazor WebAssembly はいわゆるシングルページアプリケーション、SPA なので、すべての情報はクライアント側 (ブラウザ) にダウンロード・開示されてしまいます。appsettings.json ファイルも例外ではありません。ブラウザのアドレスバーに "https://.../appsettings.json" と入力すればそれだけで appsettings.json の内容がブラウザに表示されます。そのため、上記のような監視サービスの API キー (DSN) は事実上、一般公開となります。ただ、この種の監視サービスの API キーが公開状態になることそれ自体は問題とされていません。もちろん、第3者が同じキーを使って不正なクラッシュレポートが送信されると、誤った判断や運用の混乱を招きかねません。そのため、監視サービス側の設定でレポートの受信を許可するドメインを指定するなどの、一定の防護機構がある感じです。
そうはいっても API キーをコード中に書き込んでおいていいの?
以上のとおり、監視サービスの API キーは機密情報ではありません。そのため、API キーを即値で appsettings.json に書き込んだ状態でバージョン管理システムにコミットし、共通リポジトリにプッシュしても、それは機密情報の漏洩にはなりません。
しかしそうは言っても、設定ファイル (appsettings.json) とはいえ、コード中に即値で API キーを書いておくのはちょっと気持ち悪いです。そのようなコードがコミット履歴に残ることに、気持ち的にモヤモヤするのです。もしも API キーを更新・変更するとなったときに、そのためだけにコード変更のコミットが積み上がるのもなんかイヤです。それに、単一のソースコードから、複数のサーバーインスタンスにデプロイするような要件が発生した場合に、それぞれのデプロイ先に別々の API キーを割り当てようとしたら、そのたびにコードを書き換えて発行するのでしょうか?? そもそもそのような運用をしていたら、開発中のクラッシュもすべて監視サービスに送信されてしまい、運用環境で発生したクラッシュと混ぜこぜになって大混乱は必至です。
このようなシナリオにおいて、監視サービスの API キーはどこに格納しどのように使用するのがよいでしょうか?
デプロイ時に置換
この問題に対する自分が考えた解決策は 「デプロイ時の CI/CD スクリプトで、appsettings.json の API キー記載部分を置換する」 です。
Raygun を使用する Blazor WebAssembly アプリケーションプロジェクトを、GitHub Actions で発行する場合を例にとって説明します。
まず appsettings.json 内に記載の API キーですが、これは "${RAYGUN_API_KEY}" というプレースホルダー文字で記載しておきます、
{
"Raygun": {
"ApiKey": "${RAYGUN_API_KEY}"
}
}
このようにしておくことで、実際の API キーはバージョン管理システムに残らなくなり、(あとで詳細説明しますが) 開発中のクラッシュが、運用サーバー向けのクラッシュレポートに送信されることもなくなります。
さて、実際の API キーですが、これは GitHub リポジトリのリポジトリ変数に設定しておきます。具体的には、GitHub リポジトリの設定 (Settings) から [Secrets and variables] > [Actions] と開き、[Variables] のタブから [New repository variable] ボタンをクリックして追加します。変数名は "RAYGUN_API_KEY" としておきました。
そして、デプロイ用の GitHub Actions ワークフローです。以下のように、発行する直前に、appsettings.json 内の "${RAYGUN_API_KEY}" プレースホルダー文字列を、GitHub リポジトリ変数に格納されている実際の API キー値で sed コマンドを使って置換します。
...
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Checkout the code
- uses: actions/checkout@v5
# Install .NET SDK
- name: Setup .NET SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.0.x"
- name: Install .NET WebAssembly Tools
run: dotnet workload install wasm-tools
+ # Set Raygun API key to the application settings
+ - name: Set Raygun API Key
+ env:
+ RAYGUN_API_KEY: ${{ vars.RAYGUN_API_KEY }}
+ run: sed -i "s|\${RAYGUN_API_KEY}|${RAYGUN_API_KEY}|g" Project/wwwroot/appsettings.json
# Publish the site
- name: Publish
run: dotnet publish ...
...
これでめでたく、実際の API キーをコード中に即値で残さずに、運用サーバー向けに発行するタイミングで、API キーを埋め込めるようになりました。Raygun を使った例で説明しましたが、Sentry を使った場合もまったく同じ手法で対応できます。
アプリケーション側の対応
さて、これだけですと、開発中はちょっと困ったことになります。つまり、appsettings.json 中に記載されている "${RAYGUN_API_KEY}" という無効な API キーで、開発中のクラッシュが Raygun にレポートされてしまうのです。もちろん、Raygun 側からは HTTP 403 で弾かれますが、そのままにしておくのは気持ち悪いです。
そこで Blazor WebAssembly アプリケーションコード側に手を入れて、API キーが設定されているときだけ、Raygun クライアント SDK が組み込まれるようにします。
まずは Program.cs です。
これまでは闇雲に (?)、builder.UseRaygunBlazor() を呼び出して Raygun クライアントを組み込んでいたところを、いったんアプリケーション構成を確認して、API キーが "${RAYGUN_API_KEY}" ではない (つまり、実際の API キーが設定されているであろう) 場合にのみ、Raygun を組み込むように変更します。
...
+ // API キーが設定されている場合にのみ、Raygun を構成する
+ var raygunApiKey = builder.Configuration.GetValue("Raygun:ApiKey", defaultValue: "${RAYGUN_API_KEY}");
+ if (!string.IsNullOrWhiteSpace(raygunApiKey) && raygunApiKey != "${RAYGUN_API_KEY}")
+ {
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, RaygunLoggerProvider>());
builder.UseRaygunBlazor();
+ }
await builder.Build().RunAsync();
続けて、Raygun クライアントの初期化やパン屑リストの設定を行なっている箇所 (App.razor) にも変更が必要です。
前述のとおり、API キーが設定されていない場合は、RaygunBlazorClient サービスオブジェクトは DI コンテナに登録されないこととなります、そのため、@inject RaygunBlazorClient RaygunClient で注入していると、必要なサービスが注入できない例外が発生してしまいます。
そこで、@inject ディレクティブでお任せで RaygunBlazorClient サービスオブジェクトを手に入れるのではなく、DI コンテナ (IServiceProvider) に自分から取得を試み、取得できなければそれはそれでよしとする実装に書き換えます。
- @inject RaygunBlazorClient raygunClient;
+ @inject IServiceProvider Services
<Router AppAssembly="@typeof(App).Assembly"
NotFoundPage="typeof(Pages.NotFound)"
OnNavigateAsync="OnNavigateAsync">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>
@code
{
+ private RaygunBlazorClient? RaygunClient => this.Services.GetService<RaygunBlazorClient>();
private void OnNavigateAsync(NavigationContext args)
{
// Breadcrumbとしてページ遷移を記録(クラッシュレポートに含まれる)
- raygunClient.RecordBreadcrumb($"Navigation: /{args.Path}", BreadcrumbType.Navigation);
+ this.RaygunClient?.RecordBreadcrumb($"Navigation: /{args.Path}", BreadcrumbType.Navigation);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
- if (firstRender)
+ if (firstRender && this.RaygunClient is not null)
{
- await raygunClient.InitializeAsync();
+ await this.RaygunClient.InitializeAsync();
}
}
}
これで、開発中、実際の API キーが未設定の場合は、Raygun へのクラッシュレポート送信が行なわれずに動作するようになります。
なお、開発中も、クラッシュレポートそれ自体の動作確認をしたいなど、何らかの理由で Raygun へのレポート送信を有効にしたい場合は、appsettings.Development.json に開発時用の API キーを設定しておくとよいのではないでしょうか。
{
"Raygun": {
"ApiKey": "*** ここに開発時用の API キーを記入 ***"
}
}
Blazor WebAssembly では、appsettings.json と appsettings.Development.json の双方に同じ構成エントリがあった場合、開発時は appsettings.Development.json に記載されたエントリが優先して読み取られます。そのため、上記のとおり構成しておけば、開発時は開発時用の API キーを使って Raygun へのクラッシュレポートが有効になります。
appsettings.Development.json は .gitignore ファイルでバージョン管理から除外しておく、などの運用をしておくのがよいかとは思いますが、本記事ではその詳細については割愛します。なお、Blazor WebAssembly プロジェクトでなければ、User Secrets を使うのが便利で妥当なのですが、あいにくと Blazor WebAssembly では User Secrets が使えないため、appsettings.Development.json の利用が無難なところかと思われました。
まとめ
以上、アプリケーション監視サービスの API キーは、デプロイ用の CI/CD 環境に保存しておき、コード上はプレースホルダー文字列を記載しておいて、デプロイ時の CI/CD スクリプトでそのプレースホルダー文字列を実際の API キーで置換する、というアイディアについて記しました。
今回は触れませんでしたが、サーバー側アプリケーション実行環境がある場合は、サーバー側で動的に appsettings.json を生成して Blazor WebAssemnbly アプリケーションクライアントに渡す、といった手法もとれるかもしれません。
アプリケーション監視サービスの API キーの保存・使用について、他にもよりよい手法がありましたらコメント等で教えて頂けるとありがたいです。
