GCP Windows VMでの永続ユーザー管理とSecret Manager連携によるセキュアな認証システム

はじめに

Windows Server環境において、認証情報の管理は最も重要なセキュリティ要素の一つです。特にクラウド環境では、パスワードをコードに直接記述したり、平文で保存することは深刻なセキュリティリスクを招きます。

本記事では、Google Cloud Secret ManagerTerraformを組み合わせて、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  │
                    └─────────────────┘

セキュリティレイヤー

  1. IAM認証: 最小権限の原則に基づくアクセス制御
  2. Secret暗号化: Google管理キーによる自動暗号化
  3. アクセス監査: 全てのSecret参照を記録
  4. ネットワーク分離: VPC内での安全な通信
  5. ローテーション: 定期的なパスワード更新

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=-

2. Terraformでの統合設定

 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運用が可能になります。

次回は、これらの仕組みを統合した総合的な運用ベストプラクティスについて解説します。

技術ネタ、趣味や備忘録などを書いているブログです
Hugo で構築されています。
テーマ StackJimmy によって設計されています。