「環境変数にAPIキーを直書きしている」「.envファイルをGitにコミットしてしまった」「本番環境のDBパスワードを全員が知っている」——SES現場で機密情報管理の問題に直面したことがあるエンジニアは多いはずです。
**Google Cloud Secret Managerは、APIキー、パスワード、証明書などの機密情報を安全に保管・管理・アクセスするためのフルマネージドサービスです。**暗号化、アクセス制御、監査ログ、自動ローテーションまで、機密情報のライフサイクル全体を管理できます。
本記事では、Secret Managerの基礎から、Cloud Run/GKEとの連携、CI/CDパイプラインでの活用、自動ローテーションまで、SESエンジニアが現場で即活用できる内容を解説します。

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現場での導入ガイド
段階的導入ステップ
- Phase 1 - 棚卸し:プロジェクト内の機密情報を全て洗い出し(.env, configファイル, ハードコード)
- Phase 2 - 移行:Secret Managerにシークレットを登録し、アプリケーションコードを修正
- Phase 3 - 自動化:CI/CD連携、自動ローテーション、監査ログのモニタリング
- 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シリーズをご覧ください。最新の実践ガイドを随時更新しています。