はじめに
Windows Server環境において、認証情報の管理は最も重要なセキュリティ要素の一つです。特にクラウド環境では、パスワードをコードに直接記述したり、平文で保存することは深刻なセキュリティリスクを招きます。
本記事では、Google Cloud Secret ManagerとTerraformを組み合わせて、Windows VMの永続ユーザーをセキュアに管理するシステムの構築方法を詳細に解説します。従来の手動管理からの脱却と、企業のセキュリティポリシーに準拠した自動化を実現します。
従来の問題点と課題
セキュリティリスク
1
2
3
4
5
6
7
8
|
# ❌ 危険な例:パスワードを直接記述
resource "google_compute_instance" "windows_vm" {
metadata = {
windows-startup-script-ps1 = <<-EOF
net user admin "HardcodedPassword123!" /add
EOF
}
}
|
主な問題点:
- パスワードがTerraformコードに平文で記載
- GitリポジトリにSecretが保存される
- コードレビューで認証情報が露出
- 監査ログに機密情報が記録される
運用上の課題
- 手動作業: 毎回VMにログインしてユーザーを作成
- 一貫性: 環境によってユーザー設定が異なる
- パスワード管理: 複雑なパスワードの手動生成と共有
- 権限設定: 適切な権限付与の煩雑さ
- 監査証跡: 誰がいつユーザーを作成したかの追跡困難
Secret Managerを活用したセキュアなアーキテクチャ
全体構成図
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Developer │ │ Secret │ │ Windows VM │
│ Workstation │ │ Manager │ │ │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ • Terraform │───▶│ • Passwords │───▶│ • User Creation │
│ • gcloud CLI │ │ • Certificates │ │ • Permission │
│ • IAM Auth │ │ • API Keys │ │ • Group Assign │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Audit Logs │
│ & Monitoring │
└─────────────────┘
|
セキュリティレイヤー
- IAM認証: 最小権限の原則に基づくアクセス制御
- Secret暗号化: Google管理キーによる自動暗号化
- アクセス監査: 全てのSecret参照を記録
- ネットワーク分離: VPC内での安全な通信
- ローテーション: 定期的なパスワード更新
Secret Managerの詳細設定
1. Secretの作成と管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# プロジェクト設定
export PROJECT_ID="your-project-id"
gcloud config set project $PROJECT_ID
# Secret Manager APIの有効化
gcloud services enable secretmanager.googleapis.com
# 複雑なパスワードの生成
ADMIN_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-25)
SERVICE_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-25)
# Secretの作成
echo -n "$ADMIN_PASSWORD" | gcloud secrets create windows-admin-password --data-file=-
echo -n "$SERVICE_PASSWORD" | gcloud secrets create windows-service-password --data-file=-
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
# terraform/secrets.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 4.84"
}
}
}
# Secret Managerリソースの定義
resource "google_secret_manager_secret" "windows_admin_password" {
secret_id = "windows-admin-password"
labels = {
purpose = "windows-vm-auth"
environment = var.environment
managed_by = "terraform"
}
replication {
automatic = true
}
}
resource "google_secret_manager_secret_version" "windows_admin_password" {
secret = google_secret_manager_secret.windows_admin_password.id
secret_data = var.admin_password != "" ? var.admin_password : random_password.admin_password.result
}
# ランダムパスワード生成
resource "random_password" "admin_password" {
length = 20
special = true
min_upper = 2
min_lower = 2
min_numeric = 2
min_special = 2
}
# IAMアクセス制御
resource "google_secret_manager_secret_iam_member" "vm_access" {
secret_id = google_secret_manager_secret.windows_admin_password.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.vm_service_account.email}"
}
resource "google_service_account" "vm_service_account" {
account_id = "${var.instance_name}-vm-sa"
display_name = "Windows VM Service Account"
description = "Service account for Windows VM ${var.instance_name}"
}
|
3. Secret参照データソース
1
2
3
4
5
6
7
8
9
|
# terraform/data.tf
# Secret Manager からパスワードを取得
data "google_secret_manager_secret_version" "windows_admin_password" {
secret = "windows-admin-password"
}
data "google_secret_manager_secret_version" "windows_service_password" {
secret = "windows-service-password"
}
|
Windows VMでの詳細なユーザー管理実装
1. 統合スタートアップスクリプト
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
# terraform/main.tf
resource "google_compute_instance" "windows_vm" {
name = var.instance_name
machine_type = var.machine_type
zone = var.zone
boot_disk {
initialize_params {
image = data.google_compute_image.windows_image.self_link
size = var.disk_size
type = var.disk_type
}
}
network_interface {
network = data.google_compute_network.vpc.id
subnetwork = data.google_compute_subnetwork.subnet.id
access_config {}
}
# 詳細なユーザー管理スクリプト
metadata = {
windows-startup-script-ps1 = templatefile("${path.module}/scripts/user-management.ps1", {
admin_user_name = var.admin_user_name
service_user_name = var.service_user_name
admin_secret_name = google_secret_manager_secret.windows_admin_password.secret_id
service_secret_name = google_secret_manager_secret.windows_service_password.secret_id
project_id = var.project_id
enable_rdp = var.enable_rdp
enable_monitoring = var.enable_monitoring
})
}
service_account {
email = google_service_account.vm_service_account.email
scopes = [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring.write"
]
}
tags = ["windows-server", "managed-users"]
}
|
2. PowerShellユーザー管理スクリプト
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
# scripts/user-management.ps1
param(
[string]$AdminUserName = "${admin_user_name}",
[string]$ServiceUserName = "${service_user_name}",
[string]$AdminSecretName = "${admin_secret_name}",
[string]$ServiceSecretName = "${service_secret_name}",
[string]$ProjectId = "${project_id}",
[bool]$EnableRDP = ${enable_rdp},
[bool]$EnableMonitoring = ${enable_monitoring}
)
Write-Output "Starting secure user management script..."
# ログ設定
$LogFile = "C:\Windows\Temp\user-management-$(Get-Date -Format 'yyyyMMdd-HHmmss').log"
Start-Transcript -Path $LogFile
try {
# Google Cloud SDK の確認とインストール
if (-not (Get-Command "gcloud" -ErrorAction SilentlyContinue)) {
Write-Output "Installing Google Cloud SDK..."
$GcloudInstaller = "C:\temp\GoogleCloudSDKInstaller.exe"
# インストーラーのダウンロード
Invoke-WebRequest -Uri "https://dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe" -OutFile $GcloudInstaller
# サイレントインストール
Start-Process -FilePath $GcloudInstaller -ArgumentList "/S" -Wait
# パスの更新
$env:PATH += ";C:\Program Files (x86)\Google\Cloud SDK\google-cloud-sdk\bin"
[Environment]::SetEnvironmentVariable("PATH", $env:PATH, [EnvironmentVariableTarget]::Machine)
}
# Secret Manager からパスワードを取得
Write-Output "Retrieving passwords from Secret Manager..."
$AdminPassword = gcloud secrets versions access latest --secret=$AdminSecretName --project=$ProjectId
$ServicePassword = gcloud secrets versions access latest --secret=$ServiceSecretName --project=$ProjectId
if (-not $AdminPassword -or -not $ServicePassword) {
throw "Failed to retrieve passwords from Secret Manager"
}
# 管理者ユーザーの作成
Write-Output "Creating administrator user: $AdminUserName"
# ユーザーの存在確認
$ExistingUser = Get-LocalUser -Name $AdminUserName -ErrorAction SilentlyContinue
if ($ExistingUser) {
Write-Output "User $AdminUserName already exists. Updating password..."
$SecureAdminPassword = ConvertTo-SecureString $AdminPassword -AsPlainText -Force
Set-LocalUser -Name $AdminUserName -Password $SecureAdminPassword
} else {
Write-Output "Creating new user: $AdminUserName"
$SecureAdminPassword = ConvertTo-SecureString $AdminPassword -AsPlainText -Force
New-LocalUser -Name $AdminUserName -Password $SecureAdminPassword -FullName "Windows Administrator" -Description "Admin user managed by Secret Manager"
# パスワード設定
Set-LocalUser -Name $AdminUserName -PasswordNeverExpires $true -UserMayNotChangePassword $false
}
# 管理者グループへの追加
$AdminGroups = @("Administrators", "Remote Desktop Users", "Remote Management Users")
foreach ($Group in $AdminGroups) {
try {
Add-LocalGroupMember -Group $Group -Member $AdminUserName -ErrorAction SilentlyContinue
Write-Output "Added $AdminUserName to $Group group"
} catch {
Write-Output "User $AdminUserName already in $Group group or group not found"
}
}
# サービスユーザーの作成
Write-Output "Creating service user: $ServiceUserName"
$ExistingServiceUser = Get-LocalUser -Name $ServiceUserName -ErrorAction SilentlyContinue
if ($ExistingServiceUser) {
Write-Output "Service user $ServiceUserName already exists. Updating password..."
$SecureServicePassword = ConvertTo-SecureString $ServicePassword -AsPlainText -Force
Set-LocalUser -Name $ServiceUserName -Password $SecureServicePassword
} else {
Write-Output "Creating new service user: $ServiceUserName"
$SecureServicePassword = ConvertTo-SecureString $ServicePassword -AsPlainText -Force
New-LocalUser -Name $ServiceUserName -Password $SecureServicePassword -FullName "Service Account" -Description "Service user for automated processes"
# サービスユーザー固有の設定
Set-LocalUser -Name $ServiceUserName -PasswordNeverExpires $true -UserMayNotChangePassword $true -AccountNeverExpires
}
# RDP設定
if ($EnableRDP) {
Write-Output "Enabling RDP access..."
# RDPの有効化
Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server" -Name "fDenyTSConnections" -Value 0
# ファイアウォールルールの追加
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
Write-Output "RDP access enabled"
}
# 最終ステータスの記録
$Status = @{
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
AdminUserCreated = $true
ServiceUserCreated = $true
RDPEnabled = $EnableRDP
MonitoringEnabled = $EnableMonitoring
}
$Status | ConvertTo-Json | Set-Content "C:\Windows\Temp\user-management-status.json"
Write-Output "User management completed successfully"
} catch {
Write-Error "User management failed: $($_.Exception.Message)"
Write-Error $_.ScriptStackTrace
exit 1
} finally {
Stop-Transcript
}
|
セキュリティベストプラクティス
1. IAM権限の最小化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
# terraform/iam.tf
# カスタムロールの定義(最小権限)
resource "google_project_iam_custom_role" "secret_accessor_minimal" {
role_id = "secretAccessorMinimal"
title = "Secret Accessor Minimal"
description = "Minimal permissions for accessing specific secrets"
permissions = [
"secretmanager.versions.access"
]
}
# 特定のSecretのみアクセス可能なバインディング
resource "google_secret_manager_secret_iam_binding" "admin_password_access" {
secret_id = google_secret_manager_secret.windows_admin_password.secret_id
role = google_project_iam_custom_role.secret_accessor_minimal.name
members = [
"serviceAccount:${google_service_account.vm_service_account.email}"
]
condition {
title = "Time-based access"
description = "Only allow access during business hours"
expression = "request.time.getHours() >= 9 && request.time.getHours() <= 17"
}
}
|
2. Secret自動ローテーション
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
# terraform/secret-rotation.tf
# Cloud Functionによる自動ローテーション
resource "google_cloudfunctions_function" "password_rotation" {
name = "password-rotation"
runtime = "python39"
available_memory_mb = 256
source_archive_bucket = google_storage_bucket.function_source.name
source_archive_object = google_storage_bucket_object.function_source.name
entry_point = "rotate_password"
event_trigger {
event_type = "google.pubsub.topic.publish"
resource = google_pubsub_topic.password_rotation_trigger.name
}
environment_variables = {
PROJECT_ID = var.project_id
SECRET_IDS = jsonencode([
google_secret_manager_secret.windows_admin_password.secret_id,
google_secret_manager_secret.windows_service_password.secret_id
])
}
}
# ローテーション実行トリガー
resource "google_cloud_scheduler_job" "password_rotation_schedule" {
name = "password-rotation-schedule"
description = "Triggers password rotation monthly"
schedule = "0 0 1 * *" # 毎月1日午前0時
time_zone = "Asia/Tokyo"
pubsub_target {
topic_name = google_pubsub_topic.password_rotation_trigger.id
data = base64encode("rotate")
}
}
|
3. 監査ログの強化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# terraform/audit.tf
# 専用ログシンクの作成
resource "google_logging_project_sink" "secret_access_audit" {
name = "secret-access-audit"
destination = "bigquery.googleapis.com/projects/${var.project_id}/datasets/${google_bigquery_dataset.audit_logs.dataset_id}"
filter = <<-EOT
resource.type="secretmanager.googleapis.com/Secret"
protoPayload.methodName="google.cloud.secretmanager.v1.SecretManagerService.AccessSecretVersion"
EOT
unique_writer_identity = true
}
# BigQuery監査ログデータセット
resource "google_bigquery_dataset" "audit_logs" {
dataset_id = "secret_manager_audit"
location = "US"
labels = {
purpose = "security-audit"
env = var.environment
}
}
|
トラブルシューティング
よくある問題と解決方法
1. Secret Manager接続エラー
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
# Secret Manager接続の診断
function Test-SecretManagerConnectivity {
param([string]$ProjectId, [string]$SecretId)
Write-Output "Testing Secret Manager connectivity..."
try {
# サービスアカウントの認証確認
$AuthInfo = gcloud auth list --filter="status:ACTIVE" --format="value(account)"
Write-Output "Active authentication: $AuthInfo"
# プロジェクトアクセス確認
gcloud projects describe $ProjectId --quiet
Write-Output "Project access: OK"
# Secret存在確認
gcloud secrets describe $SecretId --project=$ProjectId --quiet
Write-Output "Secret accessibility: OK"
# Secret値取得テスト
$TestValue = gcloud secrets versions access latest --secret=$SecretId --project=$ProjectId
if ($TestValue) {
Write-Output "Secret retrieval: OK"
} else {
Write-Error "Secret retrieval: FAILED"
}
} catch {
Write-Error "Connectivity test failed: $($_.Exception.Message)"
}
}
|
まとめ
Secret Managerと連携した永続ユーザー管理システムにより、以下の利点を実現できます:
セキュリティ向上
- ✅ 認証情報の暗号化保存
- ✅ アクセス履歴の完全な監査証跡
- ✅ 最小権限の原則に基づくIAM設計
- ✅ 自動パスワードローテーション
運用効率化
- ✅ Terraformによる宣言的なユーザー管理
- ✅ 環境間での一貫性保証
- ✅ 自動化されたコンプライアンス対応
- ✅ 緊急時アクセス手順の標準化
コスト最適化
- ✅ 手動作業の削減
- ✅ セキュリティインシデントの予防
- ✅ 監査コストの削減
この実装により、企業レベルのセキュリティ要件を満たしながら、効率的なWindows VM運用が可能になります。
次回は、これらの仕組みを統合した総合的な運用ベストプラクティスについて解説します。