MENU
  • トップページ
  • サービス・料金表
  • Leachの強み
  • お客様の声
  • よくある質問
  • 会社概要
  • ブログ
Leach, Inc.
  • トップページ
  • サービス・料金表
  • Leachの強み
  • お客様の声
  • よくある質問
  • 会社概要
  • ブログ
無料相談はこちら
  • トップページ
  • サービス・料金表
  • Leachの強み
  • お客様の声
  • よくある質問
  • 会社概要
  • ブログ
Leach, Inc.
  • トップページ
  • サービス・料金表
  • Leachの強み
  • お客様の声
  • よくある質問
  • 会社概要
  • ブログ
  1. ホーム
  2. 技術
  3. APIキーをバックエンドでセキュアに保持する

APIキーをバックエンドでセキュアに保持する

2025 6/04
技術
2025年6月4日
  • URLをコピーしました!

概要

外部APIのAPIキーを保持する、マルチテナント型SaaSを開発されている方も多数いらっしゃるかと思います。
バックエンドとしてAPIキーをどのように保持すべきか?考えてみました。

設計方針

設計方針としては、以下になるかと思います。

  1. シークレットは「読み取り専用」に絞る
    • APIキーは書き換えや参照が頻繁に行われるものではありません。アプリケーションから必要に応じて「読む」だけでよいので、データベースやファイルに平文で置かず、読み取り専用の仕組みを用意します。
    • 平文配置した瞬間に、鍵は暴かれるリスクが高まります。どれほどセキュアなサーバーでも、不慮のミスや権限の漏れはゼロにはできません。だからこそ、「読むためには真正性を示し、限定的にしか触れられない場所に格納する」ことが大前提です。
  2. テナントごとに「分離」を徹底する
    • マルチテナント環境では、A社のAPIキーをB社から参照されてはいけません。
    • テナントIDをベースに「名前空間」を分ける、あるいはテナントごとに別のシークレットストアを使うなど、キーの保存先を論理的・物理的に分離しましょう。
  3. 暗号化キー(KMS)は別管理
    • シークレットそのものを暗号化して保存する場合、暗号化/復号の鍵(KMSキー)はアプリケーションサーバーとは別の場所で管理します。
    • クラウド利用なら「AWS KMS」「Azure Key Vault」「GCP Cloud KMS」といったマネージドKMSを活用し、何があっても平文の鍵が流出しない構造を作ります。
  4. アクセス権限は「最小権限」に絞る
    • アプリケーション本体(バックエンド)がシークレットを取りに行く際は、「読む権限」しか与えないIAMロール/サービスプリンシパルを用意します。
    • 本番とは別の環境(ステージング・開発)を用意するなら、それぞれに異なるキーを発行し、本番のキーには一切触れられないようにします。
  5. 監査ログを有効化する
    • 誰がいつ、どのテナントのどのシークレットにアクセスしたのかを必ずログに残す。
    • もし不正にアクセスを試みる攻撃者がいても、ログをたどることで侵入経路や責任所在を明確にできます。
  6. 可能であれば、定期ローテーションを組み込む
    • 「一度発行したAPIキーはずっと使い続ける」運用は危険です。
    • 毎月または四半期に一度、(あるいはテナントの要望に応じて)自動的に新鍵に切り替える仕組みを用意しましょう。
    • ローテーションといっても面倒ではありません。秘密情報の扱いを自動化し、心にゆとりを持った運用を実現しましょう。

2. 具体的な実装パターン例

ここでは代表的な3つのアプローチを示します。どれもメリット・デメリットがありますので、自社の運用規模やクラウドサービスの採用状況に合わせて選択してください。

2.1. クラウドシークレットマネージャ(AWS Secrets Managerなど)の活用

構成イメージ

┌───────────────────┐
│  クラウドKMS/KV   │   ← APIキーを暗号化して保管。例: AWS Secrets Manager 、
│(AWS Secrets      │      Azure Key Vault、GCP Secret Manager 等
│ Manager など)    │
└───────────────────┘
           ↑
           │(暗号化/復号リクエスト)
           │
┌───────────────────┐
│   バックエンド     │   ← シークレットを読む専用のIAMロールを付与
│  (APIサーバー等)   │
│                   │
└───────────────────┘
  1. テナントごとにシークレットを作成する
    • 例えば AWS Secrets Manager なら、シークレット名を /saas/myapp/tenant-{tenant_id}/external-api-key のように命名します。
    • Azure Key Vault も同様に「VaultName/tenant-{tenant_id}-api-key」という形で分ければよいでしょう。
  2. 読み取りはSDK経由で行う
    • バックエンドからはAWS SDK(Java/Python/Node.js など)や Azure SDK を通して「SecretId(名前)」を指定して暗号化済みのAPIキーを取り出す。
    • SDK呼び出しの際、実行環境のInstance Profile や Service Principal に「読み取り権限」だけを与えることで、不正な書き換えや作成を防ぎます。
  3. ローテーション機能を自動化
    • AWS Secrets Manager には「自動ローテーション機能」があり、Lambda関数を組み合わせれば数分で新鍵発行→置き換え→デプロイ完了までを自動化可能です。
    • ローテーションの度にテナントオーナーにアラートを送りたい場合は、SNS やメール連携を活用するとよいでしょう。
  4. メリット
    • マネージドサービスなのでオンプレでVaultを立てる手間が不要
    • KMSキーやアクセス権周りをクラウドベンダーが管理してくれる安心感
    • 監査ログやローテーション機能がデフォルトで使える
  5. デメリット・懸念点
    • クラウドベンダーのサービスロックインを招きやすい
    • コストがテナント数×シークレット数でかさむ可能性がある
    • クラウド側の障害が直撃すると、一時的にAPIキー取得自体ができなくなるリスクがある

実装ポイント(AWS例)

1. IAMロール作成
   - 名前: MyApp-SecretReaderRole
   - ポリシー例 (JSON抜粋):
     {
       "Version": "2012-10-17",
       "Statement": [
         {
           "Effect": "Allow",
           "Action": [
             "secretsmanager:GetSecretValue"
           ],
           "Resource": [
             "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:/saas/myapp/tenant-*/external-api-key-*"
           ]
         }
       ]
     }

2. シークレット作成
   AWS CLI:
   $ aws secretsmanager create-secret \
       --name "/saas/myapp/tenant-1001/external-api-key" \
       --description "テナントID=1001向けの外部APIキー" \
       --secret-string '{"api_key":"YOUR_API_KEY_HERE"}'

3. バックエンドでの取得 (Node.js例)
   import AWS from 'aws-sdk';
   const secretsManager = new AWS.SecretsManager({ region: 'ap-northeast-1' });
   async function fetchApiKey(tenantId) {
     const secretId = `/saas/myapp/tenant-${tenantId}/external-api-key`;
     const data = await secretsManager.getSecretValue({ SecretId: secretId }).promise();
     const secretObj = JSON.parse(data.SecretString);
     return secretObj.api_key;
   }

2.2. HashiCorp Vault のオンプレ/Self-Hosted活用

構成イメージ

┌───────────────────┐
│  HashiCorp Vault  │   ← 自社管理のVaultクラスター。TLS必須、複数台クラスタで高可用化
│  (Vault サーバー) │
└───────────────────┘
           ↑
           │(HTTPS経由でVault APIを呼び出し)
           │
┌───────────────────┐
│   バックエンド     │   ← VaultトークンはVault上でPolicyを設定し、「読むだけ」のRole
│  (APIサーバー等)   │
│                   │
└───────────────────┘
  1. 各テナントごとに「パス」を分離
    • Vaultのkv(Key-Valueストア)を使う場合、secret/data/tenant/{tenant_id}/api-key のようにパスを切る。
    • Policyでは、path "secret/data/tenant/${tenant_id}/*" に対して「read」権限のみを与えたトークンを発行する。
  2. 動的シークレット(Dynamic Secrets)も活用可能
    • たとえばデータベース認証情報をVaultで管理している場合、テナントごとに専用のデータベースユーザーを自動発行し、期限付きで使わせることも可能。
    • 外部APIキーに対しては動的発行は難しいが、DB周りをVaultに任せることでより堅牢な設計ができます。
  3. 監査ログ・KMS連携
    • VaultはAudit Deviceを有効化すれば、誰がいつどのエンドポイントに何をしたかをすべて記録します。
    • バックエンドサーバー上でVault Agentを動かし、ローカルキャッシュを使ってVault呼び出しを高速化しつつ、マスターキーはHSMやクラウドKMSと連携できます。
  4. メリット
    • 完全に自社管理なのでクラウドロックインがない
    • Policyでのアクセス制御が細かく、必要に応じて階層的に権限を設計可能
    • オンプレ・クラウド問わずデプロイできる
  5. デメリット・懸念点
    • Vaultクラスタを立てて運用するための導入コストと運用負荷が高い
    • TLS証明書管理やHA構成、バックアップ戦略を自前で整備しなければならない
    • スケールを考えた際のノード追加やバージョンアップ時に手間がかかる

実装ポイント

1. Vault初期設定 (CLI例)
   $ vault server -config=/etc/vault/config.hcl   ← Vaultサーバーを起動

2. テナントごとのパスにAPIキーを書き込む
   $ vault kv put secret/tenant/1001/api-key value="YOUR_API_KEY_FOR_1001"

3. テナント専用のPolicy作成 (policy.hcl例)
   path "secret/data/tenant/1001/*" {
     capabilities = ["read"]
   }

   $ vault policy write tenant1001 policy.hcl

4. テナントトークン発行
   $ vault token create -policy="tenant1001" -ttl="72h"

   → ここで発行されたトークンをバックエンドに設定。  
     以降、バックエンドはこのトークンを使って `GET /v1/secret/data/tenant/1001/api-key` を呼び出し、  
     返ってきた暗号文をVault内で復号して「YOUR_API_KEY_FOR_1001」を取得。

5. バックエンド側コード例 (Go言語例)
   import "github.com/hashicorp/vault/api"

   func fetchApiKey(tenantID string) (string, error) {
     client, err := api.NewClient(api.DefaultConfig())
     if err != nil {
       return "", err
     }
     client.SetToken(os.Getenv("VAULT_TOKEN")) // テナント用トークンを環境変数で渡す

     secretPath := fmt.Sprintf("secret/data/tenant/%s/api-key", tenantID)
     secret, err := client.Logical().Read(secretPath)
     if err != nil {
       return "", err
     }
     data := secret.Data["data"].(map[string]interface{})
     return data["value"].(string), nil
   }

2.3. 暗号化済みDBカラム or KMSで自前実装

クラウドのマネージドサービスを使わず、自前で暗号化ロジックを組み込むパターンです。
「簡易構築」として考えるなら選択肢になりますが、長期的には保守が大変なので注意してください。

構成イメージ

┌───────────────────┐
│   KMS (自社 or    │   ← 暗号化キーのみKMS/HSMに保管。アプリからはKMS APIで復号を依頼
│ クラウドKMS)      │
└───────────────────┘
           ↑
           │(暗号化/復号リクエスト)
           │
┌───────────────────┐
│   バックエンド     │   ← テナントごとの暗号文をDBカラムに保存 (encrypted_api_key)
│  (APIサーバー等)   │
└───────────────────┘
           ↓
┌───────────────────┐
│   RDB (MySQL等)    │   ← 暗号化済みの文字列だけを持つ
│   encrypted_api_key│
└───────────────────┘
  1. 保存時のフロー
    • テナント登録またはAPIキー登録のエンドポイントで、まずKMSに「暗号化リクエスト」を送り、平文のAPIキーをKMSが暗号化してくれる。
    • 暗号化された文字列(encrypted_blob)をDBの encrypted_api_key カラムにINSERT。
  2. 読み取り時のフロー
    • DBから encrypted_api_key カラムをSELECT して取得。
    • KMSに「復号リクエスト」を送り、平文のAPIキーを取得。
    • 平文APIキーはメモリ上にのみ存在し、実行が終わったらすぐ破棄する。
  3. メリット
    • クラウドマネージドのシークレットマネージャを使わずとも、低コストで仕組みを構築できる
    • RDBだけで管理できるので運用がシンプル
    • テナントごとのアクセスポリシーはアプリケーションコードで柔軟に設計可能
  4. デメリット・懸念点
    • 自前で暗号化ロジックを回すため、KMSのAPI設計変更や鍵管理を自社でフォローアップする負担が大きい
    • 暗号化キーをどう安全に管理するかが別途の課題になる
    • KMS→アプリ→DB の往復が発生するため、レスポンスが若干遅くなりやすい

実装ポイント(AWS KMS+MySQL例)

1. KMSキー作成
   $ aws kms create-key \
       --description "マルチテナントSaaS用外部APIキー暗号化キー" \
       --key-usage ENCRYPT_DECRYPT \
       --origin AWS_KMS

   → 応答に得られる KeyId (例: “1234abcd-12ab-34cd-56ef-1234567890ab”)

2. テナントAPIキー保存時のフロー (Node.js例)
   import AWS from 'aws-sdk';
   import mysql from 'mysql2/promise';

   const kms = new AWS.KMS({ region: 'ap-northeast-1' });
   const connection = await mysql.createConnection({ /* DB config */ });

   async function storeApiKey(tenantId, plaintextApiKey) {
     // 1) KMSで暗号化
     const enc = await kms.encrypt({
       KeyId: "1234abcd-12ab-34cd-56ef-1234567890ab",
       Plaintext: plaintextApiKey
     }).promise();

     const encryptedBlob = enc.CiphertextBlob.toString('base64');

     // 2) DBに保存
     await connection.execute(
       'INSERT INTO tenant_secrets (tenant_id, encrypted_api_key) VALUES (?, ?) ' +
       'ON DUPLICATE KEY UPDATE encrypted_api_key = VALUES(encrypted_api_key)',
       [tenantId, encryptedBlob]
     );
   }

3. テナントAPIキー読み取り時のフロー
   async function fetchApiKey(tenantId) {
     // 1) DBから暗号文を取得
     const [rows] = await connection.query(
       'SELECT encrypted_api_key FROM tenant_secrets WHERE tenant_id = ?',
       [tenantId]
     );
     if (rows.length === 0) throw new Error('APIキー未設定');

     const encryptedBlob = Buffer.from(rows[0].encrypted_api_key, 'base64');

     // 2) KMSで復号
     const dec = await kms.decrypt({
       CiphertextBlob: encryptedBlob
     }).promise();

     return dec.Plaintext.toString('utf-8');
   }

3. よくある落とし穴と回避策

  1. ソースコードにハードコーディングしてしまう
    • 「テストだから」「一時的だから」といってコードに const API_KEY = "hogehoge"; と書いてしまうと、GitHubのリポジトリを誤って公開してしまった、といった際に流出してしまいます。
    • Git履歴に残ったままになると、消したつもりでも第三者に参照されるリスクがあります。
    • → 絶対にやめましょう。 シークレット情報は1秒たりともコードに“べた書き”すべきではありません。
  2. 暗号化キーも一緒にコミットする
    • 自前暗号化パターンを使う場合、「鍵ファイル」や「秘密鍵」をGitに上げるとアウト。
    • → 鍵情報は専用のKMS/HSMに預け、バックエンドからのみAPI経由で利用する設計としてください。
  3. テナント間アクセスコントロールを後回しにする
    • 「とりあえず同一テーブルに鍵を入れておけばいいか」と実装してしまうと、気づかぬうちにA社がB社の鍵を参照できる状態になります。
    • → 早い段階で「テナントIDをベースにロジック上もDB上も分離」しておき、開発初期から「このクエリだと他社の鍵を読む恐れがある」というレビューを徹底しましょう。
  4. ローテーションを考慮せずに運用を開始する
    • しばらく運用しているうちに「どの鍵がいつ更新されたか」が分からなくなり、最悪「APIキーが漏洩してから初めて更新する」という事態に陥ります。
    • → ローテーション(更新)の仕組みを設計・実装してからリリースしましょう。少なくともマニュアルでもよいので、定期的に鍵を差し替える運用フローを決めておくべきです。
  5. 監査ログを軽視する
    • 「うちの会社は小規模だからログなんていらない」と思っていても、実際に問題が起きた際に「誰がいつ鍵を取りにいったか」が分からなければ、対策の穴を見つけられません。
    • → シークレットマネージャやVaultが提供する監査機能をオンにし、最低でも「取り出し」「更新」「削除」の全操作ログを保持しましょう。

結論

クラウドネイティブなシークレットマネージャを使うにせよ、Vaultを立てるにせよ、「鍵は鍵、データはデータ」 と変更頻度が異なるものを別で管理すれば、シンプルで安全な構成になります。

    技術

    この記事が気に入ったら
    いいね または フォローしてね!

    Follow @LeachJapan
    よかったらシェアしてね!
    • URLをコピーしました!
    • URLをコピーしました!
    • Leach、革新的な「縦スワイプLP制作サービス」提供開始

    関連記事

    • IPAに正式受理された脆弱性報告の実録 〜発見から社会貢献まで〜
      2025年5月30日
    • WordPressのサーバーはConoHa Wingがおすすめ
      2024年10月29日
    • ConoHa VPS + VSCode で、リモート開発環境を統一する
      2024年10月24日
    検索
    この記事の目次
    新着記事
    • APIキーをバックエンドでセキュアに保持する
    • Leach、革新的な「縦スワイプLP制作サービス」提供開始
    • IPAに正式受理された脆弱性報告の実録 〜発見から社会貢献まで〜
    • 株式会社Leach、「Leach 生成AI顧問」サービス提供開始 – 企業の生成AI活用を本格支援
    • TOKYO 創業ステーション「プランコンサルティング 事業計画書策定支援 終了証」を取得
    カテゴリー
    • お知らせ
    • プレスリリース
    • 仕事
    • 制作実績
    • 技術
    • 登壇
    最近のコメント

      無料相談のご案内
      Consultations

      システム開発で失敗したくない

      成果につながるWebサイトを構築したい

      生成AIの受託開発を依頼したい

      今すぐ相談する

        スクロールできます
        社名株式会社Leach (Leach, Inc.)
        所在地〒108-0014 東京都港区芝五丁目三十六番四号 札の辻スクエア 9階
        資本金4,000,000 円
        設立日2024年11月13日
        代表者代表取締役 冨永 拓也
        事業内容自社サービス開発、ソフトウェア開発業、ITコンサル業
        取引銀行みずほ銀行 青山支店
        GMOあおぞらネット銀行
        • トップページ
        • お知らせ
        • 特定商取引法に基づく表記
        • プライバシーポリシー
        • お問い合わせ

        © Leach, Inc.

        • メニュー
        • ホーム
        • サービス
        • 強み
        • 会社概要
        • トップへ