-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathiam.tf
More file actions
728 lines (596 loc) · 25.8 KB
/
iam.tf
File metadata and controls
728 lines (596 loc) · 25.8 KB
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
# Data source to get project number for Google service account names
data "google_project" "current" {
project_id = var.project_id
}
# Local value for the KMS key to use (either created or provided)
locals {
kms_key_name = var.create_cmek ? google_kms_crypto_key.gitpod[0].id : var.kms_key_name
}
# ================================
# GOOGLE SERVICE IDENTITIES FOR CMEK
# ================================
# Create service identities when CMEK is enabled AND not using pre-created service accounts
resource "google_project_service_identity" "secretmanager" {
count = var.create_cmek && !local.using_pre_created_service_accounts ? 1 : 0
provider = google-beta
project = var.project_id
service = "secretmanager.googleapis.com"
}
resource "google_project_service_identity" "pubsub" {
count = var.create_cmek && !local.using_pre_created_service_accounts ? 1 : 0
provider = google-beta
project = var.project_id
service = "pubsub.googleapis.com"
}
resource "google_project_service_identity" "artifactregistry" {
count = var.create_cmek && !local.using_pre_created_service_accounts ? 1 : 0
provider = google-beta
project = var.project_id
service = "artifactregistry.googleapis.com"
}
resource "google_project_service_identity" "redis" {
count = var.create_cmek && !local.using_pre_created_service_accounts ? 1 : 0
provider = google-beta
project = var.project_id
service = "redis.googleapis.com"
}
# Grant KMS permissions to Google service accounts when CMEK is enabled AND not using pre-created service accounts
resource "google_kms_crypto_key_iam_member" "google_secretmanager" {
count = var.create_cmek ? 1 : 0
crypto_key_id = google_kms_crypto_key.gitpod[0].id
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:service-${data.google_project.current.number}@gcp-sa-secretmanager.iam.gserviceaccount.com"
}
resource "google_kms_crypto_key_iam_member" "google_pubsub" {
count = var.create_cmek ? 1 : 0
crypto_key_id = google_kms_crypto_key.gitpod[0].id
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:service-${data.google_project.current.number}@gcp-sa-pubsub.iam.gserviceaccount.com"
}
resource "google_kms_crypto_key_iam_member" "google_storage" {
count = var.create_cmek ? 1 : 0
crypto_key_id = google_kms_crypto_key.gitpod[0].id
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:service-${data.google_project.current.number}@gs-project-accounts.iam.gserviceaccount.com"
}
resource "google_kms_crypto_key_iam_member" "google_artifactregistry" {
count = var.create_cmek ? 1 : 0
crypto_key_id = google_kms_crypto_key.gitpod[0].id
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:service-${data.google_project.current.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com"
}
resource "google_kms_crypto_key_iam_member" "google_compute" {
count = var.create_cmek ? 1 : 0
crypto_key_id = google_kms_crypto_key.gitpod[0].id
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:service-${data.google_project.current.number}@compute-system.iam.gserviceaccount.com"
}
resource "google_kms_crypto_key_iam_member" "google_redis" {
count = var.create_cmek ? 1 : 0
crypto_key_id = google_kms_crypto_key.gitpod[0].id
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:service-${data.google_project.current.number}@cloud-redis.iam.gserviceaccount.com"
}
# 1. RUNNER SERVICE ACCOUNT
# Manages runner infrastructure with minimal permissions
resource "google_service_account" "runner" {
count = var.pre_created_service_accounts.runner == "" ? 1 : 0
account_id = "${var.runner_name}-runner"
display_name = "Ona Runner"
description = "Service account for runner infrastructure management"
project = var.project_id
}
# Custom role with minimal compute permissions for runner
resource "google_project_iam_custom_role" "runner" {
count = var.pre_created_service_accounts.runner == "" ? 1 : 0
role_id = "${replace(var.runner_name, "-", "_")}_runner"
title = "Ona Runner"
description = "Minimal permissions for runner infrastructure management"
project = var.project_id
permissions = [
# Instance lifecycle management
"compute.instances.create",
"compute.instances.delete",
"compute.instances.get",
"compute.instances.list",
"compute.instances.start",
"compute.instances.stop",
"compute.instances.setLabels",
"compute.instances.setMetadata",
"compute.instances.setTags",
"compute.instances.attachDisk",
"compute.instances.detachDisk",
"compute.instances.setDiskAutoDelete",
"compute.instances.setServiceAccount",
"compute.instances.listReferrers",
# Disk management
"compute.disks.create",
"compute.disks.delete",
"compute.disks.get",
"compute.disks.list",
# Network resources
"compute.networks.get",
"compute.networks.list",
"compute.networks.use",
"compute.subnetworks.get",
"compute.subnetworks.list",
"compute.subnetworks.use",
"compute.addresses.create",
"compute.addresses.delete",
"compute.addresses.get",
"compute.addresses.use",
# Firewall permissions removed - all firewall rules are managed via Terraform
# No dynamic firewall operations are performed by the orchestrator
# Health check permissions for instance group management
"compute.healthChecks.use",
# Operations monitoring
"compute.globalOperations.get",
"compute.regionOperations.get",
"compute.zoneOperations.get",
# Machine type and disk type info
"compute.machineTypes.get",
"compute.machineTypes.list",
"compute.diskTypes.get",
"compute.diskTypes.list",
# Image management for VM creation and snapshot reconciler
"compute.images.get",
"compute.images.list",
"compute.images.useReadOnly",
"compute.images.create",
"compute.images.delete",
"compute.images.setLabels",
# Required for creating images from disks (snapshot reconciler)
"compute.disks.use",
"compute.disks.useReadOnly",
# Artifact Registry permissions for devcontainer image cache (minimal)
"artifactregistry.repositories.get",
"artifactregistry.repositories.list",
"artifactregistry.repositories.create",
"artifactregistry.repositories.delete",
"artifactregistry.repositories.update",
"artifactregistry.dockerimages.get",
"artifactregistry.dockerimages.list",
"artifactregistry.repositories.downloadArtifacts",
"artifactregistry.repositories.uploadArtifacts",
# Secret Manager permissions for environment secrets
"secretmanager.secrets.create",
"secretmanager.secrets.delete",
"secretmanager.secrets.get",
"secretmanager.secrets.list",
"secretmanager.secrets.getIamPolicy",
"secretmanager.secrets.setIamPolicy",
"secretmanager.versions.access",
"secretmanager.versions.add",
"secretmanager.versions.destroy",
# Pub/Sub permissions for event-driven reconciliation
"pubsub.subscriptions.get",
"pubsub.subscriptions.list",
"pubsub.subscriptions.consume",
"pubsub.topics.get",
"pubsub.topics.list",
# IAM permissions for service account management.
# actAs (roles/iam.serviceAccountUser) is granted per-SA via
# google_service_account_iam_member resources below — only on the
# runner, environment_vm, and proxy_vm SAs the runner needs to attach
# to instances and instance templates.
"iam.serviceAccounts.getIamPolicy",
"iam.serviceAccounts.setIamPolicy",
# Instance template permissions for runner control plane
"compute.instanceTemplates.create",
"compute.instanceTemplates.delete",
"compute.instanceTemplates.get",
"compute.instanceTemplates.getIamPolicy",
"compute.instanceTemplates.list",
"compute.instanceTemplates.setIamPolicy",
"compute.instanceTemplates.useReadOnly",
"compute.instanceGroupManagers.get",
"compute.instanceGroupManagers.list",
"compute.instanceGroupManagers.create",
"compute.instanceGroupManagers.delete",
"compute.instanceGroupManagers.update",
"compute.instanceGroupManagers.use",
# Instance group permissions required for MIG operations
"compute.instanceGroups.delete",
"compute.instanceGroups.list",
# Autoscaler permissions for dynamic warm pool scaling
"compute.autoscalers.create",
"compute.autoscalers.delete",
"compute.autoscalers.get",
"compute.autoscalers.update",
# Cloud Monitoring permissions for publishing warm pool scaling metrics
"monitoring.timeSeries.create",
# Cloud Logging permissions for environment and prebuild log persistence
"logging.logEntries.list", # Read environment logs from Cloud Logging
"logging.logEntries.create", # Write prebuild logs to Cloud Logging
"logging.logs.delete", # Delete prebuild logs when prebuild is deleted
]
}
# Bind custom role to runner control plane
resource "google_project_iam_member" "runner_cp_custom_role" {
count = !local.using_pre_created_service_accounts && local.runner_sa_email != "" ? 1 : 0
project = var.project_id
role = var.pre_created_service_accounts.runner == "" ? google_project_iam_custom_role.runner[0].id : "projects/${var.project_id}/roles/${replace(var.runner_name, "-", "_")}_runner"
member = "serviceAccount:${local.runner_sa_email}"
depends_on = [
google_project_iam_custom_role.runner,
google_service_account.runner
]
}
# Essential permissions for runner control plane (consolidated logging and monitoring below)
resource "google_project_iam_member" "runner_cp_trace" {
count = !local.using_pre_created_service_accounts && local.runner_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/cloudtrace.agent"
member = "serviceAccount:${local.runner_sa_email}"
}
# Redis access for IAM authentication and state management
resource "google_project_iam_member" "runner_cp_redis_editor" {
count = !local.using_pre_created_service_accounts && local.runner_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/redis.editor" # Can read/write but not manage instance
member = "serviceAccount:${local.runner_sa_email}"
}
# Additional permission needed for IAM authentication to Redis Cluster
resource "google_project_iam_member" "runner_cp_redis_db_connection" {
count = !local.using_pre_created_service_accounts && local.runner_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/redis.dbConnectionUser" # Required for IAM authentication
member = "serviceAccount:${local.runner_sa_email}"
}
# Certificate Manager viewer role for external LB certificate access
resource "google_project_iam_member" "runner_cp_certificate_manager" {
count = !local.using_pre_created_service_accounts && local.runner_sa_email != "" && var.loadbalancer_type == "external" ? 1 : 0
project = var.project_id
role = "roles/certificatemanager.viewer"
member = "serviceAccount:${local.runner_sa_email}"
}
# Secret Manager access for internal LB certificate secret
resource "google_secret_manager_secret_iam_member" "runner_cp_certificate_secret_access" {
count = var.certificate_secret_id != "" ? 1 : 0
project = var.project_id
secret_id = split("/", var.certificate_secret_id)[3]
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${local.runner_sa_email}"
}
# GCS access for runner assets bucket (runner VMs)
# objectAdmin is required because the runner reads trust bundles and writes
# metrics audit payloads (managed metrics) to this bucket.
resource "google_storage_bucket_iam_member" "runner_runner_assets_access" {
bucket = google_storage_bucket.runner_assets.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${local.runner_sa_email}"
}
# GCS access for agent storage bucket (runner VMs)
resource "google_storage_bucket_iam_member" "runner_agent_storage_access" {
count = var.enable_agents ? 1 : 0
bucket = google_storage_bucket.agent_storage[0].name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${local.runner_sa_email}"
}
# 2. ENVIRONMENT VM SERVICE ACCOUNT
# Minimal permissions for individual environment VMs
resource "google_service_account" "environment_vm" {
count = var.pre_created_service_accounts.environment_vm == "" ? 1 : 0
account_id = "${var.runner_name}-env-vm"
display_name = "Ona Environment VM"
description = "Minimal service account for environment VMs"
project = var.project_id
}
# Minimal permissions for environment VMs
resource "google_project_iam_member" "env_vm_artifact_registry" {
count = !local.using_pre_created_service_accounts && local.environment_vm_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/artifactregistry.reader"
member = "serviceAccount:${local.environment_vm_sa_email}"
}
# Logging and monitoring permissions consolidated below
# Allow runner control plane to access the Redis credentials secret
resource "google_secret_manager_secret_iam_member" "runner_cp_redis_secret_access" {
project = var.project_id
secret_id = google_secret_manager_secret.redis_auth.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${local.runner_sa_email}"
}
# Allow runner to destroy secret versions (for cost optimization)
resource "google_project_iam_member" "runner_secret_version_manager" {
project = var.project_id
role = "roles/secretmanager.secretVersionManager"
member = "serviceAccount:${local.runner_sa_email}"
}
# RUNNER TOKEN SECRET MANAGER RESOURCE
# Create a secret for storing the runner token securely
resource "google_secret_manager_secret" "runner_token" {
secret_id = "${var.runner_name}-runner-token"
project = var.project_id
labels = merge(var.labels, {
purpose = "runner-authentication"
type = "api-token"
})
replication {
dynamic "user_managed" {
for_each = local.kms_key_name != null ? [1] : []
content {
replicas {
location = var.region
customer_managed_encryption {
kms_key_name = local.kms_key_name
}
}
}
}
dynamic "auto" {
for_each = local.kms_key_name == null ? [1] : []
content {}
}
}
}
# Create initial secret version with actual join token
resource "google_secret_manager_secret_version" "runner_token_initial" {
secret = google_secret_manager_secret.runner_token.id
# Store the actual join token provided via Terraform variable
secret_data = jsonencode({
join_token = var.runner_token
created_by = "terraform"
})
lifecycle {
ignore_changes = [secret_data]
}
}
# Allow runner control plane to access its own authentication token
resource "google_secret_manager_secret_iam_member" "runner_token_access" {
project = var.project_id
secret_id = google_secret_manager_secret.runner_token.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${local.runner_sa_email}"
}
# Allow runner to access metrics configuration secret
resource "google_secret_manager_secret_iam_member" "runner_metrics_access" {
project = var.project_id
secret_id = google_secret_manager_secret.metrics.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${local.runner_sa_email}"
}
# Allow proxy VM to access metrics configuration secret
resource "google_secret_manager_secret_iam_member" "proxy_metrics_access" {
project = var.project_id
secret_id = google_secret_manager_secret.metrics.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${local.proxy_vm_sa_email}"
}
# ✅ SECURE: Individual IAM members instead of bindings to prevent overwriting existing memberships
# IAM members are additive and safer than bindings which are authoritative
# Logging permissions - individual members for each service account
resource "google_project_iam_member" "runner_cp_logging" {
count = !local.using_pre_created_service_accounts && local.runner_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/logging.logWriter"
member = "serviceAccount:${local.runner_sa_email}"
}
resource "google_project_iam_member" "env_vm_logging" {
count = !local.using_pre_created_service_accounts && local.environment_vm_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/logging.logWriter"
member = "serviceAccount:${local.environment_vm_sa_email}"
}
# Monitoring permissions - individual members for each service account
resource "google_project_iam_member" "runner_cp_monitoring" {
count = !local.using_pre_created_service_accounts && local.runner_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/monitoring.metricWriter"
member = "serviceAccount:${local.runner_sa_email}"
}
resource "google_project_iam_member" "env_vm_monitoring" {
count = !local.using_pre_created_service_accounts && local.environment_vm_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/monitoring.metricWriter"
member = "serviceAccount:${local.environment_vm_sa_email}"
}
# Service account for proxy VMs
resource "google_service_account" "proxy_vm" {
count = var.pre_created_service_accounts.proxy_vm == "" ? 1 : 0
account_id = "${var.runner_name}-proxy-vm"
display_name = "Ona Proxy VM Service"
description = "Service account for Ona proxy VM instances"
project = var.project_id
}
# Custom role with minimal permissions for proxy VM
resource "google_project_iam_custom_role" "proxy_vm" {
count = var.pre_created_service_accounts.proxy_vm == "" ? 1 : 0
role_id = "${replace(var.runner_name, "-", "_")}_proxy_vm"
title = "Ona Proxy VM Minimal"
description = "Minimal permissions for Ona proxy VM instances"
project = var.project_id
permissions = [
# Basic instance metadata reading (for self-introspection)
"compute.instances.get",
"compute.instances.list",
# Network information reading (for proxy functionality)
"compute.networks.get",
"compute.subnetworks.get",
# Zone/region information (for location awareness)
"compute.zones.get",
"compute.regions.get",
# Project information (for service discovery)
"compute.projects.get",
]
}
# IAM permissions for proxy VM service
resource "google_project_iam_member" "proxy_vm_logging" {
count = !local.using_pre_created_service_accounts && local.proxy_vm_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/logging.logWriter"
member = "serviceAccount:${local.proxy_vm_sa_email}"
}
resource "google_project_iam_member" "proxy_vm_monitoring" {
count = !local.using_pre_created_service_accounts && local.proxy_vm_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/monitoring.metricWriter"
member = "serviceAccount:${local.proxy_vm_sa_email}"
}
# ✅ SECURE: Custom role with minimal permissions instead of compute.instanceAdmin.v1
resource "google_project_iam_member" "proxy_vm_compute" {
count = !local.using_pre_created_service_accounts && local.proxy_vm_sa_email != "" ? 1 : 0
project = var.project_id
role = var.pre_created_service_accounts.proxy_vm == "" ? google_project_iam_custom_role.proxy_vm[0].id : "projects/${var.project_id}/roles/${replace(var.runner_name, "-", "_")}_proxy_vm"
member = "serviceAccount:${local.proxy_vm_sa_email}"
depends_on = [
google_project_iam_custom_role.proxy_vm,
google_service_account.proxy_vm
]
}
resource "google_project_iam_member" "proxy_vm_artifact_registry" {
count = !local.using_pre_created_service_accounts && local.proxy_vm_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/artifactregistry.reader"
member = "serviceAccount:${local.proxy_vm_sa_email}"
}
resource "google_project_iam_member" "proxy_vm_cloud_run" {
count = !local.using_pre_created_service_accounts && local.proxy_vm_sa_email != "" ? 1 : 0
project = var.project_id
role = "roles/run.viewer"
member = "serviceAccount:${local.proxy_vm_sa_email}"
}
# Certificate Manager viewer role for external LB certificate access
resource "google_project_iam_member" "proxy_vm_certificate_manager" {
count = !local.using_pre_created_service_accounts && local.proxy_vm_sa_email != "" && var.loadbalancer_type == "external" ? 1 : 0
project = var.project_id
role = "roles/certificatemanager.viewer"
member = "serviceAccount:${local.proxy_vm_sa_email}"
}
# Secret Manager access for internal LB certificate secret
resource "google_secret_manager_secret_iam_member" "proxy_vm_certificate_secret_access" {
count = var.certificate_secret_id != "" ? 1 : 0
project = var.project_id
secret_id = split("/", var.certificate_secret_id)[3]
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${local.proxy_vm_sa_email}"
}
# GCS access for runner assets bucket (proxy VMs)
resource "google_storage_bucket_iam_member" "proxy_vm_runner_assets_access" {
bucket = google_storage_bucket.runner_assets.name
role = "roles/storage.objectViewer"
member = "serviceAccount:${local.proxy_vm_sa_email}"
}
# ================================
# AUDIT LOGGING CONFIGURATION
# ================================
# Enable comprehensive audit logging for Secret Manager operations
resource "google_project_iam_audit_config" "secret_manager_audit" {
project = var.project_id
service = "secretmanager.googleapis.com"
audit_log_config {
log_type = "DATA_READ" # This logs secret access
exempted_members = [] # No exemptions - log everything
}
audit_log_config {
log_type = "DATA_WRITE" # This logs secret creation/updates
}
audit_log_config {
log_type = "ADMIN_READ" # This logs metadata access
}
}
# Enable comprehensive audit logging for Compute Engine operations
resource "google_project_iam_audit_config" "compute_audit" {
project = var.project_id
service = "compute.googleapis.com"
audit_log_config {
log_type = "ADMIN_READ"
}
audit_log_config {
log_type = "DATA_WRITE"
}
}
# Enable comprehensive audit logging for IAM operations
resource "google_project_iam_audit_config" "iam_audit" {
project = var.project_id
service = "iam.googleapis.com"
audit_log_config {
log_type = "ADMIN_READ"
}
audit_log_config {
log_type = "DATA_WRITE"
}
}
# Enable audit logging for Storage operations
resource "google_project_iam_audit_config" "storage_audit" {
project = var.project_id
service = "storage.googleapis.com"
audit_log_config {
log_type = "DATA_READ"
exempted_members = []
}
audit_log_config {
log_type = "DATA_WRITE"
}
}
# GCS access for runner assets bucket (environment VMs)
resource "google_storage_bucket_iam_member" "env_vm_runner_assets_access" {
bucket = google_storage_bucket.runner_assets.name
role = "roles/storage.objectViewer"
member = "serviceAccount:${local.environment_vm_sa_email}"
}
# ================================
# KMS ACCESS FOR CMEK ENCRYPTION
# ================================
# KMS access for all service accounts when CMEK is enabled
resource "google_kms_crypto_key_iam_member" "runner_kms_access" {
count = (var.create_cmek || var.kms_key_name != null) && local.runner_sa_email != "" ? 1 : 0
crypto_key_id = local.kms_key_name
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:${local.runner_sa_email}"
}
resource "google_kms_crypto_key_iam_member" "environment_vm_kms_access" {
count = (var.create_cmek || var.kms_key_name != null) && local.environment_vm_sa_email != "" ? 1 : 0
crypto_key_id = local.kms_key_name
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:${local.environment_vm_sa_email}"
}
resource "google_kms_crypto_key_iam_member" "proxy_vm_kms_access" {
count = (var.create_cmek || var.kms_key_name != null) && local.proxy_vm_sa_email != "" ? 1 : 0
crypto_key_id = local.kms_key_name
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:${local.proxy_vm_sa_email}"
}
# ================================
# RUNNER actAs BINDINGS (PER-SA)
# ================================
# Grants the runner SA roles/iam.serviceAccountUser on the three SAs it
# attaches to instances and instance templates:
# - environment_vm_sa: attached to environment VMs.
# - proxy_vm_sa: attached to proxy VM instance templates.
# - runner_sa (self): attached to runner VM instance templates.
#
# Scoped per-SA so the runner cannot impersonate unrelated service
# accounts in the project.
#
# When pre_created_service_accounts is set the operator is responsible
# for granting roles/iam.serviceAccountUser on the relevant SAs to the
# runner SA out of band; the module does not manage IAM on SAs it did
# not create.
resource "google_service_account_iam_member" "runner_actas_runner" {
count = !local.using_pre_created_service_accounts && local.runner_sa_email != "" ? 1 : 0
service_account_id = local.runner_sa_name
role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${local.runner_sa_email}"
depends_on = [google_service_account.runner]
}
resource "google_service_account_iam_member" "runner_actas_environment_vm" {
count = !local.using_pre_created_service_accounts && local.runner_sa_email != "" && local.environment_vm_sa_email != "" ? 1 : 0
service_account_id = local.environment_vm_sa_name
role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${local.runner_sa_email}"
depends_on = [
google_service_account.runner,
google_service_account.environment_vm,
]
}
resource "google_service_account_iam_member" "runner_actas_proxy_vm" {
count = !local.using_pre_created_service_accounts && local.runner_sa_email != "" && local.proxy_vm_sa_email != "" ? 1 : 0
service_account_id = local.proxy_vm_sa_name
role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${local.runner_sa_email}"
depends_on = [
google_service_account.runner,
google_service_account.proxy_vm,
]
}