IAM interface refactor (#1595)
* IAM modules refactor proposal * policy * subheading * Update 20230816-iam-refactor.md * log Julio's +1 * data-catalog-policy-tag * dataproc * dataproc * folder * folder * folder * folder * project * better filtering in test examples * project * folder * folder * organization * fix variable descriptions * kms * net-vpc * dataplex-datascan * modules/iam-service-account * modules/source-repository/ * blueprints/cloud-operations/vm-migration/ * blueprints/third-party-solutions/wordpress * dataplex-datascan * blueprints/cloud-operations/workload-identity-federation * blueprints/data-solutions/cloudsql-multiregion/ * blueprints/data-solutions/composer-2 * Update 20230816-iam-refactor.md * Update 20230816-iam-refactor.md * capture discussion in architectural doc * update variable names and refactor proposal * project * blueprints first round * folder * organization * data-catalog-policy-tag * re-enable folder inventory * project module style fix * dataproc * source-repository * source-repository tests * dataplex-datascan * dataplex-datascan tests * net-vpc * net-vpc test examples * iam-service-account * iam-service-account test examples * kms * boilerplate * tfdoc * fix module tests * more blueprint fixes * fix typo in data blueprints * incomplete refactor of data platform foundations * tfdoc * data platform foundation * refactor data platform foundation iam locals * remove redundant example test * shielded folder fix * fix typo * project factory * project factory outputs * tfdoc * test workflow: less verbose tests, fix tf version * re-enable -vv, shorter traceback, fix action version * ignore github extension warning, re-enable action version * fast bootstrap IAM, untested * bootstrap stage IAM fixes * stage 0 tests * fast stage 1 * tenant stage 1 * minor changes to fast stage 0 and 1 * fast security stage * fast mt stage 0 * fast mt stage 0 * fast pf
This commit is contained in:
committed by
GitHub
parent
6eeba5e599
commit
819894d2ba
@@ -10,11 +10,7 @@ This module implements the creation and management of one GCP project including
|
||||
- [IAM](#iam)
|
||||
- [Authoritative IAM](#authoritative-iam)
|
||||
- [Additive IAM](#additive-iam)
|
||||
- [Additive IAM by Role](#additive-iam-by-role)
|
||||
- [Additive IAM by Principal](#additive-iam-by-principal)
|
||||
- [Additive IAM by Binding](#additive-iam-by-binding)
|
||||
- [Service Identities and Authoritative IAM](#service-identities-and-authoritative-iam)
|
||||
- [Using Shortcodes for Service Identities in Additive Iam](#using-shortcodes-for-service-identities-in-additive-iam)
|
||||
- [Service Identities Requiring Manual Iam Grants](#service-identities-requiring-manual-iam-grants)
|
||||
- [Shared VPC](#shared-vpc)
|
||||
- [Organization Policies](#organization-policies)
|
||||
@@ -48,13 +44,13 @@ module "project" {
|
||||
|
||||
## IAM
|
||||
|
||||
IAM is managed via several variables that implement different levels of control:
|
||||
IAM is managed via several variables that implement different features and levels of control:
|
||||
|
||||
- `group_iam` and `iam` configure authoritative bindings that manage individual roles exclusively, mapping to the [`google_project_iam_binding`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_binding) resource
|
||||
- `iam_additive`, `iam_additive_members` and `iam_members` configure additive bindings that only manage individual role/member pairs, mapping to the [`google_project_iam_member`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_member) resource
|
||||
- `iam_policy` which controls the entire IAM policy for the project, where any binding created outside this module (eg in the console) will be removed at each `terraform apply` cycle regardless of the role
|
||||
- `iam` and `group_iam` configure authoritative bindings that manage individual roles exclusively, and are internally merged
|
||||
- `iam_bindings` configure authoritative bindings with optional support for conditions, and are not internally merged with the previous two variables
|
||||
- `iam_bindings_additive` configure additive bindings via individual role/member pairs with optional support conditions
|
||||
|
||||
The authoritative and additive approaches can be used together, provided different roles are managed by each. The IAM policy is incompatible with the other approaches, and must be used with extreme care.
|
||||
The authoritative and additive approaches can be used together, provided different roles are managed by each. Some care must also be taken with the `groups_iam` variable to ensure that variable keys are static values, so that Terraform is able to compute the dependency graph.
|
||||
|
||||
Be mindful about service identity roles when using authoritative IAM, as you might inadvertently remove a role from a [service identity](https://cloud.google.com/iam/docs/service-account-types#google-managed) or default service account. For example, using `roles/editor` with `iam` or `group_iam` will remove the default permissions for the Cloud Services identity. A simple workaround for these scenarios is described below.
|
||||
|
||||
@@ -107,75 +103,24 @@ module "project" {
|
||||
# tftest modules=1 resources=5 inventory=iam-group.yaml
|
||||
```
|
||||
|
||||
### Additive IAM
|
||||
|
||||
Additive IAM is typically used where bindings for specific roles are controlled by different modules or in different Terraform stages. One example is when the project is created by one team but a different team manages service account creation for the project, and some of the project-level roles overlap in the two configurations.
|
||||
|
||||
#### Additive IAM by Role
|
||||
|
||||
Additive IAM is supported via the `iam_additive` variable which is keyed by role:
|
||||
The `iam_bindings` variable behaves like a more verbose version of `iam`, and allows setting binding-level IAM conditions.
|
||||
|
||||
```hcl
|
||||
module "project" {
|
||||
source = "./fabric/modules/project"
|
||||
name = "project-example"
|
||||
iam_additive = {
|
||||
"roles/viewer" = [
|
||||
"group:one@example.org",
|
||||
"group:two@xample.org"
|
||||
],
|
||||
"roles/storage.objectAdmin" = [
|
||||
"group:two@example.org"
|
||||
],
|
||||
"roles/owner" = [
|
||||
"group:three@example.org"
|
||||
],
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=5 inventory=iam-additive.yaml
|
||||
```
|
||||
|
||||
#### Additive IAM by Principal
|
||||
|
||||
Additive IAM is also supported via the `iam_additive_members` variable which is keyed by principal:
|
||||
|
||||
```hcl
|
||||
module "project" {
|
||||
source = "./fabric/modules/project"
|
||||
name = "project-example"
|
||||
iam_additive_members = {
|
||||
"user:one@example.org" = ["roles/owner"]
|
||||
"user:two@example.org" = ["roles/owner", "roles/editor"]
|
||||
}
|
||||
|
||||
}
|
||||
# tftest modules=1 resources=4 inventory=iam-additive-members.yaml
|
||||
```
|
||||
|
||||
#### Additive IAM by Binding
|
||||
|
||||
When the above approaches to additive IAM are unworkable due to dynamically generated principals, the `iam_members` variable allows specifying individual role/principal pairs using arbitrary keys. This IAM variable also supports conditions.
|
||||
|
||||
```hcl
|
||||
module "project" {
|
||||
source = "./fabric/modules/project"
|
||||
name = "project-example"
|
||||
iam_members = {
|
||||
one-owner = {
|
||||
member = "user:one@example.org"
|
||||
role = "roles/owner"
|
||||
}
|
||||
two-viewer = {
|
||||
member = "user:two@example.org"
|
||||
role = "roles/viewer"
|
||||
}
|
||||
two-compute-admin = {
|
||||
member = "user:two@example.org"
|
||||
role = "roles/compute.admin"
|
||||
}
|
||||
one-delegated-grant = {
|
||||
member = "user:one@example.org"
|
||||
role = "roles/resourcemanager.projectIamAdmin"
|
||||
source = "./fabric/modules/project"
|
||||
billing_account = "123456-123456-123456"
|
||||
name = "project-example"
|
||||
parent = "folders/1234567890"
|
||||
prefix = "foo"
|
||||
services = [
|
||||
"container.googleapis.com",
|
||||
"stackdriver.googleapis.com"
|
||||
]
|
||||
iam_bindings = {
|
||||
"roles/resourcemanager.projectIamAdmin" = {
|
||||
members = [
|
||||
"group:test-admins@example.org"
|
||||
]
|
||||
condition = {
|
||||
title = "delegated_network_user_one"
|
||||
expression = <<-END
|
||||
@@ -189,7 +134,30 @@ module "project" {
|
||||
}
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=5 inventory=iam-members.yaml
|
||||
# tftest modules=1 resources=4 inventory=iam-bindings.yaml
|
||||
```
|
||||
|
||||
### Additive IAM
|
||||
|
||||
Additive IAM is typically used where bindings for specific roles are controlled by different modules or in different Terraform stages. One common example is a host project managed by the networking team, and a project factory that manages service projects and needs to assign `roles/networkUser` on the host project.
|
||||
|
||||
The `iam_bindings_additive` variable allows setting individual role/principal binding pairs. Support for IAM conditions is implemented like for `iam_bindings` above.
|
||||
|
||||
```hcl
|
||||
module "project" {
|
||||
source = "./fabric/modules/project"
|
||||
name = "project-1"
|
||||
services = [
|
||||
"compute.googleapis.com"
|
||||
]
|
||||
iam_bindings_additive = {
|
||||
group-owner = {
|
||||
member = "group:p1-owners@example.org"
|
||||
role = "roles/owner"
|
||||
}
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=3 inventory=iam-bindings-additive.yaml
|
||||
```
|
||||
|
||||
### Service Identities and Authoritative IAM
|
||||
@@ -214,29 +182,6 @@ module "project" {
|
||||
# tftest modules=1 resources=2
|
||||
```
|
||||
|
||||
### Using Shortcodes for Service Identities in Additive Iam
|
||||
|
||||
Most Service Identities contains project number in their e-mail address and this prevents additive IAM to work, as these values are not known at moment of execution of `terraform plan` (its not an issue for authoritative IAM). To refer current project Service Identities you may use shortcodes for Service Identities similarly as for `service_identity_iam` when configuring Shared VPC.
|
||||
|
||||
```hcl
|
||||
module "project" {
|
||||
source = "./fabric/modules/project"
|
||||
name = "project-example"
|
||||
|
||||
services = [
|
||||
"run.googleapis.com",
|
||||
"container.googleapis.com",
|
||||
]
|
||||
|
||||
iam_additive = {
|
||||
"roles/editor" = ["cloudservices"]
|
||||
"roles/vpcaccess.user" = ["cloudrun"]
|
||||
"roles/container.hostServiceAgentUser" = ["container-engine"]
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=6
|
||||
```
|
||||
|
||||
### Service Identities Requiring Manual Iam Grants
|
||||
|
||||
The module will create service identities at project creation instead of creating of them at the time of first use. This allows granting these service identities roles in other projects, something which is usually necessary in a Shared VPC context.
|
||||
@@ -544,33 +489,6 @@ module "project" {
|
||||
# tftest modules=1 resources=3 inventory=logging-data-access.yaml
|
||||
```
|
||||
|
||||
While this sets an authoritative policies that has exclusive control of both IAM bindings for all roles and data access log configuration, and should be used with extreme care:
|
||||
|
||||
```hcl
|
||||
module "project" {
|
||||
source = "./fabric/modules/project"
|
||||
name = "my-project"
|
||||
billing_account = "123456-123456-123456"
|
||||
parent = "folders/1234567890"
|
||||
iam_policy = {
|
||||
"roles/owner" = ["group:org-admins@example.com"]
|
||||
"roles/resourcemanager.folderAdmin" = ["group:org-admins@example.com"]
|
||||
"roles/resourcemanager.organizationAdmin" = ["group:org-admins@example.com"]
|
||||
"roles/resourcemanager.projectCreator" = ["group:org-admins@example.com"]
|
||||
}
|
||||
logging_data_access = {
|
||||
allServices = {
|
||||
ADMIN_READ = ["group:organization-admins@example.org"]
|
||||
}
|
||||
"storage.googleapis.com" = {
|
||||
DATA_READ = []
|
||||
DATA_WRITE = []
|
||||
}
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=2 inventory=iam-policy.yaml
|
||||
```
|
||||
|
||||
## Cloud Kms Encryption Keys
|
||||
|
||||
The module offers a simple, centralized way to assign `roles/cloudkms.cryptoKeyEncrypterDecrypter` to service identities.
|
||||
@@ -655,7 +573,7 @@ output "compute_robot" {
|
||||
|
||||
| name | description | resources |
|
||||
|---|---|---|
|
||||
| [iam.tf](./iam.tf) | Generic and OSLogin-specific IAM bindings and roles. | <code>google_project_iam_binding</code> · <code>google_project_iam_custom_role</code> · <code>google_project_iam_member</code> · <code>google_project_iam_policy</code> |
|
||||
| [iam.tf](./iam.tf) | Generic and OSLogin-specific IAM bindings and roles. | <code>google_project_iam_binding</code> · <code>google_project_iam_custom_role</code> · <code>google_project_iam_member</code> |
|
||||
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | <code>google_bigquery_dataset_iam_member</code> · <code>google_logging_project_exclusion</code> · <code>google_logging_project_sink</code> · <code>google_project_iam_audit_config</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
|
||||
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_compute_project_metadata_item</code> · <code>google_essential_contacts_contact</code> · <code>google_monitoring_monitored_project</code> · <code>google_project</code> · <code>google_project_service</code> · <code>google_resource_manager_lien</code> |
|
||||
| [organization-policies.tf](./organization-policies.tf) | Project-level organization policies. | <code>google_org_policy_policy</code> |
|
||||
@@ -671,42 +589,38 @@ output "compute_robot" {
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [name](variables.tf#L176) | Project name and id suffix. | <code>string</code> | ✓ | |
|
||||
| [name](variables.tf#L185) | Project name and id suffix. | <code>string</code> | ✓ | |
|
||||
| [auto_create_network](variables.tf#L17) | Whether to create the default network for the project. | <code>bool</code> | | <code>false</code> |
|
||||
| [billing_account](variables.tf#L23) | Billing account id. | <code>string</code> | | <code>null</code> |
|
||||
| [contacts](variables.tf#L29) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [custom_roles](variables.tf#L36) | Map of role name => list of permissions to create in this project. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [default_service_account](variables.tf#L43) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | <code>string</code> | | <code>"keep"</code> |
|
||||
| [descriptive_name](variables.tf#L49) | Name of the project name. Used for project name instead of `name` variable. | <code>string</code> | | <code>null</code> |
|
||||
| [group_iam](variables.tf#L55) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam](variables.tf#L62) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_additive](variables.tf#L69) | IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_additive_members](variables.tf#L76) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_members](variables.tf#L82) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_policy](variables.tf#L97) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | <code>map(list(string))</code> | | <code>null</code> |
|
||||
| [labels](variables.tf#L103) | Resource labels. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [lien_reason](variables.tf#L110) | If non-empty, creates a project lien with this description. | <code>string</code> | | <code>""</code> |
|
||||
| [logging_data_access](variables.tf#L116) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map(map(list(string)))</code> | | <code>{}</code> |
|
||||
| [logging_exclusions](variables.tf#L131) | Logging exclusions for this project in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L138) | Logging sinks to create for this project. | <code title="map(object({ bq_partitioned_table = optional(bool) description = optional(string) destination = string disabled = optional(bool, false) exclusions = optional(map(string), {}) filter = string iam = optional(bool, true) type = string unique_writer = optional(bool) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [metric_scopes](variables.tf#L169) | List of projects that will act as metric scopes for this project. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [org_policies](variables.tf#L181) | Organization policies applied to this project keyed by policy name. | <code title="map(object({ inherit_from_parent = optional(bool) # for list policies only. reset = optional(bool) rules = optional(list(object({ allow = optional(object({ all = optional(bool) values = optional(list(string)) })) deny = optional(object({ all = optional(bool) values = optional(list(string)) })) enforce = optional(bool) # for boolean policies only. condition = optional(object({ description = optional(string) expression = optional(string) location = optional(string) title = optional(string) }), {}) })), []) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [org_policies_data_path](variables.tf#L208) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
|
||||
| [oslogin](variables.tf#L214) | Enable OS Login. | <code>bool</code> | | <code>false</code> |
|
||||
| [oslogin_admins](variables.tf#L220) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [oslogin_users](variables.tf#L228) | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [parent](variables.tf#L235) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> |
|
||||
| [prefix](variables.tf#L245) | Optional prefix used to generate project id and name. | <code>string</code> | | <code>null</code> |
|
||||
| [project_create](variables.tf#L255) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> |
|
||||
| [service_config](variables.tf#L261) | Configure service API activation. | <code title="object({ disable_on_destroy = bool disable_dependent_services = bool })">object({…})</code> | | <code title="{ disable_on_destroy = false disable_dependent_services = false }">{…}</code> |
|
||||
| [service_encryption_key_ids](variables.tf#L273) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [service_perimeter_bridges](variables.tf#L280) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list(string)</code> | | <code>null</code> |
|
||||
| [service_perimeter_standard](variables.tf#L287) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> |
|
||||
| [services](variables.tf#L293) | Service APIs to enable. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [shared_vpc_host_config](variables.tf#L299) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object({ enabled = bool service_projects = optional(list(string), []) })">object({…})</code> | | <code>null</code> |
|
||||
| [shared_vpc_service_config](variables.tf#L308) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object({ host_project = string service_identity_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) })">object({…})</code> | | <code title="{ host_project = null }">{…}</code> |
|
||||
| [skip_delete](variables.tf#L330) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> |
|
||||
| [tag_bindings](variables.tf#L336) | Tag bindings for this project, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
| [compute_metadata](variables.tf#L29) | Optional compute metadata key/values. Only usable if compute API has been enabled. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [contacts](variables.tf#L36) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [custom_roles](variables.tf#L43) | Map of role name => list of permissions to create in this project. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [default_service_account](variables.tf#L50) | Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`. | <code>string</code> | | <code>"keep"</code> |
|
||||
| [descriptive_name](variables.tf#L63) | Name of the project name. Used for project name instead of `name` variable. | <code>string</code> | | <code>null</code> |
|
||||
| [group_iam](variables.tf#L69) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam](variables.tf#L76) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L83) | Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}. | <code title="map(object({ members = list(string) condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_bindings_additive](variables.tf#L97) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [labels](variables.tf#L112) | Resource labels. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [lien_reason](variables.tf#L119) | If non-empty, creates a project lien with this description. | <code>string</code> | | <code>null</code> |
|
||||
| [logging_data_access](variables.tf#L125) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map(map(list(string)))</code> | | <code>{}</code> |
|
||||
| [logging_exclusions](variables.tf#L140) | Logging exclusions for this project in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L147) | Logging sinks to create for this project. | <code title="map(object({ bq_partitioned_table = optional(bool) description = optional(string) destination = string disabled = optional(bool, false) exclusions = optional(map(string), {}) filter = string iam = optional(bool, true) type = string unique_writer = optional(bool) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [metric_scopes](variables.tf#L178) | List of projects that will act as metric scopes for this project. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [org_policies](variables.tf#L190) | Organization policies applied to this project keyed by policy name. | <code title="map(object({ inherit_from_parent = optional(bool) # for list policies only. reset = optional(bool) rules = optional(list(object({ allow = optional(object({ all = optional(bool) values = optional(list(string)) })) deny = optional(object({ all = optional(bool) values = optional(list(string)) })) enforce = optional(bool) # for boolean policies only. condition = optional(object({ description = optional(string) expression = optional(string) location = optional(string) title = optional(string) }), {}) })), []) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [org_policies_data_path](variables.tf#L217) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
|
||||
| [parent](variables.tf#L223) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> |
|
||||
| [prefix](variables.tf#L233) | Optional prefix used to generate project id and name. | <code>string</code> | | <code>null</code> |
|
||||
| [project_create](variables.tf#L243) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> |
|
||||
| [service_config](variables.tf#L249) | Configure service API activation. | <code title="object({ disable_on_destroy = bool disable_dependent_services = bool })">object({…})</code> | | <code title="{ disable_on_destroy = false disable_dependent_services = false }">{…}</code> |
|
||||
| [service_encryption_key_ids](variables.tf#L261) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [service_perimeter_bridges](variables.tf#L268) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list(string)</code> | | <code>null</code> |
|
||||
| [service_perimeter_standard](variables.tf#L275) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> |
|
||||
| [services](variables.tf#L281) | Service APIs to enable. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [shared_vpc_host_config](variables.tf#L287) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object({ enabled = bool service_projects = optional(list(string), []) })">object({…})</code> | | <code>null</code> |
|
||||
| [shared_vpc_service_config](variables.tf#L296) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object({ host_project = string service_identity_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) })">object({…})</code> | | <code title="{ host_project = null }">{…}</code> |
|
||||
| [skip_delete](variables.tf#L318) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> |
|
||||
| [tag_bindings](variables.tf#L324) | Tag bindings for this project, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
|
||||
# IAM notes:
|
||||
# - external users need to have accepted the invitation email to join
|
||||
# - oslogin roles also require role to list instances
|
||||
# - additive (non-authoritative) roles might fail due to dynamic values
|
||||
|
||||
locals {
|
||||
_group_iam_roles = distinct(flatten(values(var.group_iam)))
|
||||
@@ -28,16 +26,6 @@ locals {
|
||||
for k, v in var.group_iam : "group:${k}" if try(index(v, r), null) != null
|
||||
]
|
||||
}
|
||||
_iam_additive_pairs = flatten([
|
||||
for role, members in var.iam_additive : [
|
||||
for member in members : { role = role, member = member }
|
||||
]
|
||||
])
|
||||
_iam_additive_member_pairs = flatten([
|
||||
for member, roles in var.iam_additive_members : [
|
||||
for role in roles : { role = role, member = member }
|
||||
]
|
||||
])
|
||||
iam = {
|
||||
for role in distinct(concat(keys(var.iam), keys(local._group_iam))) :
|
||||
role => concat(
|
||||
@@ -45,21 +33,6 @@ locals {
|
||||
try(local._group_iam[role], [])
|
||||
)
|
||||
}
|
||||
iam_additive = {
|
||||
for pair in concat(local._iam_additive_pairs, local._iam_additive_member_pairs) :
|
||||
"${pair.role}-${pair.member}" => {
|
||||
role = pair.role
|
||||
member = (
|
||||
pair.member == "cloudservices"
|
||||
? "serviceAccount:${local.service_account_cloud_services}"
|
||||
: pair.member == "default-compute"
|
||||
? "serviceAccount:${local.service_accounts_default.compute}"
|
||||
: pair.member == "default-gae"
|
||||
? "serviceAccount:${local.service_accounts_default.gae}"
|
||||
: try("serviceAccount:${local.service_accounts_robots[pair.member]}", pair.member)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_project_iam_custom_role" "roles" {
|
||||
@@ -82,23 +55,27 @@ resource "google_project_iam_binding" "authoritative" {
|
||||
]
|
||||
}
|
||||
|
||||
resource "google_project_iam_member" "additive" {
|
||||
for_each = (
|
||||
length(var.iam_additive) + length(var.iam_additive_members) > 0
|
||||
? local.iam_additive
|
||||
: {}
|
||||
)
|
||||
project = local.project.project_id
|
||||
role = each.value.role
|
||||
member = each.value.member
|
||||
resource "google_project_iam_binding" "bindings" {
|
||||
for_each = var.iam_bindings
|
||||
project = local.project.project_id
|
||||
role = each.key
|
||||
members = each.value.members
|
||||
dynamic "condition" {
|
||||
for_each = each.value.condition == null ? [] : [""]
|
||||
content {
|
||||
expression = each.value.condition.expression
|
||||
title = each.value.condition.title
|
||||
description = each.value.condition.description
|
||||
}
|
||||
}
|
||||
depends_on = [
|
||||
google_project_service.project_services,
|
||||
google_project_iam_custom_role.roles
|
||||
]
|
||||
}
|
||||
|
||||
resource "google_project_iam_member" "members" {
|
||||
for_each = var.iam_members
|
||||
resource "google_project_iam_member" "bindings" {
|
||||
for_each = var.iam_bindings_additive
|
||||
project = local.project.project_id
|
||||
role = each.value.role
|
||||
member = each.value.member
|
||||
@@ -115,62 +92,3 @@ resource "google_project_iam_member" "members" {
|
||||
google_project_iam_custom_role.roles
|
||||
]
|
||||
}
|
||||
|
||||
resource "google_project_iam_member" "oslogin_iam_serviceaccountuser" {
|
||||
for_each = var.oslogin ? toset(distinct(concat(var.oslogin_admins, var.oslogin_users))) : toset([])
|
||||
project = local.project.project_id
|
||||
role = "roles/iam.serviceAccountUser"
|
||||
member = each.value
|
||||
}
|
||||
|
||||
resource "google_project_iam_member" "oslogin_compute_viewer" {
|
||||
for_each = var.oslogin ? toset(distinct(concat(var.oslogin_admins, var.oslogin_users))) : toset([])
|
||||
project = local.project.project_id
|
||||
role = "roles/compute.viewer"
|
||||
member = each.value
|
||||
}
|
||||
|
||||
resource "google_project_iam_member" "oslogin_admins" {
|
||||
for_each = var.oslogin ? toset(var.oslogin_admins) : toset([])
|
||||
project = local.project.project_id
|
||||
role = "roles/compute.osAdminLogin"
|
||||
member = each.value
|
||||
}
|
||||
|
||||
resource "google_project_iam_member" "oslogin_users" {
|
||||
for_each = var.oslogin ? toset(var.oslogin_users) : toset([])
|
||||
project = local.project.project_id
|
||||
role = "roles/compute.osLogin"
|
||||
member = each.value
|
||||
}
|
||||
|
||||
resource "google_project_iam_policy" "authoritative" {
|
||||
count = var.iam_policy != null ? 1 : 0
|
||||
project = local.project.project_id
|
||||
policy_data = data.google_iam_policy.authoritative.0.policy_data
|
||||
}
|
||||
|
||||
data "google_iam_policy" "authoritative" {
|
||||
count = var.iam_policy != null ? 1 : 0
|
||||
dynamic "binding" {
|
||||
for_each = try(var.iam_policy, {})
|
||||
content {
|
||||
role = binding.key
|
||||
members = binding.value
|
||||
}
|
||||
}
|
||||
dynamic "audit_config" {
|
||||
for_each = var.logging_data_access
|
||||
content {
|
||||
service = audit_config.key
|
||||
dynamic "audit_log_configs" {
|
||||
for_each = audit_config.value
|
||||
iterator = config
|
||||
content {
|
||||
log_type = config.key
|
||||
exempted_members = config.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,9 @@ locals {
|
||||
}
|
||||
|
||||
resource "google_project_iam_audit_config" "default" {
|
||||
for_each = (
|
||||
var.iam_policy == null ? var.logging_data_access : {}
|
||||
)
|
||||
project = local.project.project_id
|
||||
service = each.key
|
||||
for_each = var.logging_data_access
|
||||
project = local.project.project_id
|
||||
service = each.key
|
||||
dynamic "audit_log_config" {
|
||||
for_each = each.value
|
||||
iterator = config
|
||||
@@ -70,7 +68,8 @@ resource "google_logging_project_sink" "sink" {
|
||||
|
||||
depends_on = [
|
||||
google_project_iam_binding.authoritative,
|
||||
google_project_iam_member.additive
|
||||
google_project_iam_binding.bindings,
|
||||
google_project_iam_member.bindings
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -61,17 +61,18 @@ resource "google_project_service" "project_services" {
|
||||
disable_dependent_services = var.service_config.disable_dependent_services
|
||||
}
|
||||
|
||||
resource "google_compute_project_metadata_item" "oslogin_meta" {
|
||||
count = var.oslogin ? 1 : 0
|
||||
project = local.project.project_id
|
||||
key = "enable-oslogin"
|
||||
value = "TRUE"
|
||||
# depend on services or it will fail on destroy
|
||||
resource "google_compute_project_metadata_item" "default" {
|
||||
for_each = (
|
||||
contains(var.services, "compute.googleapis.com") ? var.compute_metadata : {}
|
||||
)
|
||||
project = local.project.project_id
|
||||
key = each.key
|
||||
value = each.value
|
||||
depends_on = [google_project_service.project_services]
|
||||
}
|
||||
|
||||
resource "google_resource_manager_lien" "lien" {
|
||||
count = var.lien_reason != "" ? 1 : 0
|
||||
count = var.lien_reason != null ? 1 : 0
|
||||
parent = "projects/${local.project.number}"
|
||||
restrictions = ["resourcemanager.projects.delete"]
|
||||
origin = "created-by-terraform"
|
||||
|
||||
@@ -26,6 +26,13 @@ variable "billing_account" {
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "compute_metadata" {
|
||||
description = "Optional compute metadata key/values. Only usable if compute API has been enabled."
|
||||
type = map(string)
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "contacts" {
|
||||
description = "List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES."
|
||||
type = map(list(string))
|
||||
@@ -44,6 +51,13 @@ variable "default_service_account" {
|
||||
description = "Project default service account setting: can be one of `delete`, `deprivilege`, `disable`, or `keep`."
|
||||
default = "keep"
|
||||
type = string
|
||||
validation {
|
||||
condition = (
|
||||
var.default_service_account == null ||
|
||||
contains(["delete", "deprivilege", "disable", "keep"], var.default_service_account)
|
||||
)
|
||||
error_message = "Only `delete`, `deprivilege`, `disable`, or `keep` are supported."
|
||||
}
|
||||
}
|
||||
|
||||
variable "descriptive_name" {
|
||||
@@ -60,27 +74,28 @@ variable "group_iam" {
|
||||
}
|
||||
|
||||
variable "iam" {
|
||||
description = "IAM bindings in {ROLE => [MEMBERS]} format."
|
||||
description = "Authoritative IAM bindings in {ROLE => [MEMBERS]} format."
|
||||
type = map(list(string))
|
||||
default = {}
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "iam_additive" {
|
||||
description = "IAM additive bindings in {ROLE => [MEMBERS]} format."
|
||||
type = map(list(string))
|
||||
default = {}
|
||||
nullable = false
|
||||
variable "iam_bindings" {
|
||||
description = "Authoritative IAM bindings in {ROLE => {members = [], condition = {}}}."
|
||||
type = map(object({
|
||||
members = list(string)
|
||||
condition = optional(object({
|
||||
expression = string
|
||||
title = string
|
||||
description = optional(string)
|
||||
}))
|
||||
}))
|
||||
nullable = false
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "iam_additive_members" {
|
||||
description = "IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values."
|
||||
type = map(list(string))
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "iam_members" {
|
||||
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
|
||||
variable "iam_bindings_additive" {
|
||||
description = "Individual additive IAM bindings. Keys are arbitrary."
|
||||
type = map(object({
|
||||
member = string
|
||||
role = string
|
||||
@@ -94,12 +109,6 @@ variable "iam_members" {
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "iam_policy" {
|
||||
description = "IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution."
|
||||
type = map(list(string))
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "labels" {
|
||||
description = "Resource labels."
|
||||
type = map(string)
|
||||
@@ -110,7 +119,7 @@ variable "labels" {
|
||||
variable "lien_reason" {
|
||||
description = "If non-empty, creates a project lien with this description."
|
||||
type = string
|
||||
default = ""
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "logging_data_access" {
|
||||
@@ -211,27 +220,6 @@ variable "org_policies_data_path" {
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "oslogin" {
|
||||
description = "Enable OS Login."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "oslogin_admins" {
|
||||
description = "List of IAM-style identities that will be granted roles necessary for OS Login administrators."
|
||||
type = list(string)
|
||||
default = []
|
||||
nullable = false
|
||||
|
||||
}
|
||||
|
||||
variable "oslogin_users" {
|
||||
description = "List of IAM-style identities that will be granted roles necessary for OS Login users."
|
||||
type = list(string)
|
||||
default = []
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "parent" {
|
||||
description = "Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format."
|
||||
type = string
|
||||
|
||||
Reference in New Issue
Block a user