google_permissions/pam_entitlement.tf (216 lines of code) (raw):
locals {
// this is the list of roles that we want all of the entitlements to have by default
default_admin_role_list = [
"roles/compute.admin",
"roles/dns.admin",
"roles/storage.admin",
"roles/spanner.admin",
"roles/cloudsql.admin",
"roles/secretmanager.secretAccessor", // added for OPST-1833
"roles/secretmanager.secretVersionAdder" // added for OPST-1833
]
// this is the role that we want to give to the JIT access users
jit_access_user_role = "organizations/442341870013/roles/JITAccessUser"
// Populate the environments list dynamically
environments = [
for environment in ["nonprod", "prod"] : environment
if(environment == "nonprod" && var.google_nonprod_project_id != "") || (environment == "prod" && var.google_prod_project_id != "")
]
additional_entitlements = flatten([
for environment in local.environments : [
for entitlement in try(var.entitlement_data.additional_entitlements, []) : {
key = "${var.app_code}/${environment}/${entitlement.name}"
tenant = var.app_code
project_id = environment == "nonprod" ? var.google_nonprod_project_id : var.google_prod_project_id
entitlement = entitlement
}
]
])
// The maximum allowed request duration is 4 hours no matter what the user specifies
// GCP allows this to be up to 12 hours, but we're going to limit it to 4 hours.
max_allowed_request_duration = 14400
// these are the perms REQUIRED for a user to be able to approve the entitlement
approvers_group_permissions = ["roles/privilegedaccessmanager.viewer"]
default_admin_entitlement_name = "admin-entitlement-01"
# Create the map with the hard-coded value and append the distinct principals
entitlement_wg_map = var.app_code != "" ? merge(
{
"default" : ["workgroup:${var.app_code}/developers"] # this the default value for the default system entitlement
},
{
for name, add_entitlement in try(local.additional_entitlements, []) : add_entitlement.key => add_entitlement.entitlement.principals
}
) : {}
# prep the module workgroup lookup list for the approval workflow
# approvals on default currently NYI
approver_wg_map = {
for e in try(local.additional_entitlements, []) :
e.key => e.entitlement.approval_workflow.principals
if can(e.entitlement.approval_workflow) && e.entitlement.approval_workflow != null
}
}
module "workgroup" {
source = "../mozilla_workgroup"
for_each = local.entitlement_wg_map
ids = each.value
}
locals {
module_outputs = {
for key, mod in module.workgroup : key => mod.members
}
}
module "approvals_workgroup" {
source = "../mozilla_workgroup"
for_each = local.approver_wg_map
ids = each.value
}
locals {
approvals_module_outputs = {
for key, mod in module.approvals_workgroup : key => mod.members
}
}
# now we handle the additional entitlements - these need to be created for BOTH environments
resource "google_privileged_access_manager_entitlement" "default_prod_entitlement" {
count = var.entitlement_enabled && (var.google_prod_project_id != "") ? 1 : 0
entitlement_id = local.default_admin_entitlement_name
location = "global"
max_request_duration = "${local.max_allowed_request_duration}s"
parent = "projects/${var.google_prod_project_id}"
requester_justification_config {
unstructured {}
}
eligible_users {
principals = local.module_outputs["default"]
}
privileged_access {
gcp_iam_access {
dynamic "role_bindings" {
for_each = setunion(try(var.entitlement_data.additional_roles, []), local.default_admin_role_list)
content {
role = role_bindings.value
}
}
resource = "//cloudresourcemanager.googleapis.com/projects/${var.google_prod_project_id}"
resource_type = "cloudresourcemanager.googleapis.com/Project"
}
}
# NYI for defaults - need to add to the schema.json file
#
# additional_notification_targets { # leave this empty for now
# admin_email_recipients = []
# requester_email_recipients = []
# }
#
# dynamic "approval_workflow" { //optional block
# for_each = var.number_of_approvals > 0 ? [1] : []
# content {
# manual_approvals {
# require_approver_justification = each.value.prod.entitlement. # leave this false for now
# steps {
# approvals_needed = 1 # this is all that's supported by google ATM
# approver_email_recipients = []
# approvers {
# principals =
# }
# }
# }
# }
# }
}
# now we handle the additional entitlements - these need to be created for BOTH environments
resource "google_privileged_access_manager_entitlement" "default_nonprod_entitlement" {
count = var.entitlement_enabled && (var.google_nonprod_project_id != "") ? 1 : 0
entitlement_id = local.default_admin_entitlement_name
location = "global"
max_request_duration = "${local.max_allowed_request_duration}s"
parent = "projects/${var.google_nonprod_project_id}"
requester_justification_config {
unstructured {}
}
eligible_users {
principals = local.module_outputs["default"]
}
privileged_access {
gcp_iam_access {
dynamic "role_bindings" {
for_each = setunion(try(var.entitlement_data.additional_roles, []), local.default_admin_role_list)
content {
role = role_bindings.value
}
}
resource = "//cloudresourcemanager.googleapis.com/projects/${var.google_nonprod_project_id}"
resource_type = "cloudresourcemanager.googleapis.com/Project"
}
}
# NYI for defaults - need to add to the schema.json file
#
# additional_notification_targets { # leave this empty for now
# admin_email_recipients = []
# requester_email_recipients = []
# }
#
# dynamic "approval_workflow" { //optional block
# for_each = var.number_of_approvals > 0 ? [1] : []
# content {
# manual_approvals {
# require_approver_justification = each.value.nonprod.entitlement. # leave this false for now
# steps {
# approvals_needed = 1 # this is all that's supported by google ATM
# approver_email_recipients = []
# approvers {
# principals =
# }
# }
# }
# }
# }
}
resource "google_privileged_access_manager_entitlement" "additional_entitlements" {
for_each = var.entitlement_enabled ? {
for entitlement in local.additional_entitlements : entitlement.key => entitlement
} : {}
entitlement_id = each.value.entitlement.name
location = "global"
max_request_duration = "${local.max_allowed_request_duration}s"
parent = "projects/${each.value.project_id}"
requester_justification_config {
unstructured {}
}
eligible_users {
principals = local.module_outputs[each.key]
}
privileged_access {
gcp_iam_access {
dynamic "role_bindings" {
for_each = each.value.entitlement.roles
content {
role = role_bindings.value
}
}
resource = "//cloudresourcemanager.googleapis.com/projects/${each.value.project_id}"
resource_type = "cloudresourcemanager.googleapis.com/Project"
}
}
dynamic "approval_workflow" { //optional block
for_each = try(length(each.value.entitlement.approval_workflow.principals), 0) > 0 ? [1] : []
content {
manual_approvals {
require_approver_justification = try(each.value.entitlement.approval_workflow.require_approver_justification, false) # leave this false for now
steps {
approvals_needed = 1 # this is all that's supported by google ATM
approver_email_recipients = []
approvers {
principals = local.approvals_module_outputs[each.key]
}
}
}
}
}
}
// Needed for slack notifications for PAM activity from cloudasset to a central topic
// The project feed requires that cloudasset API is not only set up but also that the service account is created
// Create a service account for the cloudasset API
// ref: https://stackoverflow.com/questions/63785247/gcp-managed-service-account-is-not-created-for-cloud-asset-api
//
resource "google_project_service_identity" "cloud_asset_sa" {
for_each = var.entitlement_enabled && var.entitlement_slack_topic != "" ? toset(local.environments) : []
provider = google-beta
project = each.value == "nonprod" ? var.google_nonprod_project_id : var.google_prod_project_id
service = "cloudasset.googleapis.com"
}
// Create a feed that sends notifications about network resource updates.
resource "google_cloud_asset_project_feed" "project_feed" {
for_each = var.entitlement_enabled && var.entitlement_slack_topic != "" ? toset(local.environments) : []
project = each.value == "nonprod" ? var.google_nonprod_project_id : var.google_prod_project_id
feed_id = var.feed_id
content_type = "RESOURCE"
asset_types = [
"privilegedaccessmanager.googleapis.com/Grant",
]
feed_output_config {
pubsub_destination {
topic = var.entitlement_slack_topic
}
}
}
# custom role for JIT access - created at the org level
resource "google_project_iam_member" "developers_jitaccess_nonprod" {
for_each = var.entitlement_enabled && var.google_nonprod_project_id != "" ? toset(module.developers_workgroup.members) : toset([])
project = var.google_nonprod_project_id
role = local.jit_access_user_role
member = each.value
}
resource "google_project_iam_member" "developers_jitaccess_prod" {
for_each = var.entitlement_enabled && var.google_prod_project_id != "" ? toset(module.developers_workgroup.members) : toset([])
project = var.google_prod_project_id
role = local.jit_access_user_role
member = each.value
}