目次
はじめに
- スタンドアロンアプリケーションやクラウドベースのWebアプリケーション等の設定に関する設計ポリシーを説明します。
- ここではアプリケーションの設定情報を構成(Configuration)、個々の設定項目・値のことを設定(Setting)、と表記します。(「設定」の集合が「構成」のイメージです。)
- 構成情報・設定を格納するストレージを「構成情報ストレージ」と表記します。構成情報ストレージの具体例として、環境変数、設定ファイル、データベース等を想定しています。
- 他のシリーズの紹介です。
基本的なポリシー
設定の分類
- アプリの設計を行うと様々な設定値がでてきます。大きく分けて、システム寄りの設定(システム設定)、業務寄りの設定(業務設定)、に分類できます。
- 求められる要件(セキュリティレベル、参照頻度・更新頻度、データ量、等)に基づいて、設定を整理・分類し、最適なストレージを選択します。この選択によって使用するソリューションや、バックアップ方式等が変動し、運用コストに影響する場合もあります。
- システム設定は、参照頻度は高いかもしれませんが、データ量や変更頻度は少なく、最悪の場合は保守で復元することも可能なので、安価でシンプルな構成情報ストレージを利用できます。
- 業務設定は顧客データになるので、より厳密な管理が必要となります。また、大量データで高頻度の参照・更新になる場合もあります。これらを考慮して、データベース等のより信頼性の高い構成情報ストレージを使用する必要があります。
- システム設定に業務設定が混在すると、システム設定用でも業務設定と同様に高度な構成情報ストレージが必要になってしまうので注意が必要です。
- システム設定なのか業務設定なのか判断が難しい場合があります。ユーザ都合(新規契約等)やユーザ操作によって追加・変更・削除される設定は全て「業務設定」、という判断方法もあります。
システム設定の格納
- クラウドを使用する場合、実行環境に応じて複数の構成情報ストレージが提供されており、用途に応じて使用するストレージを選択します。
種類(Azure例) 説明 設定の例 アプリ構成管理
(App Configuration)結合試験、運用等の環境や性能に依存する設定 DBやWebAPI接続文字列、ログ条件、リトライ条件 サーバ構成
(App Service, Functions等)サーバ上でアプリを稼働するための最小限の設定 クラウド構成管理への接続情報 アプリ設定ファイル
(appsettings.json)環境に依存しない共通の設定 機能構成オプション(DI関連等) 開発用アプリ設定ファイル
(appsettings.Development.json)開発環境でのみ有効な設定 開発DB接続文字列、モック使用 - クラウドでは、アプリケーションの構成を集中管理できるソリューション(以後、「アプリ構成管理」と表記)が提供されています。設定の管理を容易にし、保守性を向上するため、可能な限りアプリ構成管理でアプリの構成・設定を管理します。
- アプリ開発時は、ローカル環境のみでアプリの実行やデバッグができると開発効率が高まるので、ローカルにあるアプリ設定ファイル・開発用設定ファイルに必要な設定を定義します。(環境に依存しないアプリの設定はアプリ設定ファイル、開発環境に依存した設定は開発用アプリ設定ファイルに定義する。)
- クラウドでの開発環境を前提としている場合、環境に依存しないアプリの設定はアプリ設定ファイルではなく、アプリ構成管理で定義しても良いかもしれません。
- ユーザ設定、業務依存が大きい設定、等はシステムの稼働に必要な構成・設定とは要件が異なる場合が多いので、データベースやファイル等の別の構成情報ストレージを使用します。
- 重要性が低く、再現が容易な設定に関しては、ブラウザのローカルストレージやCookieに保存する方法も考えられます。
機密情報の格納
- ここでいう機密情報とは、システムの稼働に必要となる機微な情報であり、システム構成要素や外部システムとの接続(認証)に必要となるシークレット・アクセスキー・証明書、データの暗号化に使用するキー、等を想定しています。
- 機密情報はセキュアなストレージで管理します。クラウドを使用する場合、AzureではKeyVault、AWSではKSMやCloudHSM、などです。
- クラウドを使用する場合、Azure環境のマネージドIDのように、パスワード等の機密情報を設定せずに安全に接続する仕組みが用意されています。可能な限り、このような機密情報を不要とする仕組みを使用します。(管理対象となる機密情報の削減。)
- アップロード・ダウンロードファイル用の暗号化キー、ユーザが指定した外部連携用のキー、等のように業務都合で作成・登録される機密情報は、求められる安全性・データ量・アクセス頻度・運用費用等に応じて適切なストレージを選択します。設計・開発時のセキュリティガイドラインが設けられている企業では、それに準拠して決定する必要があります。
- 単純なものであれば、機密情報をAES256等で暗号化してDBに保存する方式が考えられます。(暗号化で使用するキーは、前述のセキュアなストレージで管理、等。)
- マイナンバー(個人番号)等のより安全性が求められる機密情報を管理する場合、利用料金が比較的高額になりますが、KeyVaultやCloudHSM等で提供するHSM機能の利用をお薦めします。HSM(Hardware Security Module)では、機密情報を暗号化・復元化するためのキーがハードウェアで管理され、外部から容易に取得できない仕組みになっています。
独自の構成情報ストレージの実装
- 一般的な構成情報ストレージでは、設定値の頻繁な更新は想定していません。高頻度の参照と更新が行われるような設定では、性能を維持しつつ安全に設定を更新できるような独自の構成ストレージが必要かもしれません。
- このような構成ストレージは、同時アクセスに対する安全な読み取り・書き込みを実現する必要があり、マルチリーダー・ライターロック等の難易度の高い設計・実装が求められる場合があります。設計・実装の品質確保が不十分な場合、アクセス頻度の高い運用環境でしか発生しないバグになる場合もあるので、実現には十分な検討が必要です。
表示項目と多言語対応
- 実行環境やユーザの選択に応じて使用言語の切替を可能とするアプリでは、タイトル、ボタン、ツールチップ等のUI部品の情報を国ごとに定義・保持する必要があります。
- このような仕組みは、多くのプログラミング言語の国際化機能としてサポートされています。例えば、JavaではResourceBundleとリソースファイル、C#ではリソース等を使用します。
- 多言語対応が不要な場合でも、エラーメッセージ、送信メールの件名・本文、等も同様の仕組みの使用をお薦めします。
リンク
詳細なポリシー
設計全般
- 既定値を設ける。
- アプリ実行の難易度(必要な知識)を下げるために、既定値を設けます。
- 開発環境から本番環境のリソースを誤って変更や削除しないよう、運用環境に関する接続情報は既定値や設定例に含めてはいけません。
- 同様に、意図しない変更や不正アクセスを防ぐために、開発環境以外のパスワードやアクセスキー・トークン等の機密情報は含めてはいけません。
- アプリフレームワークを業務チームで使用する場合、暗号化ビット数、リトライ回数、タイムアウト値等の技術的な設定値を決めるのは困難なので、フレームワークの開発側で最低限の既定値を設定する必要があります。
- 安全な既定値を使用する。
- セキュリティレベルを下げない値を既定値とします。(安全なデフォルト)
- もし、セキュリティレベルを下げるような設定を設ける場合、運用環境での重大なインシデントになる前に、その設定が行われていることを気づけるようにする工夫が重要です。詳細は後述します。
- 開発環境の設定が運用環境に混入すると重大なインシデントになるので、運用環境の値を既定値にすることをお薦めします。
- 開発効率や安全性を向上する設定を開発者に配布する。
- アプリの改修の都度、開発環境のDBや外部システムへの接続情報(シークレットやアクセスキーを含む)を設定ファイルに定義するのは大変です。現場のセキュリティガイドラインとして問題なければ、設定ファイルに開発環境用の接続情報を定義します。
- 開発環境では利用が難しい外部システムを使用する機能では、モック動作を行う機能を実装します。モック動作を可能とすることで、動作確認や単体テストが容易になり、開発効率が向上します。開発者に配布する開発用アプリ構成ファイルでは、モック動作を有効にします。ただし、この設定が結合試験環境や運用環境に移行しないよう既定値は無効にしておきます。
- 要件に応じて設定値の検証機能を実装する。
- 一般的な業務アプリの結合試験や総合試験では、設計書(設定定義)に基づいて構築した環境でアプリの各種テストを行い、設定値を含むアプリの妥当性を検証します。設定の誤りがある場合、その工程で不具合として検出され、訂正されます。このように結合試験工程以降で設定値の検証も行われるので、設定値の検証を実装する必要性は低いと考えます。(結合試験以降では、品質管理のために、結合対象となる機能・環境のバージョンを厳密に管理する想定です。)
- エンドユーザが設定を変更できるようなプロダクトやパッケージ、アプリフレームワーク等の場合、設定の間違いを検証する工程がないので、設定値の検証の実装が必要です。
- 設定値が不正な状態でサービスを継続すると、データの不整合や情報漏洩等のインシデントに繋がる場合があります。検証を実装する場合、設定の不正(検証失敗)を可能な限り早く検出することが重要です。アプリ起動の直後、ユーザのサインイン直後、等の早いタイミングで設定値を検証します。検証に失敗した場合、業務影響を踏まえてアプリの実行を停止するのか、継続(縮退運転)するのかを設計します。
類似機能での設定値の継承
- 類似の機能間で共通の設定の仕組みにすると、次の例のように特定のユースケースでの設定が困難になる場合があります。
設定例 例外的なユースケース REST API実行の待機時間、再試行回数等 信頼性が低いREST AP使用時は引き上げ DBコマンドタイムアウト 実行完了を長時間待機するバッチはコマンドタイムアウトを伸長 データ取得上限件数、削除最大件数 大量データを扱う特定バッチの上限件数の引き上げ ページ辺り件数、最大ページ数 管理者画面での表示件数の引き上げ - このような場合、次のように継承(共通と個別で設定を分離)を可能とする設計にします。
(この例では、Webサービスを実行するための汎用的なクラスWebClientを実装し、実行のタイムアウトは60秒としています。マスタデータ検索REST APIを実行するために、WebClientから派生したMstRestClientクラスを実装します。このREST APIは信頼性が低いのでタイムアウトは120秒に伸長します。)123WebClient::TimeoutBySecond=60WebClient::RetryCount=3WebClient:MstRestClient:TimeoutBySecond=120123"WebClient": {"default": { "TimeoutBySecond": 60, "RetryCount": 3 },"MstRestClient": { "TimeoutBySecond": 120 },
URIによる複数設定の統合
- URIは、インターネット上のHTMLや画像等のリソースを単一情報で一意に示せることにメリットがあります。
- スキーマ、ホスト名、ポート番号、ディレクトリ名等の設定値からURIを生成するような場合、URIを指定可能とする単一の設定値に統合します。
- 個別の設定値に分けてしまうと接続先が分かりづらくなります。また、環境を切り替える際に、複数の設定値を纏めて変更する必要があり、少々手間がかかります。
環境依存値はベース値を使用
- 処理で必要となるURIやパスが環境に依存する場合、実行環境を容易に切り替えられるよう、環境依存部分をベースURI、ベースパス、等のような設定値にします。
- REST API等のWebサービスを使用するアプリの場合、次の例のように、「Webサービスを識別するホスト名、ポート番号、一部パス」は環境依存(黄色部分)、「呼び出す先のWebサービスのパス」は実装で固定(青色部分)、することになると思います。
想定例 サンプルURI ローカル環境 https://localhost:8438/MstProj/api/v1/Customer/1 開発環境 https://dev-mstapi:8080/api/v1/Customer/1 結合試験環境A https://it-mstapi/ita/api/v1/Customer/1 結合試験環境B https://it-mstapi/itb/api/v1/Customer/1 運用環境 https://prod-mstapi/api/v1/Customer/1
リンク
実行時パラメータの使用
- 次のように、設定値と実行時に決定するパラメータを組み合わせる場合、実行時の値を展開する場所を示す印(プレースホルダ)を使用します。プレースホルダを使用することで期待するパスやURIをイメージしやすくなり、実行時パラメータの種類や位置の変更が容易になるためです。(プレースホルダ{}の部分を実行時の値に展開するイメージです。)
設定値の例 展開例 /backup/{date}/org/{userid} /backup/20231231/org/1234 https://server/api/v1/Customer/{userid} https://server/api/v1/Customer/1234 CREATE DATABASE {dbname} COLLATE {collate} CREATE DATABASE CUS001 COLLATE French_CI_AI - ユーザや外部システム等の信頼できない入力をプレースホルダに展開する場合、SQLインジェクション、OSコマンドインジェクション、ログインジェクション等の対策が必要になります。
- プレースホルダとしてではなく設定値の値として{userid}が含まれる場合、誤った展開になる場合があります。このような場合、[userid], #userid#, {{userid}}, __userid__等のように別の表現にするか、「{{はプレースホルダの始まりではなく{と見なす」のようなエスケープのルール、を検討します。
- 展開したいパラメータは将来的に増減する可能性がある場合、プレースホルダ名から展開する値を選択する方式をお薦めします。例えば、検索したレコードのカラム値を展開するような場合、プレースホルダの名称({カラム名})から展開するカラム値を決定します。
終端区切り文字の有無の明確化
- URIやパスの終端に”/”や”\”等の区切文字(終端区切り文字)を付けるかどうかルールとして明確にします。
- 終端区切り文字について、一方の設定値では必要、他方の設定値では不要、のような設計になると、次のようにバグにつながります。123456789// 期待するurl値: "https://server/parent/child"// 失敗例1:設定値の最後に"\"がない前提の実装string baseUri = "https://server/parent/"; // 設定値を想定string result = baseUri + "/child"; // 結果NG: "https://server/parent//child"// 失敗例2:設定値の最後に"\"がある前提の実装string baseUri = "https://server/parent"; // 設定値を想定string result = baseUri + "child"; // 結果NG: "https://server/parentchild"
- フールプルーフや開発者の負担を軽減するために、実装では終端区切り文字の有無にかかわらず正常動作させることをお薦めします。
- 例えば、JavaのURIやPath、C#のUriやPath、等のように、区切り文字の有無を問題なく処理できるライブラリを使用してURIやパスを編集します。文字列で操作する場合、次の例のように、終端の区切り文字をトリム後(=区切り文字がない状態)する方法もあります。123// 文字列操作の例string baseUri = "https://server/parent/"; // 設定値を想定string result = baseUri.TrimEnd('/') + "/child"; // 結果OK: "https://server/parent/child"
相対パスの基準フォルダの明確化
- 相対パスを指定可能な設定値の場合、基準とするフォルダ(基準フォルダ)が曖昧になりやすいので、仕様として明確にすることをお薦めします。
- 基準フォルダとして、アプリを実行した際のカレントディレクトリを使う方法、アプリで指定したディレクトリを使う方法が考えられます。
- 前者のカレントディレクトリを使用する方法は、実行環境・実行方法で変動する可能性があり、実行環境によっては正しく動作しない場合があるので、それらを考慮した設計が必要になります。
- 後者のアプリ指定フォルダを使用する方法では、アプリ本体となるコマンド・バイナリファイルが配置されたディレクトリ、設定値で定義したディレクトリ、等を基準にできます。基準フォルダが固定されるので、経験的にこちらの方法が安全で薦めです。
コメントの活用
- 設定ファイルを使用する場合、メンテナンス性を向上するために、コメントの定義を許容することをお薦めします。(一部の設定ストレージでは設定値に対するメモを登録できる場合があります。)
- 設定の既定値を使用する場合、有効となる既定値が分かるよう「既定値を設定した定義」をコメントアウトして残しておくことをお薦めします。
- 設定値の許容値、注意点がコメントとして残っていると事故を防ぐことができます。基本は設計書やガイド等に書くべき内容なので必要最小限で重要なもののみに絞ってコメントすることをお薦めします。個人的な意見ですが、開発者は用意されている資料を見ない人が多いので、このように記載しておいた方が作業効率が上がり、無用なサポートを削減できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | { // "TargeFile": "default.json" // "RetryCount": 3 "FileScanInterval": 10, // [s] "ReportYearMonth": "2023-03", // "yyyy-MM" "TenantId": "005d28bb-2cec-4c11-bdeb-0be69dff0875", // A社テナント "FingerPrint": "57DDE...0DEC06", // SHA-224(大小文字無視) "RunMinutes": "30", // 分(0-59) "DisplayType": 2, // 0: 非表示, 1:常時表示, 2: ユーザ設定に基づいて表示 "IgnoreClientCert": true, // 運用環境でのtrueは禁止 "ApiSecret": "", // Key Vaultで管理 "DisableFunc1": true, // 変更時はDisableFunc2も併せて変更 ... } |
注意が必要な設定値
- 削除対象に影響を与える設定では、指定された値の妥当性の検証をお薦めします。
- ., .., \等の指定により、アプリそのものやOS領域の重要ファイルを削除する可能性があります。
- 被害を最小限にするために、削除対象のパスが「アプリのデータ領域配下」「一時ディレクトリ配下」「ホームディレクトリ配下」等であることの検証をお薦めします。
- 運用時のインシデントに繋がる設定は気づけるようにする。
- 開発環境に用意できない外部システムがある場合、このような外部システムの呼び出し処理をモック動作させる設計にします。モック動作させるかどうかは設定値で切り替えられるようにします。
- モック動作を可能とすることで、開発やテスト効率を向上できますが、このような設定を運用環境に移行すると重大なインシデントに繋がる場合があります。
- このような事態に早急に気づけるような工夫が必要です。運用環境では警告以上のログは厳密にチェックされる場合が多いので、このような設定が有効になっている場合は警告ログの出力をお薦めします。
- 開発時のインシデントに繋がる設定は無効にしておく。
- クラウド上のデータベースやストレージ等の有料リソースの作成・変更を行う機能では、意図しない課金を防ぐために、開発者にはモック動作を有効にした設定の配布をお薦めします。月初に数十万円の請求が来て初めて気づく場合もあるので、クラウド側のコスト監視/アラート機能の使用をお薦めします。
- 開発環境から外部にメールが送信され、インシデント(情報漏洩)になる場合があります。このようなインシデントを防止するために、メール送信機能ではモック動作を実装し、開発者にはモック動作を有効にした設定の配布をお薦めします。また、使用するメールアドレス(ドメイン)として、自ドメインや到達不可能なexample.com等のサンプルドメインの使用をお薦めします。
リンク