𝕏 f B! L
案件・求人数 12,345
案件を探す(準備中) エージェントを探す(準備中) お役立ち情報 ログイン
案件・求人数 12,345
Google Cloud Secret Manager セキュリティガイド|機密情報管理のベストプラクティス

Google Cloud Secret Manager セキュリティガイド|機密情報管理のベストプラクティス

Google CloudSecret Managerセキュリティ機密情報管理SESエンジニア
目次

「環境変数にAPIキーを直書きしている」「.envファイルをGitにコミットしてしまった」「本番環境のDBパスワードを全員が知っている」——SES現場で機密情報管理の問題に直面したことがあるエンジニアは多いはずです。

**Google Cloud Secret Managerは、APIキー、パスワード、証明書などの機密情報を安全に保管・管理・アクセスするためのフルマネージドサービスです。**暗号化、アクセス制御、監査ログ、自動ローテーションまで、機密情報のライフサイクル全体を管理できます。

本記事では、Secret Managerの基礎から、Cloud Run/GKEとの連携、CI/CDパイプラインでの活用、自動ローテーションまで、SESエンジニアが現場で即活用できる内容を解説します。

Google Cloud Secret Manager セキュリティ管理の全体像

Secret Managerの基礎

なぜ Secret Manager が必要なのか

従来の機密情報管理方法とそのリスクを整理します。

方法リスク深刻度
ソースコード直書きGit履歴に残る、漏洩時に全システム影響🔴 Critical
.envファイル共有時の漏洩、バージョン管理困難🟠 High
環境変数プロセスリスト/ログに露出🟡 Medium
ConfigMap (K8s)Base64エンコードのみ(暗号化なし)🟡 Medium
Secret Manager暗号化 + アクセス制御 + 監査ログ🟢 Low

主な機能

  • AES-256暗号化:保存時に自動暗号化、Customer-managed keys(CMEK)もサポート
  • バージョン管理:シークレットの変更履歴をすべて保持
  • IAMベースのアクセス制御:誰がどのシークレットにアクセスできるかを細粒度で制御
  • 監査ログ:すべてのアクセスをCloud Audit Logsに記録
  • 自動ローテーション:Cloud Functionsと連携した定期的な更新
  • リージョンレプリケーション:マルチリージョンでの可用性確保

Google Cloud IAMセキュリティガイドで解説したアクセス制御の概念と組み合わせて活用します。

基本操作

gcloud CLIでのシークレット管理

# シークレットの作成
gcloud secrets create database-password \
  --replication-policy="user-managed" \
  --locations="asia-northeast1,asia-northeast2" \
  --labels="env=production,team=backend"

# シークレットのバージョンを追加(値を設定)
echo -n "super-secret-password-2026" | \
  gcloud secrets versions add database-password --data-file=-

# シークレットの読み取り
gcloud secrets versions access latest \
  --secret="database-password"

# バージョン一覧の確認
gcloud secrets versions list database-password

# 特定バージョンの読み取り
gcloud secrets versions access 2 \
  --secret="database-password"

# バージョンの無効化(ソフト削除)
gcloud secrets versions disable 1 \
  --secret="database-password"

# バージョンの破棄(復旧不可)
gcloud secrets versions destroy 1 \
  --secret="database-password"

Terraformでの管理

# シークレットの定義
resource "google_secret_manager_secret" "database_password" {
  secret_id = "database-password"

  replication {
    user_managed {
      replicas {
        location = "asia-northeast1"
      }
      replicas {
        location = "asia-northeast2"
      }
    }
  }

  labels = {
    env  = "production"
    team = "backend"
  }

  # 自動ローテーション設定
  rotation {
    next_rotation_time = "2026-04-01T00:00:00Z"
    rotation_period    = "7776000s" # 90日
  }

  topics {
    name = google_pubsub_topic.secret_rotation.id
  }
}

# シークレットバージョン(初期値)
resource "google_secret_manager_secret_version" "database_password_v1" {
  secret      = google_secret_manager_secret.database_password.id
  secret_data = var.database_password # 変数から取得(tfstateに注意)
}

# IAMバインディング(Cloud Runサービスアカウントにアクセス権を付与)
resource "google_secret_manager_secret_iam_member" "cloud_run_access" {
  secret_id = google_secret_manager_secret.database_password.secret_id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${google_service_account.cloud_run.email}"
}

Cloud Runとの連携

シークレットの環境変数マウント

# Cloud Run サービス定義
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: my-api
  annotations:
    run.googleapis.com/ingress: all
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/maxScale: "10"
    spec:
      serviceAccountName: [email protected]
      containers:
        - image: gcr.io/project-id/my-api:latest
          env:
            # 環境変数としてマウント
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: latest
                  name: database-password
            - name: API_KEY
              valueFrom:
                secretKeyRef:
                  key: "3"  # 特定バージョン
                  name: external-api-key
          # ファイルとしてマウント
          volumeMounts:
            - name: tls-cert
              mountPath: /secrets/tls
              readOnly: true
      volumes:
        - name: tls-cert
          secret:
            secretName: tls-certificate
            items:
              - key: latest
                path: cert.pem

gcloud CLIでのデプロイ

# シークレットを環境変数として設定
gcloud run deploy my-api \
  --image gcr.io/project-id/my-api:latest \
  --set-secrets="DB_PASSWORD=database-password:latest,API_KEY=external-api-key:3" \
  [email protected] \
  --region=asia-northeast1

# シークレットをファイルとしてマウント
gcloud run deploy my-api \
  --image gcr.io/project-id/my-api:latest \
  --set-secrets="/secrets/tls/cert.pem=tls-certificate:latest" \
  --region=asia-northeast1

アプリケーションコードからのアクセス

// Node.js / TypeScript
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';

const client = new SecretManagerServiceClient();

async function getSecret(secretName: string, version = 'latest'): Promise<string> {
  const projectId = process.env.GOOGLE_CLOUD_PROJECT;
  const name = `projects/${projectId}/secrets/${secretName}/versions/${version}`;
  
  const [response] = await client.accessSecretVersion({ name });
  const payload = response.payload?.data;
  
  if (!payload) {
    throw new Error(`シークレット ${secretName} が空です`);
  }
  
  return typeof payload === 'string' ? payload : payload.toString('utf-8');
}

// キャッシュ付きのシークレットアクセス
class SecretCache {
  private cache = new Map<string, { value: string; expiry: number }>();
  private readonly ttlMs: number;

  constructor(ttlMs = 300_000) { // デフォルト5分キャッシュ
    this.ttlMs = ttlMs;
  }

  async get(secretName: string): Promise<string> {
    const cached = this.cache.get(secretName);
    if (cached && Date.now() < cached.expiry) {
      return cached.value;
    }

    const value = await getSecret(secretName);
    this.cache.set(secretName, {
      value,
      expiry: Date.now() + this.ttlMs,
    });

    return value;
  }

  invalidate(secretName: string): void {
    this.cache.delete(secretName);
  }
}

// 使用例
const secrets = new SecretCache();

async function connectDatabase() {
  const password = await secrets.get('database-password');
  const connectionString = `postgresql://app:${password}@db.example.com:5432/mydb`;
  // ...
}

GKE(Kubernetes)との連携

Secret Store CSI Driverの利用

# SecretProviderClass の定義
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: gcp-secrets
spec:
  provider: gcp
  parameters:
    secrets: |
      - resourceName: "projects/project-id/secrets/database-password/versions/latest"
        path: "db-password"
      - resourceName: "projects/project-id/secrets/redis-password/versions/latest"
        path: "redis-password"
      - resourceName: "projects/project-id/secrets/tls-certificate/versions/latest"
        path: "tls-cert.pem"

---
# Deploymentでのマウント
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-api
  template:
    metadata:
      labels:
        app: my-api
    spec:
      serviceAccountName: my-api-ksa
      containers:
        - name: api
          image: gcr.io/project-id/my-api:latest
          volumeMounts:
            - name: secrets
              mountPath: /secrets
              readOnly: true
          env:
            - name: DB_PASSWORD_FILE
              value: /secrets/db-password
      volumes:
        - name: secrets
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: gcp-secrets

Workload Identityの設定

# GKEクラスタでWorkload Identityを有効化
gcloud container clusters update my-cluster \
  --workload-pool=project-id.svc.id.goog \
  --region=asia-northeast1

# Kubernetes サービスアカウントの作成
kubectl create serviceaccount my-api-ksa

# IAMバインディング
gcloud iam service-accounts add-iam-policy-binding \
  [email protected] \
  --role=roles/iam.workloadIdentityUser \
  --member="serviceAccount:project-id.svc.id.goog[default/my-api-ksa]"

# KSAにGSAをアノテーション
kubectl annotate serviceaccount my-api-ksa \
  iam.gke.io/[email protected]

シークレットの自動ローテーション

Cloud Functionsによるローテーション

# rotate_secret.py
import os
import json
import string
import secrets
from google.cloud import secretmanager
from google.cloud import sql_v1beta4

def rotate_database_password(event, context):
    """Pub/Subトリガーで呼ばれるローテーション関数"""
    
    # イベントデータの解析
    secret_name = event.get('name', '')
    
    # 新しいパスワードの生成
    alphabet = string.ascii_letters + string.digits + '!@#$%^&*'
    new_password = ''.join(secrets.choice(alphabet) for _ in range(32))
    
    # 1. Cloud SQLのパスワードを更新
    update_cloudsql_password(new_password)
    
    # 2. Secret Managerに新バージョンとして保存
    client = secretmanager.SecretManagerServiceClient()
    parent = client.secret_path(os.environ['PROJECT_ID'], 'database-password')
    
    client.add_secret_version(
        parent=parent,
        payload={'data': new_password.encode('utf-8')}
    )
    
    print(f'✅ シークレットのローテーション完了: {secret_name}')


def update_cloudsql_password(new_password: str):
    """Cloud SQLのユーザーパスワードを更新"""
    client = sql_v1beta4.SqlUsersServiceClient()
    
    request = sql_v1beta4.UpdateUserRequest(
        project=os.environ['PROJECT_ID'],
        instance=os.environ['CLOUDSQL_INSTANCE'],
        name='app_user',
        body=sql_v1beta4.User(
            password=new_password
        )
    )
    
    client.update(request=request)
    print('✅ Cloud SQLパスワード更新完了')

ローテーションスケジュールの設定

# Pub/Subトピック(ローテーショントリガー)
resource "google_pubsub_topic" "secret_rotation" {
  name = "secret-rotation-trigger"
}

# Cloud Functions(ローテーション実行)
resource "google_cloudfunctions2_function" "rotate_secret" {
  name     = "rotate-database-password"
  location = "asia-northeast1"

  build_config {
    runtime     = "python312"
    entry_point = "rotate_database_password"
    source {
      storage_source {
        bucket = google_storage_bucket.functions.name
        object = google_storage_bucket_object.rotate_function.name
      }
    }
  }

  service_config {
    available_memory   = "256M"
    timeout_seconds    = 60
    
    environment_variables = {
      PROJECT_ID       = var.project_id
      CLOUDSQL_INSTANCE = google_sql_database_instance.main.name
    }
    
    service_account_email = google_service_account.rotation.email
  }

  event_trigger {
    trigger_region = "asia-northeast1"
    event_type     = "google.cloud.pubsub.topic.v1.messagePublished"
    pubsub_topic   = google_pubsub_topic.secret_rotation.id
  }
}

# Secret Managerのローテーション設定
resource "google_secret_manager_secret" "database_password" {
  secret_id = "database-password"

  rotation {
    next_rotation_time = "2026-04-01T00:00:00Z"
    rotation_period    = "7776000s" # 90日
  }

  topics {
    name = google_pubsub_topic.secret_rotation.id
  }

  replication {
    user_managed {
      replicas {
        location = "asia-northeast1"
      }
    }
  }
}

CI/CDパイプラインでの活用

GitHub ActionsでのSecret Manager利用

# .github/workflows/deploy.yml
name: Deploy to Cloud Run

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read

    steps:
      - uses: actions/checkout@v4

      - id: auth
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ vars.WIF_PROVIDER }}
          service_account: ${{ vars.DEPLOY_SA }}

      - name: Access Secrets for Build
        id: secrets
        run: |
          # ビルド時に必要なシークレットを取得
          NPM_TOKEN=$(gcloud secrets versions access latest --secret="npm-registry-token")
          echo "::add-mask::$NPM_TOKEN"
          echo "NPM_TOKEN=$NPM_TOKEN" >> $GITHUB_ENV

      - name: Build and Push
        run: |
          docker build \
            --build-arg NPM_TOKEN=$NPM_TOKEN \
            -t gcr.io/${{ vars.PROJECT_ID }}/my-api:${{ github.sha }} .
          docker push gcr.io/${{ vars.PROJECT_ID }}/my-api:${{ github.sha }}

      - name: Deploy to Cloud Run
        run: |
          gcloud run deploy my-api \
            --image gcr.io/${{ vars.PROJECT_ID }}/my-api:${{ github.sha }} \
            --set-secrets="DB_PASSWORD=database-password:latest" \
            --region=asia-northeast1

セキュリティのベストプラクティス

アクセス制御の最小権限原則

# 読み取り専用アクセス(アプリケーション用)
resource "google_secret_manager_secret_iam_member" "app_accessor" {
  secret_id = google_secret_manager_secret.database_password.secret_id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${google_service_account.app.email}"
  
  # 条件付きアクセス(特定のシークレットバージョンのみ)
  condition {
    title       = "latest-only"
    description = "latestバージョンへのアクセスのみ許可"
    expression  = "resource.name.endsWith('/versions/latest')"
  }
}

# 管理者アクセス(シークレットの作成・更新権限)
resource "google_secret_manager_secret_iam_member" "admin" {
  secret_id = google_secret_manager_secret.database_password.secret_id
  role      = "roles/secretmanager.admin"
  member    = "group:[email protected]"
}

監査とモニタリング

# Secret Managerへのアクセスログを確認
gcloud logging read '
  resource.type="audited_resource"
  AND protoPayload.serviceName="secretmanager.googleapis.com"
  AND protoPayload.methodName="google.cloud.secretmanager.v1.SecretManagerService.AccessSecretVersion"
' --limit=50 --format=json

# 不正アクセスのアラート設定
gcloud alpha monitoring policies create \
  --notification-channels="projects/project-id/notificationChannels/12345" \
  --display-name="Secret Manager Unauthorized Access" \
  --condition-display-name="Unauthorized secret access" \
  --condition-filter='
    resource.type="audited_resource"
    AND protoPayload.serviceName="secretmanager.googleapis.com"
    AND protoPayload.status.code!=0
  '

SES現場での導入ガイド

段階的導入ステップ

  1. Phase 1 - 棚卸し:プロジェクト内の機密情報を全て洗い出し(.env, configファイル, ハードコード)
  2. Phase 2 - 移行:Secret Managerにシークレットを登録し、アプリケーションコードを修正
  3. Phase 3 - 自動化:CI/CD連携、自動ローテーション、監査ログのモニタリング
  4. Phase 4 - 運用最適化:不要なバージョンの削除、アクセスパターンの見直し

コスト見積もり

項目料金備考
シークレット保管$0.06/月/シークレットアクティブなシークレットのみ
アクセス$0.03/10,000回APIコール数
ローテーションCloud Functions料金月数回程度なら無料枠内

典型的なSESプロジェクト(シークレット50個、1日10,000アクセス)で月額$4-5程度のコストです。

Google Cloud VPCネットワーキングCloud Armor WAFガイドと組み合わせて、多層防御のセキュリティ体制を構築しましょう。

まとめ|Secret Managerで「機密情報をコードに書かない」文化を

Google Cloud Secret Managerは、機密情報管理の課題をシンプルかつ安全に解決するサービスです。

  • 暗号化とアクセス制御でシークレットの漏洩リスクを最小化
  • バージョン管理で変更履歴を追跡し、問題発生時にロールバック可能
  • Cloud Run/GKE連携でアプリケーションへの安全なシークレット注入
  • 自動ローテーションでパスワードの定期変更を運用負荷なしで実現
  • 監査ログで「誰が・いつ・どのシークレットにアクセスしたか」を完全記録

SES現場では、「機密情報が.envファイルに書かれている」プロジェクトはまだまだ多いです。Secret Managerへの移行を提案し、セキュリティ意識の高いエンジニアとして評価を高めましょう。

Google Cloudの活用法をさらに深く学びたい方は、Google Cloudシリーズをご覧ください。最新の実践ガイドを随時更新しています。

SES案件をお探しですか?

SES記事をもっと読む →
🏗️

SES BASE 編集長

SES業界歴10年以上のメンバーが在籍する編集チーム。SES企業での営業・エンジニア経験、フリーランス独立経験を持つメンバーが、業界のリアルな情報をお届けします。

📊 業界データに基づく記事制作 🔍 IPA・経済産業省データ参照 💼 SES実務経験者が執筆・監修