terraform-modules/app-group-admin-seed/seed.tf (247 lines of code) (raw):
/**
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Create admin project
module "admin-project" {
source = "terraform-google-modules/project-factory/google"
version = "11.3.0"
random_project_id = true
billing_account = var.billing_account
name = var.project_name
org_id = var.org_id
folder_id = var.folder_id
default_service_account = "keep"
activate_apis = [
"iam.googleapis.com",
"cloudresourcemanager.googleapis.com",
"cloudbuild.googleapis.com",
"secretmanager.googleapis.com",
"serviceusage.googleapis.com",
"cloudbilling.googleapis.com",
"cloudfunctions.googleapis.com",
"apikeys.googleapis.com"
]
}
// Create a new service account for Cloud Build to be used for the IaC pipeline
resource "google_service_account" "iac-sa" {
count = var.create_service_account ? 1 : 0
project = module.admin-project.project_id
account_id = "cloudbuild-iac"
display_name = "Cloud Build - Infra as Code service account"
}
// Grant project level roles to the Cloud Build SA for IaC pipeline
resource "google_project_iam_member" "iac-sa-cloudbuild-roles" {
project = module.admin-project.project_id
for_each = toset([
"roles/cloudbuild.builds.builder",
"roles/logging.logWriter",
"roles/owner"
])
role = each.key
member = "serviceAccount:${google_service_account.iac-sa[0].email}"
}
resource "google_storage_bucket" "iac-state-bucket" {
name = join("-", [module.admin-project.project_id, "infra-tf"])
project = module.admin-project.project_id
location = var.region
storage_class = null
uniform_bucket_level_access = true
labels = null
force_destroy = true
}
resource "google_storage_bucket_iam_member" "bucket-members-2" {
bucket = google_storage_bucket.iac-state-bucket.name
for_each = toset([
"roles/storage.objectCreator",
"roles/storage.objectViewer",
])
role = each.key
member = "serviceAccount:${google_service_account.iac-sa[0].email}"
}
// Create a new service account for Cloud Build to be used for the application CI/CD pipeline
resource "google_service_account" "cicd-sa" {
count = var.create_service_account ? 1 : 0
project = module.admin-project.project_id
account_id = "cloudbuild-cicd"
display_name = "Cloud Build - CI/CD service account"
}
resource "google_project_iam_member" "cicd-sa-cloudbuild" {
project = module.admin-project.project_id
role = "roles/cloudbuild.builds.builder"
member = "serviceAccount:${google_service_account.cicd-sa[0].email}"
}
resource "google_project_iam_member" "cicd-sa-cloudbuild-roles" {
project = module.admin-project.project_id
for_each = toset([
"roles/serviceusage.serviceUsageAdmin",
"roles/clouddeploy.operator",
"roles/cloudbuild.builds.builder",
"roles/secretmanager.secretAccessor",
"roles/secretmanager.admin",
"roles/serviceusage.apiKeysAdmin",
"roles/storage.admin",
"roles/artifactregistry.writer"
])
role = each.key
member = "serviceAccount:${google_service_account.cicd-sa[0].email}"
}
//Create SA for cloud deploy
resource "google_service_account" "cloud-deploy" {
count = var.create_service_account ? 1 : 0
project = module.admin-project.project_id
account_id = "clouddeploy"
display_name = "Cloud Deploy service account"
}
//Permission cloud deploy SA
resource "google_project_iam_member" "cloud-deploy-roles" {
project = module.admin-project.project_id
for_each = toset([
"roles/logging.logWriter",
"roles/clouddeploy.jobRunner",
"roles/storage.objectViewer"
])
role = each.key
member = "serviceAccount:${google_service_account.cloud-deploy[0].email}"
}
//Add the cloud deploy account to secretmanager so the cicd pipelines can pull and use it.
//Also provide access to Cloud Build cicd service account to look up that secret.
resource "google_secret_manager_secret" "clouddeploy-sa" {
secret_id = "clouddeploy-sa"
replication {
auto {}
}
project = module.admin-project.project_id
}
resource "google_secret_manager_secret_version" "clouddeploy-sa-secret" {
provider = google
secret = google_secret_manager_secret.clouddeploy-sa.id
secret_data = "${google_service_account.cloud-deploy[0].email}"
}
resource "google_secret_manager_secret_iam_member" "clouddeploy-sa-secret-access" {
provider = google
secret_id = google_secret_manager_secret.clouddeploy-sa.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.iac-sa[0].email}"
}
# Add CloudDeploy SA to GCS so the Cloud Function can provide it roles to deploy to GKE
resource "google_storage_bucket_object" "gke-deploy" {
count = length(var.trigger_buckets_dep)
name = "${var.app_name}-CloudDeploy-SA.txt"
content = google_service_account.cloud-deploy[0].email
bucket = var.trigger_buckets_dep[count.index]
}
# Add IaC and CICD SA to GCS so Cloud Function can provide it secret read roles
resource "google_storage_bucket_object" "secret-read-iac" {
name = "${var.app_name}-IaC-SA.txt"
content = google_service_account.iac-sa[0].email
bucket = var.trigger_bucket_sec
}
resource "time_sleep" "wait_20_seconds" {
create_duration = "20s"
}
resource "google_storage_bucket_object" "secret-read-cicd" {
name = "${var.app_name}-CICD-SA.txt"
content = google_service_account.cicd-sa[0].email
bucket = var.trigger_bucket_sec
depends_on = [google_storage_bucket_object.secret-read-cicd,time_sleep.wait_20_seconds]
}
# Add IaC SA to GCS so Cloud Function can provide it billing and project creator roles
resource "google_storage_bucket_object" "billing-user-iac" {
name = "${var.app_name}-IaC-SA.txt"
content = google_service_account.iac-sa[0].email
bucket = var.trigger_bucket_billing
}
resource "google_storage_bucket_object" "project-creator-iac" {
name = "${var.app_name}-IaC-SA.txt"
content = google_service_account.iac-sa[0].email
bucket = var.trigger_bucket_proj
}
//Allow Cloud Build IaC to impersonate cloud deploy SA to do the deployment
//TODO : not needed
//resource "google_service_account_iam_member" "iac-sa-impersonate-cd" {
// service_account_id = google_service_account.cloud-deploy[0].name
// role = "roles/iam.serviceAccountUser"
// member = "serviceAccount:${google_service_account.iac-sa[0].email}"
//}
//Allow Cloud Build IaC SA to impersonate Cloud Build CICD SA so the former can create a cloud build trigger and attach the latter to it,
resource "google_service_account_iam_member" "iac-sa-impersonate-cicd" {
service_account_id = google_service_account.cicd-sa[0].name
role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${google_service_account.iac-sa[0].email}"
}
//Allow Cloud Build CICD to impersonate cloud deploy SA to do the deployment
resource "google_service_account_iam_member" "cicd-sa-impersonate-cd" {
service_account_id = google_service_account.cloud-deploy[0].name
role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${google_service_account.cicd-sa[0].email}"
}
// Create new service accounts per environment for workload identity
locals {
wi_roles = ["roles/secretmanager.secretAccessor", "roles/secretmanager.admin", "roles/storage.objectCreator", "roles/storage.objectViewer", "roles/storage.admin"]
wi_roles_perm_combo = [
for pair in setproduct(local.wi_roles, var.env) : {
role = pair[0]
member = "serviceAccount:${google_service_account.workload-identity-sa[pair[1]].email}"
environment = pair[1]
}
]
wi_roles_mapping = {
for m in local.wi_roles_perm_combo : "${m.role} ${m.environment}" => m
}
}
// Create a new service account for workload identity
resource "google_service_account" "workload-identity-sa" {
for_each = toset(var.env)
project = module.admin-project.project_id
account_id = "${each.key}-wi-${var.app_name}"
display_name = "Workload Identity SA for ${each.key} environment"
}
// Grant project level roles to the workload identity SA
resource "google_project_iam_member" "workload-identity-sa-roles" {
project = module.admin-project.project_id
for_each = local.wi_roles_mapping
role = each.value.role
member = each.value.member
}
//Following section creates new secrets in application seed/admin project
resource "google_secret_manager_secret" "app-name" {
secret_id = "app-name"
replication {
auto {}
}
project = module.admin-project.project_id
}
resource "google_secret_manager_secret_version" "app-name-secret" {
secret = google_secret_manager_secret.app-name.id
secret_data = var.app_name
}
resource "google_secret_manager_secret_iam_member" "app-name-secret-access" {
secret_id = google_secret_manager_secret.app-name.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.cicd-sa[0].email}"
}
resource "google_secret_manager_secret" "region" {
secret_id = "region"
replication {
auto {}
}
project = module.admin-project.project_id
}
resource "google_secret_manager_secret_version" "region-secret" {
secret = google_secret_manager_secret.region.id
secret_data = var.region
}
resource "google_secret_manager_secret_iam_member" "region-secret-access" {
secret_id = google_secret_manager_secret.region.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.cicd-sa[0].email}"
}