Merge branch 'master' into patch-2

This commit is contained in:
Kartheek 2022-11-03 09:16:32 -07:00 committed by GitHub
commit 49b5b97afe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 749 additions and 860 deletions

View File

@ -29,7 +29,7 @@ The current list of modules supports most of the core foundational and networkin
Currently available modules: Currently available modules:
- **foundational** - [billing budget](./modules/billing-budget), [Cloud Identity group](./modules/cloud-identity-group/), [folder](./modules/folder), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [organization](./modules/organization), [organization-policy](./modules/organization-policy), [project](./modules/project), [projects-data-source](./modules/projects-data-source) - **foundational** - [billing budget](./modules/billing-budget), [Cloud Identity group](./modules/cloud-identity-group/), [folder](./modules/folder), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [organization](./modules/organization), [project](./modules/project), [projects-data-source](./modules/projects-data-source)
- **networking** - [DNS](./modules/dns), [Cloud Endpoints](./modules/endpoints), [address reservation](./modules/net-address), [NAT](./modules/net-cloudnat), [Global Load Balancer (classic)](./modules/net-glb/), [L4 ILB](./modules/net-ilb), [L7 ILB](./modules/net-ilb-l7), [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [VPN static](./modules/net-vpn-static), [Service Directory](./modules/service-directory) - **networking** - [DNS](./modules/dns), [Cloud Endpoints](./modules/endpoints), [address reservation](./modules/net-address), [NAT](./modules/net-cloudnat), [Global Load Balancer (classic)](./modules/net-glb/), [L4 ILB](./modules/net-ilb), [L7 ILB](./modules/net-ilb-l7), [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [VPN static](./modules/net-vpn-static), [Service Directory](./modules/service-directory)
- **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool) - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool)
- **data** - [BigQuery dataset](./modules/bigquery-dataset), [Bigtable instance](./modules/bigtable-instance), [Cloud SQL instance](./modules/cloudsql-instance), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Datafusion](./modules/datafusion), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub) - **data** - [BigQuery dataset](./modules/bigquery-dataset), [Bigtable instance](./modules/bigtable-instance), [Cloud SQL instance](./modules/cloudsql-instance), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Datafusion](./modules/datafusion), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub)

View File

@ -182,17 +182,18 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
|---|---|:---:|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|:---:|
| [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object&#40;&#123;&#10; outputs_bucket &#61; string&#10; project_id &#61; string&#10; project_number &#61; string&#10; federated_identity_pool &#61; string&#10; federated_identity_providers &#61; map&#40;object&#40;&#123;&#10; issuer &#61; string&#10; issuer_uri &#61; string&#10; name &#61; string&#10; principal_tpl &#61; string&#10; principalset_tpl &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>00-bootstrap</code> | | [automation](variables.tf#L20) | Automation resources created by the bootstrap stage. | <code title="object&#40;&#123;&#10; outputs_bucket &#61; string&#10; project_id &#61; string&#10; project_number &#61; string&#10; federated_identity_pool &#61; string&#10; federated_identity_providers &#61; map&#40;object&#40;&#123;&#10; issuer &#61; string&#10; issuer_uri &#61; string&#10; name &#61; string&#10; principal_tpl &#61; string&#10; principalset_tpl &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>00-bootstrap</code> |
| [billing_account](variables.tf#L38) | Billing account id and organization id ('nnnnnnnn' or null). | <code title="object&#40;&#123;&#10; id &#61; string&#10; organization_id &#61; number&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>00-bootstrap</code> | | [billing_account](variables.tf#L38) | Billing account id and organization id ('nnnnnnnn' or null). | <code title="object&#40;&#123;&#10; id &#61; string&#10; organization_id &#61; number&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>00-bootstrap</code> |
| [organization](variables.tf#L191) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>00-bootstrap</code> | | [organization](variables.tf#L197) | Organization details. | <code title="object&#40;&#123;&#10; domain &#61; string&#10; id &#61; number&#10; customer_id &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>00-bootstrap</code> |
| [prefix](variables.tf#L215) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>00-bootstrap</code> | | [prefix](variables.tf#L221) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>00-bootstrap</code> |
| [cicd_repositories](variables.tf#L47) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object&#40;&#123;&#10; data_platform_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; data_platform_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; gke_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; gke_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; networking &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; project_factory_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; project_factory_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; security &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | | [cicd_repositories](variables.tf#L47) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object&#40;&#123;&#10; data_platform_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; data_platform_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; gke_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; gke_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; networking &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; project_factory_dev &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; project_factory_prod &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10; security &#61; object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | |
| [custom_roles](variables.tf#L129) | Custom roles defined at the org level, in key => id format. | <code title="object&#40;&#123;&#10; service_project_network_admin &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | <code>00-bootstrap</code> | | [custom_roles](variables.tf#L129) | Custom roles defined at the org level, in key => id format. | <code title="object&#40;&#123;&#10; service_project_network_admin &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | <code>00-bootstrap</code> |
| [fast_features](variables.tf#L138) | Selective control for top-level FAST features. | <code title="object&#40;&#123;&#10; data_platform &#61; bool&#10; gke &#61; bool&#10; project_factory &#61; bool&#10; sandbox &#61; bool&#10; teams &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; data_platform &#61; true&#10; gke &#61; true&#10; project_factory &#61; true&#10; sandbox &#61; true&#10; teams &#61; true&#10;&#125;">&#123;&#8230;&#125;</code> | <code>00-bootstrap</code> | | [data_dir](variables.tf#L138) | Relative path for the folder storing configuration data. | <code>string</code> | | <code>&#34;data&#34;</code> | |
| [groups](variables.tf#L158) | Group names to grant organization-level permissions. | <code>map&#40;string&#41;</code> | | <code title="&#123;&#10; gcp-billing-admins &#61; &#34;gcp-billing-admins&#34;,&#10; gcp-devops &#61; &#34;gcp-devops&#34;,&#10; gcp-network-admins &#61; &#34;gcp-network-admins&#34;&#10; gcp-organization-admins &#61; &#34;gcp-organization-admins&#34;&#10; gcp-security-admins &#61; &#34;gcp-security-admins&#34;&#10; gcp-support &#61; &#34;gcp-support&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | <code>00-bootstrap</code> | | [fast_features](variables.tf#L144) | Selective control for top-level FAST features. | <code title="object&#40;&#123;&#10; data_platform &#61; bool&#10; gke &#61; bool&#10; project_factory &#61; bool&#10; sandbox &#61; bool&#10; teams &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; data_platform &#61; true&#10; gke &#61; true&#10; project_factory &#61; true&#10; sandbox &#61; true&#10; teams &#61; true&#10;&#125;">&#123;&#8230;&#125;</code> | <code>00-bootstrap</code> |
| [locations](variables.tf#L173) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object&#40;&#123;&#10; bq &#61; string&#10; gcs &#61; string&#10; logging &#61; string&#10; pubsub &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; bq &#61; &#34;EU&#34;&#10; gcs &#61; &#34;EU&#34;&#10; logging &#61; &#34;global&#34;&#10; pubsub &#61; &#91;&#93;&#10;&#125;">&#123;&#8230;&#125;</code> | <code>00-bootstrap</code> | | [groups](variables.tf#L164) | Group names to grant organization-level permissions. | <code>map&#40;string&#41;</code> | | <code title="&#123;&#10; gcp-billing-admins &#61; &#34;gcp-billing-admins&#34;,&#10; gcp-devops &#61; &#34;gcp-devops&#34;,&#10; gcp-network-admins &#61; &#34;gcp-network-admins&#34;&#10; gcp-organization-admins &#61; &#34;gcp-organization-admins&#34;&#10; gcp-security-admins &#61; &#34;gcp-security-admins&#34;&#10; gcp-support &#61; &#34;gcp-support&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | <code>00-bootstrap</code> |
| [organization_policy_configs](variables.tf#L201) | Organization policies customization. | <code title="object&#40;&#123;&#10; allowed_policy_member_domains &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | | [locations](variables.tf#L179) | Optional locations for GCS, BigQuery, and logging buckets created here. | <code title="object&#40;&#123;&#10; bq &#61; string&#10; gcs &#61; string&#10; logging &#61; string&#10; pubsub &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; bq &#61; &#34;EU&#34;&#10; gcs &#61; &#34;EU&#34;&#10; logging &#61; &#34;global&#34;&#10; pubsub &#61; &#91;&#93;&#10;&#125;">&#123;&#8230;&#125;</code> | <code>00-bootstrap</code> |
| [outputs_location](variables.tf#L209) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable | <code>string</code> | | <code>null</code> | | | [organization_policy_configs](variables.tf#L207) | Organization policies customization. | <code title="object&#40;&#123;&#10; allowed_policy_member_domains &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | |
| [tag_names](variables.tf#L226) | Customized names for resource management tags. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; context &#61; &#34;context&#34;&#10; environment &#61; &#34;environment&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | | [outputs_location](variables.tf#L215) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable | <code>string</code> | | <code>null</code> | |
| [team_folders](variables.tf#L243) | Team folders to be created. Format is described in a code comment. | <code title="map&#40;object&#40;&#123;&#10; descriptive_name &#61; string&#10; group_iam &#61; map&#40;list&#40;string&#41;&#41;&#10; impersonation_groups &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | | | [tag_names](variables.tf#L232) | Customized names for resource management tags. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; context &#61; &#34;context&#34;&#10; environment &#61; &#34;environment&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [team_folders](variables.tf#L249) | Team folders to be created. Format is described in a code comment. | <code title="map&#40;object&#40;&#123;&#10; descriptive_name &#61; string&#10; group_iam &#61; map&#40;list&#40;string&#41;&#41;&#10; impersonation_groups &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | |
## Outputs ## Outputs

View File

@ -0,0 +1,73 @@
# skip boilerplate check
#
# sample subset of useful organization policies, edit to suit requirements
compute.disableGuestAttributesAccess:
enforce: true
compute.requireOsLogin:
enforce: true
compute.restrictLoadBalancerCreationForTypes:
allow:
values:
- in:INTERNAL
compute.skipDefaultNetworkCreation:
enforce: true
compute.vmExternalIpAccess:
deny:
all: true
# compute.disableInternetNetworkEndpointGroup:
# enforce: true
# compute.disableNestedVirtualization:
# enforce: true
# compute.disableSerialPortAccess:
# enforce: true
# compute.restrictCloudNATUsage:
# deny:
# all: true
# compute.restrictDedicatedInterconnectUsage:
# deny:
# all: true
# compute.restrictPartnerInterconnectUsage:
# deny:
# all: true
# compute.restrictProtocolForwardingCreationForTypes:
# deny:
# all: true
# compute.restrictSharedVpcHostProjects:
# deny:
# all: true
# compute.restrictSharedVpcSubnetworks:
# deny:
# all: true
# compute.restrictVpcPeering:
# deny:
# all: true
# compute.restrictVpnPeerIPs:
# deny:
# all: true
# compute.restrictXpnProjectLienRemoval:
# enforce: true
# compute.setNewProjectDefaultToZonalDNSOnly:
# enforce: true
# compute.vmCanIpForward:
# deny:
# all: true

View File

@ -0,0 +1,12 @@
# skip boilerplate check
#
# sample subset of useful organization policies, edit to suit requirements
iam.automaticIamGrantsForDefaultServiceAccounts:
enforce: true
iam.disableServiceAccountKeyCreation:
enforce: true
iam.disableServiceAccountKeyUpload:
enforce: true

View File

@ -0,0 +1,26 @@
# skip boilerplate check
#
# sample subset of useful organization policies, edit to suit requirements
run.allowedIngress:
allow:
values:
- is:internal
# run.allowedVPCEgress:
# allow:
# values:
# - is:private-ranges-only
# cloudfunctions.allowedIngressSettings:
# allow:
# values:
# - is:ALLOW_INTERNAL_ONLY
# cloudfunctions.allowedVpcConnectorEgressSettings:
# allow:
# values:
# - is:PRIVATE_RANGES_ONLY
# cloudfunctions.requireVPCConnector:
# enforce: true

View File

@ -0,0 +1,9 @@
# skip boilerplate check
#
# sample subset of useful organization policies, edit to suit requirements
sql.restrictAuthorizedNetworks:
enforce: true
sql.restrictPublicIp:
enforce: true

View File

@ -0,0 +1,6 @@
# skip boilerplate check
#
# sample subset of useful organization policies, edit to suit requirements
storage.uniformBucketLevelAccess:
enforce: true

View File

@ -66,44 +66,12 @@ module "organization" {
) )
} : {} } : {}
) )
# sample subset of useful organization policies, edit to suit requirements
# sample subset of useful organization policies, edit to suit requirements
org_policies = { org_policies = {
"compute.disableGuestAttributesAccess" = { enforce = true } "iam.allowedPolicyMemberDomains" = { allow = { values = local.all_drs_domains } }
"compute.requireOsLogin" = { enforce = true }
"compute.restrictLoadBalancerCreationForTypes" = { allow = { values = ["in:INTERNAL"] } } #"gcp.resourceLocations" = {
"compute.skipDefaultNetworkCreation" = { enforce = true }
"compute.vmExternalIpAccess" = { deny = { all = true } }
"iam.allowedPolicyMemberDomains" = { allow = { values = local.all_drs_domains } }
"iam.automaticIamGrantsForDefaultServiceAccounts" = { enforce = true }
"iam.disableServiceAccountKeyCreation" = { enforce = true }
"iam.disableServiceAccountKeyUpload" = { enforce = true }
"run.allowedIngress" = { allow = { values = ["is:internal"] } }
"sql.restrictAuthorizedNetworks" = { enforce = true }
"sql.restrictPublicIp" = { enforce = true }
"storage.uniformBucketLevelAccess" = { enforce = true }
# "cloudfunctions.allowedIngressSettings" = {
# allow = { values = ["is:ALLOW_INTERNAL_ONLY"] }
# }
# "cloudfunctions.allowedVpcConnectorEgressSettings" = {
# allow = { values = ["is:PRIVATE_RANGES_ONLY"] }
# }
# "cloudfunctions.requireVPCConnector" = { enforce = true }
# "compute.disableInternetNetworkEndpointGroup" = { enforce = true }
# "compute.disableNestedVirtualization" = { enforce = true }
# "compute.disableSerialPortAccess" = { enforce = true }
# "compute.restrictCloudNATUsage" = { deny = { all = true }}
# "compute.restrictDedicatedInterconnectUsage" = { deny = { all = true }}
# "compute.restrictPartnerInterconnectUsage" = { deny = { all = true }}
# "compute.restrictProtocolForwardingCreationForTypes" = { deny = { all = true }}
# "compute.restrictSharedVpcHostProjects" = { deny = { all = true }}
# "compute.restrictSharedVpcSubnetworks" = { deny = { all = true }}
# "compute.restrictVpcPeering" = { deny = { all = true }}
# "compute.restrictVpnPeerIPs" = { deny = { all = true }}
# "compute.restrictXpnProjectLienRemoval" = { enforce = true }
# "compute.setNewProjectDefaultToZonalDNSOnly" = { enforce = true }
# "compute.vmCanIpForward" = { deny = { all = true }}
# "gcp.resourceLocations" = {
# allow = { values = local.allowed_regions } # allow = { values = local.allowed_regions }
# } # }
# "iam.workloadIdentityPoolProviders" = { # "iam.workloadIdentityPoolProviders" = {
@ -114,8 +82,9 @@ module "organization" {
# ] # ]
# } # }
# } # }
# "run.allowedVPCEgress" = { allow = { values = ["is:private-ranges-only"] } }
} }
org_policies_data_path = "${var.data_dir}/org-policies"
tags = { tags = {
(var.tag_names.context) = { (var.tag_names.context) = {
description = "Resource management context." description = "Resource management context."

View File

@ -135,6 +135,12 @@ variable "custom_roles" {
default = null default = null
} }
variable "data_dir" {
description = "Relative path for the folder storing configuration data."
type = string
default = "data"
}
variable "fast_features" { variable "fast_features" {
# tfdoc:variable:source 00-bootstrap # tfdoc:variable:source 00-bootstrap
description = "Selective control for top-level FAST features." description = "Selective control for top-level FAST features."

View File

@ -36,7 +36,6 @@ These modules are used in the examples included in this repository. If you are u
- [service accounts](./iam-service-account) - [service accounts](./iam-service-account)
- [logging bucket](./logging-bucket) - [logging bucket](./logging-bucket)
- [organization](./organization) - [organization](./organization)
- [organization-policy](./organization-policy)
- [project](./project) - [project](./project)
- [projects-data-source](./projects-data-source) - [projects-data-source](./projects-data-source)

View File

@ -75,6 +75,10 @@ module "folder" {
# tftest modules=1 resources=8 # tftest modules=1 resources=8
``` ```
### Organization policy factory
See the [organization policy factory in the project module](../project#organization-policy-factory).
### Firewall policy factory ### Firewall policy factory
In the same way as for the [organization](../organization) module, the in-built factory allows you to define a single policy, using one file for rules, and an optional file for CIDR range substitution variables. Remember that non-absolute paths are relative to the root module (the folder where you run `terraform`). In the same way as for the [organization](../organization) module, the in-built factory allows you to define a single policy, using one file for rules, and an optional file for CIDR range substitution variables. Remember that non-absolute paths are relative to the root module (the folder where you run `terraform`).
@ -311,8 +315,9 @@ module "folder" {
| [logging_sinks](variables.tf#L105) | Logging sinks to create for this folder. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; include_children &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [logging_sinks](variables.tf#L105) | Logging sinks to create for this folder. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; include_children &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [name](variables.tf#L126) | Folder name. | <code>string</code> | | <code>null</code> | | [name](variables.tf#L126) | Folder name. | <code>string</code> | | <code>null</code> |
| [org_policies](variables.tf#L132) | Organization policies applied to this folder keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [org_policies](variables.tf#L132) | Organization policies applied to this folder keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [parent](variables.tf#L172) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> | | [org_policies_data_path](variables.tf#L172) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L182) | Tag bindings for this folder, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [parent](variables.tf#L178) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L188) | Tag bindings for this folder, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -17,8 +17,57 @@
# tfdoc:file:description Folder-level organization policies. # tfdoc:file:description Folder-level organization policies.
locals { locals {
_factory_data_raw = (
var.org_policies_data_path == null
? tomap({})
: merge([
for f in fileset(var.org_policies_data_path, "*.yaml") :
yamldecode(file("${var.org_policies_data_path}/${f}"))
]...)
)
# simulate applying defaults to data coming from yaml files
_factory_data = {
for k, v in local._factory_data_raw :
k => {
inherit_from_parent = try(v.inherit_from_parent, null)
reset = try(v.reset, null)
allow = can(v.allow) ? {
all = try(v.allow.all, null)
values = try(v.allow.values, null)
} : null
deny = can(v.deny) ? {
all = try(v.deny.all, null)
values = try(v.deny.values, null)
} : null
enforce = try(v.enforce, true)
rules = [
for r in try(v.rules, []) : {
allow = can(r.allow) ? {
all = try(r.allow.all, null)
values = try(r.allow.values, null)
} : null
deny = can(r.deny) ? {
all = try(r.deny.all, null)
values = try(r.deny.values, null)
} : null
enforce = try(r.enforce, true)
condition = {
description = try(r.condition.description, null)
expression = try(r.condition.expression, null)
location = try(r.condition.location, null)
title = try(r.condition.title, null)
}
}
]
}
}
_org_policies = merge(local._factory_data, var.org_policies)
org_policies = { org_policies = {
for k, v in var.org_policies : for k, v in local._org_policies :
k => merge(v, { k => merge(v, {
name = "${local.folder.name}/policies/${k}" name = "${local.folder.name}/policies/${k}"
parent = local.folder.name parent = local.folder.name

View File

@ -169,6 +169,12 @@ variable "org_policies" {
nullable = false nullable = false
} }
variable "org_policies_data_path" {
description = "Path containing org policies in YAML format."
type = string
default = null
}
variable "parent" { variable "parent" {
description = "Parent in folders/folder_id or organizations/org_id format." description = "Parent in folders/folder_id or organizations/org_id format."
type = string type = string

View File

@ -1,166 +0,0 @@
# Google Cloud Organization Policy
This module allows creation and management of [GCP Organization Policies](https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints) by defining them in a well formatted `yaml` files or with HCL.
Yaml based factory can simplify centralized management of Org Policies for a DevSecOps team by providing a simple way to define/structure policies and exclusions.
> **_NOTE:_** This module uses experimental feature `module_variable_optional_attrs` which will be included into [terraform release 1.3](https://github.com/hashicorp/terraform/releases/tag/v1.3.0-alpha20220706).
## Example
### Terraform code
```hcl
# using configuration provided in a set of yaml files
module "org-policy-factory" {
source = "./fabric/modules/organization-policy"
config_directory = "./policies"
}
# using configuration provided in the module variable
module "org-policy" {
source = "./fabric/modules/organization-policy"
policies = {
"folders/1234567890" = {
# enforce boolean policy with no conditions
"iam.disableServiceAccountKeyUpload" = {
rules = [
{
enforce = true
}
]
},
# Deny All for compute.vmCanIpForward policy
"compute.vmCanIpForward" = {
inherit_from_parent = false
rules = [
deny = [] # stands for deny_all
]
}
},
"organizations/1234567890" = {
# allow only internal ingress when match condition env=prod
"run.allowedIngress" = {
rules = [
{
allow = ["internal"]
condition = {
description= "allow ingress"
expression = "resource.matchTag('123456789/environment', 'prod')"
title = "allow-for-prod-org"
}
}
]
}
}
}
}
# tftest skip
```
## Org Policy definition format and structure
### Structure of `policies` variable
```hcl
policies = {
"parent_id" = { # parent id in format projects/project-id, folders/1234567890 or organizations/1234567890.
"policy_name" = { # policy constraint id, for example compute.vmExternalIpAccess.
inherit_from_parent = true|false # (Optional) Only for list constraints. Determines the inheritance behavior for this policy.
reset = true|false # (Optional) Ignores policies set above this resource and restores the constraint_default enforcement behavior.
rules = [ # Up to 10 PolicyRules are allowed.
{
allow = ["value1", "value2"] # (Optional) Only for list constraints. Stands for `allow_all` if set to empty list `[]` or to `values.allowed_values` if set to a list of values
denyl = ["value3", "value4"] # (Optional) Only for list constraints. Stands for `deny_all` if set to empty list `[]` or to `values.denied_values` if set to a list of values
enforce = true|false # (Optional) Only for boolean constraints. If true, then the Policy is enforced.
condition = { # (Optional) A condition which determines whether this rule is used in the evaluation of the policy.
description = "Condition description" # (Optional)
expression = "Condition expression" # (Optional) For example "resource.matchTag('123456789/environment', 'prod')".
location = "policy-error.log" # (Optional) String indicating the location of the expression for error reporting.
title = "condition-title" # (Optional)
}
}
]
}
}
}
# tftest skip
```
### Structure of configuration provided in a yaml file/s
Configuration should be placed in a set of yaml files in the config directory. Policy entry structure as follows:
```yaml
parent_id: # parent id in format projects/project-id, folders/1234567890 or organizations/1234567890.
policy_name1: # policy constraint id, for example compute.vmExternalIpAccess.
inherit_from_parent: true|false # (Optional) Only for list constraints. Determines the inheritance behavior for this policy.
reset: true|false # (Optional) Ignores policies set above this resource and restores the constraint_default enforcement behavior.
rules:
- allow: ["value1", "value2"] # (Optional) Only for list constraints. Stands for `allow_all` if set to empty list `[]` or to `values.allowed_values` if set to a list of values
deny: ["value3", "value4"] # (Optional) Only for list constraints. Stands for `deny_all` if set to empty list `[]` or to `values.denied_values` if set to a list of values
enforce: true|false # (Optional) Only for boolean constraints. If true, then the Policy is enforced.
condition: # (Optional) A condition which determines whether this rule is used in the evaluation of the policy.
description: Condition description # (Optional)
expression: Condition expression # (Optional) For example resource.matchTag("123456789/environment", "prod")
location: policy-error.log # (Optional) String indicating the location of the expression for error reporting.
title: condition-title # (Optional)
```
Module allows policies to be distributed into multiple yaml files for a better management and navigation.
```bash
├── org-policies
│ ├── baseline.yaml
│   ├── image-import-projects.yaml
│   └── exclusions.yaml
```
Organization policies example yaml configuration
```bash
cat ./policies/baseline.yaml
organizations/1234567890:
constraints/compute.vmExternalIpAccess:
rules:
- deny: [] # Stands for deny_all = true
folders/1234567890:
compute.vmCanIpForward:
inherit_from_parent: false
reset: false
rules:
- allow: [] # Stands for allow_all = true
projects/my-project-id:
run.allowedIngress:
inherit_from_parent: true
rules:
- allow: ['internal'] # Stands for values.allowed_values
condition:
description: allow internal ingress
expression: resource.matchTag("123456789/environment", "prod")
location: test.log
title: allow-for-prod
iam.allowServiceAccountCredentialLifetimeExtension:
rules:
- deny: [] # Stands for deny_all = true
compute.disableGlobalLoadBalancing:
reset: true
```
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [config_directory](variables.tf#L17) | Paths to a folder where organization policy configs are stored in yaml format. Files suffix must be `.yaml`. | <code>string</code> | | <code>null</code> |
| [policies](variables.tf#L23) | Organization policies keyed by parent in format `projects/project-id`, `folders/1234567890` or `organizations/1234567890`. | <code title="map&#40;map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; List policy only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;&#10; list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;list&#40;string&#41;&#41; &#35; List policy only. Stands for &#96;allow_all&#96; if set to empty list &#96;&#91;&#93;&#96; or to &#96;values.allowed_values&#96; if set to a list of values &#10; deny &#61; optional&#40;list&#40;string&#41;&#41; &#35; List policy only. Stands for &#96;deny_all&#96; if set to empty list &#96;&#91;&#93;&#96; or to &#96;values.denied_values&#96; if set to a list of values&#10; enforce &#61; optional&#40;bool&#41; &#35; Boolean policy only. &#10; condition &#61; optional&#40;&#10; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#41;&#10; &#125;&#41;&#41;&#10; &#41;&#10;&#125;&#41;&#41;&#41;">map&#40;map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [policies](outputs.tf#L17) | Organization policies. | |
<!-- END TFDOC -->

View File

@ -1,102 +0,0 @@
/**
* 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
*
* http://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.
*/
locals {
policy_files = var.config_directory == null ? [] : concat(
[
for config_file in fileset("${path.root}/${var.config_directory}", "**/*.yaml") :
"${path.root}/${var.config_directory}/${config_file}"
]
)
policies_raw = merge(
merge(
[
for config_file in local.policy_files :
try(yamldecode(file(config_file)), {})
]...
), var.policies)
policies_list = flatten([
for parent, policies in local.policies_raw : [
for policy_name, policy in policies : {
parent = parent,
policy_name = policy_name,
inherit_from_parent = try(policy["inherit_from_parent"], null),
reset = try(policy["reset"], null),
rules = [
for rule in try(policy["rules"], []) : {
allow_all = try(length(rule["allow"]), -1) == 0 ? "TRUE" : null
deny_all = try(length(rule["deny"]), -1) == 0 ? "TRUE" : null
enforce = try(rule["enforce"], null) == true ? "TRUE" : try(
rule["enforce"], null) == false ? "FALSE" : null,
condition = try(rule["condition"], null) != null ? {
description = try(rule["condition"]["description"], null),
expression = try(rule["condition"]["expression"], null),
location = try(rule["condition"]["location"], null),
title = try(rule["condition"]["title"], null)
} : null,
values = try(length(rule["allow"]), 0) > 0 || try(length(rule["deny"]), 0) > 0 ? {
allowed_values = try(length(rule["allow"]), 0) > 0 ? rule["allow"] : null
denied_values = try(length(rule["deny"]), 0) > 0 ? rule["deny"] : null
} : null
}
]
}
]
])
policies_map = {
for item in local.policies_list :
format("%s-%s", item["parent"], item["policy_name"]) => item
}
}
resource "google_org_policy_policy" "primary" {
for_each = local.policies_map
name = format("%s/policies/%s", each.value.parent, each.value.policy_name)
parent = each.value.parent
spec {
inherit_from_parent = each.value.inherit_from_parent
reset = each.value.reset
dynamic "rules" {
for_each = each.value.rules
content {
allow_all = rules.value.allow_all
deny_all = rules.value.deny_all
enforce = rules.value.enforce
dynamic "condition" {
for_each = rules.value.condition != null ? [""] : []
content {
description = rules.value.condition.description
expression = rules.value.condition.expression
location = rules.value.condition.location
title = rules.value.condition.title
}
}
dynamic "values" {
for_each = rules.value.values != null ? [""] : []
content {
allowed_values = rules.value.values.allowed_values
denied_values = rules.value.values.denied_values
}
}
}
}
}
}

View File

@ -1,20 +0,0 @@
/**
* 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
*
* http://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.
*/
output "policies" {
description = "Organization policies."
value = google_org_policy_policy.primary
}

View File

@ -1,45 +0,0 @@
/**
* 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
*
* http://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.
*/
variable "config_directory" {
description = "Paths to a folder where organization policy configs are stored in yaml format. Files suffix must be `.yaml`."
type = string
default = null
}
variable "policies" {
description = "Organization policies keyed by parent in format `projects/project-id`, `folders/1234567890` or `organizations/1234567890`."
type = map(map(object({
inherit_from_parent = optional(bool) # List policy only.
reset = optional(bool)
rules = optional(
list(object({
allow = optional(list(string)) # List policy only. Stands for `allow_all` if set to empty list `[]` or to `values.allowed_values` if set to a list of values
deny = optional(list(string)) # List policy only. Stands for `deny_all` if set to empty list `[]` or to `values.denied_values` if set to a list of values
enforce = optional(bool) # Boolean policy only.
condition = optional(
object({
description = optional(string)
expression = optional(string)
location = optional(string)
title = optional(string)
})
)
}))
)
})))
default = {}
}

View File

@ -1,29 +0,0 @@
# 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.
terraform {
required_version = ">= 1.3.1"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -76,6 +76,10 @@ If you set audit policies via the `iam_audit_config_authoritative` variable, be
Some care must also be takend with the `groups_iam` variable (and in some situations with the additive variables) to ensure that variable keys are static values, so that Terraform is able to compute the dependency graph. Some care must also be takend with the `groups_iam` variable (and in some situations with the additive variables) to ensure that variable keys are static values, so that Terraform is able to compute the dependency graph.
### Organization policy factory
See the [organization policy factory in the project module](../project#organization-policy-factory).
## Hierarchical firewall policies ## Hierarchical firewall policies
Hirerarchical firewall policies can be managed in two ways: Hirerarchical firewall policies can be managed in two ways:
@ -336,8 +340,9 @@ module "org" {
| [logging_exclusions](variables.tf#L122) | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [logging_exclusions](variables.tf#L122) | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_sinks](variables.tf#L129) | Logging sinks to create for this organization. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; include_children &#61; bool&#10; bq_partitioned_table &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [logging_sinks](variables.tf#L129) | Logging sinks to create for this organization. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; include_children &#61; bool&#10; bq_partitioned_table &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies](variables.tf#L151) | Organization policies applied to this organization keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [org_policies](variables.tf#L151) | Organization policies applied to this organization keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [tag_bindings](variables.tf#L200) | Tag bindings for this organization, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [org_policies_data_path](variables.tf#L200) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [tags](variables.tf#L206) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; values &#61; map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | | [tag_bindings](variables.tf#L206) | Tag bindings for this organization, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [tags](variables.tf#L212) | Tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; values &#61; map&#40;object&#40;&#123;&#10; description &#61; string&#10; iam &#61; map&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -17,8 +17,57 @@
# tfdoc:file:description Organization-level organization policies. # tfdoc:file:description Organization-level organization policies.
locals { locals {
_factory_data_raw = (
var.org_policies_data_path == null
? tomap({})
: merge([
for f in fileset(var.org_policies_data_path, "*.yaml") :
yamldecode(file("${var.org_policies_data_path}/${f}"))
]...)
)
# simulate applying defaults to data coming from yaml files
_factory_data = {
for k, v in local._factory_data_raw :
k => {
inherit_from_parent = try(v.inherit_from_parent, null)
reset = try(v.reset, null)
allow = can(v.allow) ? {
all = try(v.allow.all, null)
values = try(v.allow.values, null)
} : null
deny = can(v.deny) ? {
all = try(v.deny.all, null)
values = try(v.deny.values, null)
} : null
enforce = try(v.enforce, true)
rules = [
for r in try(v.rules, []) : {
allow = can(r.allow) ? {
all = try(r.allow.all, null)
values = try(r.allow.values, null)
} : null
deny = can(r.deny) ? {
all = try(r.deny.all, null)
values = try(r.deny.values, null)
} : null
enforce = try(r.enforce, true)
condition = {
description = try(r.condition.description, null)
expression = try(r.condition.expression, null)
location = try(r.condition.location, null)
title = try(r.condition.title, null)
}
}
]
}
}
_org_policies = merge(local._factory_data, var.org_policies)
org_policies = { org_policies = {
for k, v in var.org_policies : for k, v in local._org_policies :
k => merge(v, { k => merge(v, {
name = "${var.organization_id}/policies/${k}" name = "${var.organization_id}/policies/${k}"
parent = var.organization_id parent = var.organization_id

View File

@ -197,6 +197,12 @@ variable "organization_id" {
} }
} }
variable "org_policies_data_path" {
description = "Path containing org policies in YAML format."
type = string
default = null
}
variable "tag_bindings" { variable "tag_bindings" {
description = "Tag bindings for this organization, in key => tag value id format." description = "Tag bindings for this organization, in key => tag value id format."
type = map(string) type = map(string)

View File

@ -211,6 +211,71 @@ module "project" {
# tftest modules=1 resources=10 # tftest modules=1 resources=10
``` ```
### Organization policy factory
Organization policies can be loaded from a directory containing YAML files where each file defines one or more constraints. The structure of the YAML files is exactly the same as the `org_policies` variable.
Note that contraints defined via `org_policies` take precedence over those in `org_policies_data_path`. In other words, if you specify the same contraint in a YAML file *and* in the `org_policies` variable, the latter will take priority.
The example below deploys a few organization policies split between two YAML files.
```hcl
module "folder" {
source = "./fabric/modules/folder"
parent = "organizations/1234567890"
name = "Folder name"
org_policies_data_path = "/my/path"
}
# tftest skip
```
```yaml
# /my/path/boolean.yaml
iam.disableServiceAccountKeyCreation:
enforce: true
iam.disableServiceAccountKeyUpload:
enforce: false
rules:
- condition:
expression: resource.matchTagId("tagKeys/1234", "tagValues/1234")
title: condition
description: test condition
location: xxx
enforce: true
```
```yaml
# /my/path/list.yaml
compute.vmExternalIpAccess:
deny:
all: true
iam.allowedPolicyMemberDomains:
allow:
values:
- C0xxxxxxx
- C0yyyyyyy
compute.restrictLoadBalancerCreationForTypes:
deny:
values: ["in:EXTERNAL"]
rules:
- condition:
expression: resource.matchTagId("tagKeys/1234", "tagValues/1234")
title: condition
description: test condition
allow:
values: ["in:EXTERNAL"]
- condition:
expression: resource.matchTagId("tagKeys/12345", "tagValues/12345")
title: condition2
description: test condition2
allow:
all: true
```
## Logging Sinks ## Logging Sinks
```hcl ```hcl
@ -407,21 +472,22 @@ output "compute_robot" {
| [logging_sinks](variables.tf#L102) | Logging sinks to create for this project. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; iam &#61; bool&#10; unique_writer &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [logging_sinks](variables.tf#L102) | Logging sinks to create for this project. | <code title="map&#40;object&#40;&#123;&#10; destination &#61; string&#10; type &#61; string&#10; filter &#61; string&#10; iam &#61; bool&#10; unique_writer &#61; bool&#10; exclusions &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [metric_scopes](variables.tf#L124) | List of projects that will act as metric scopes for this project. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [metric_scopes](variables.tf#L124) | List of projects that will act as metric scopes for this project. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [org_policies](variables.tf#L136) | Organization policies applied to this project keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [org_policies](variables.tf#L136) | Organization policies applied to this project keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool, true&#41; &#35; for boolean policies only.&#10; condition &#61; object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [oslogin](variables.tf#L176) | Enable OS Login. | <code>bool</code> | | <code>false</code> | | [org_policies_data_path](variables.tf#L176) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [oslogin_admins](variables.tf#L182) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [oslogin](variables.tf#L182) | Enable OS Login. | <code>bool</code> | | <code>false</code> |
| [oslogin_users](variables.tf#L190) | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [oslogin_admins](variables.tf#L188) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [parent](variables.tf#L197) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> | | [oslogin_users](variables.tf#L196) | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [prefix](variables.tf#L207) | Prefix used to generate project id and name. | <code>string</code> | | <code>null</code> | | [parent](variables.tf#L203) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> |
| [project_create](variables.tf#L213) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> | | [prefix](variables.tf#L213) | Prefix used to generate project id and name. | <code>string</code> | | <code>null</code> |
| [service_config](variables.tf#L219) | Configure service API activation. | <code title="object&#40;&#123;&#10; disable_on_destroy &#61; bool&#10; disable_dependent_services &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; disable_on_destroy &#61; false&#10; disable_dependent_services &#61; false&#10;&#125;">&#123;&#8230;&#125;</code> | | [project_create](variables.tf#L219) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> |
| [service_encryption_key_ids](variables.tf#L231) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [service_config](variables.tf#L225) | Configure service API activation. | <code title="object&#40;&#123;&#10; disable_on_destroy &#61; bool&#10; disable_dependent_services &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; disable_on_destroy &#61; false&#10; disable_dependent_services &#61; false&#10;&#125;">&#123;&#8230;&#125;</code> |
| [service_perimeter_bridges](variables.tf#L238) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list&#40;string&#41;</code> | | <code>null</code> | | [service_encryption_key_ids](variables.tf#L237) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeter_standard](variables.tf#L245) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> | | [service_perimeter_bridges](variables.tf#L244) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list&#40;string&#41;</code> | | <code>null</code> |
| [services](variables.tf#L251) | Service APIs to enable. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [service_perimeter_standard](variables.tf#L251) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> |
| [shared_vpc_host_config](variables.tf#L257) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object&#40;&#123;&#10; enabled &#61; bool&#10; service_projects &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [services](variables.tf#L257) | Service APIs to enable. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [shared_vpc_service_config](variables.tf#L266) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [shared_vpc_host_config](variables.tf#L263) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object&#40;&#123;&#10; enabled &#61; bool&#10; service_projects &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [skip_delete](variables.tf#L276) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> | | [shared_vpc_service_config](variables.tf#L272) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L282) | Tag bindings for this project, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [skip_delete](variables.tf#L282) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> |
| [tag_bindings](variables.tf#L288) | Tag bindings for this project, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -17,8 +17,57 @@
# tfdoc:file:description Project-level organization policies. # tfdoc:file:description Project-level organization policies.
locals { locals {
_factory_data_raw = (
var.org_policies_data_path == null
? tomap({})
: merge([
for f in fileset(var.org_policies_data_path, "*.yaml") :
yamldecode(file("${var.org_policies_data_path}/${f}"))
]...)
)
# simulate applying defaults to data coming from yaml files
_factory_data = {
for k, v in local._factory_data_raw :
k => {
inherit_from_parent = try(v.inherit_from_parent, null)
reset = try(v.reset, null)
allow = can(v.allow) ? {
all = try(v.allow.all, null)
values = try(v.allow.values, null)
} : null
deny = can(v.deny) ? {
all = try(v.deny.all, null)
values = try(v.deny.values, null)
} : null
enforce = try(v.enforce, true)
rules = [
for r in try(v.rules, []) : {
allow = can(r.allow) ? {
all = try(r.allow.all, null)
values = try(r.allow.values, null)
} : null
deny = can(r.deny) ? {
all = try(r.deny.all, null)
values = try(r.deny.values, null)
} : null
enforce = try(r.enforce, true)
condition = {
description = try(r.condition.description, null)
expression = try(r.condition.expression, null)
location = try(r.condition.location, null)
title = try(r.condition.title, null)
}
}
]
}
}
_org_policies = merge(local._factory_data, var.org_policies)
org_policies = { org_policies = {
for k, v in var.org_policies : for k, v in local._org_policies :
k => merge(v, { k => merge(v, {
name = "projects/${local.project.project_id}/policies/${k}" name = "projects/${local.project.project_id}/policies/${k}"
parent = "projects/${local.project.project_id}" parent = "projects/${local.project.project_id}"

View File

@ -173,6 +173,12 @@ variable "org_policies" {
nullable = false nullable = false
} }
variable "org_policies_data_path" {
description = "Path containing org policies in YAML format."
type = string
default = null
}
variable "oslogin" { variable "oslogin" {
description = "Enable OS Login." description = "Enable OS Login."
type = bool type = bool

View File

@ -27,4 +27,5 @@ module "test" {
logging_sinks = var.logging_sinks logging_sinks = var.logging_sinks
logging_exclusions = var.logging_exclusions logging_exclusions = var.logging_exclusions
org_policies = var.org_policies org_policies = var.org_policies
org_policies_data_path = var.org_policies_data_path
} }

View File

@ -58,3 +58,8 @@ variable "org_policies" {
type = any type = any
default = {} default = {}
} }
variable "org_policies_data_path" {
type = any
default = null
}

View File

@ -12,31 +12,106 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import hcl2
import yaml
BOOLEAN_POLICIES = '''{
"iam.disableServiceAccountKeyCreation" = {
enforce = true
}
"iam.disableServiceAccountKeyUpload" = {
enforce = false
rules = [
{
condition = {
expression = "resource.matchTagId(aa, bb)"
title = "condition"
description = "test condition"
location = "xxx"
}
enforce = true
}
]
}
}'''
LIST_POLICIES = '''{
"compute.vmExternalIpAccess" = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
}
"compute.restrictLoadBalancerCreationForTypes" = {
deny = { values = ["in:EXTERNAL"] }
rules = [
{
condition = {
expression = "resource.matchTagId(aa, bb)"
title = "condition"
description = "test condition"
location = "xxx"
}
allow = {
values = ["EXTERNAL_1"]
}
},
{
condition = {
expression = "resource.matchTagId(cc, dd)"
title = "condition2"
description = "test condition2"
location = "xxx"
}
allow = {
all = true
}
}
]
}
}'''
def test_policy_boolean(plan_runner): def test_policy_boolean(plan_runner):
"Test boolean org policy." "Test boolean org policy."
policies = '''{ _, resources = plan_runner(org_policies=BOOLEAN_POLICIES)
"iam.disableServiceAccountKeyCreation" = { validate_policy_boolean_resources(resources)
enforce = true
}
"iam.disableServiceAccountKeyUpload" = {
enforce = false
rules = [
{
condition = {
expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
title = "condition"
description = "test condition"
location = "xxx"
}
enforce = true
}
]
}
}'''
_, resources = plan_runner(org_policies=policies)
assert len(resources) == 3
def test_policy_list(plan_runner):
"Test list org policy."
_, resources = plan_runner(org_policies=LIST_POLICIES)
validate_policy_list_resources(resources)
def test_policy_boolean_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {BOOLEAN_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_boolean_resources(resources)
def test_policy_list_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {LIST_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_list_resources(resources)
def validate_policy_boolean_resources(resources):
assert len(resources) == 3
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
assert len(policies) == 2 assert len(policies) == 2
@ -76,7 +151,7 @@ def test_policy_boolean(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -86,46 +161,7 @@ def test_policy_boolean(plan_runner):
} }
def test_policy_list(plan_runner): def validate_policy_list_resources(resources):
"Test list org policy."
policies = '''{
"compute.vmExternalIpAccess" = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
}
"compute.restrictLoadBalancerCreationForTypes" = {
deny = { values = ["in:EXTERNAL"] }
rules = [
{
condition = {
expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
title = "condition"
description = "test condition"
location = "xxx"
}
allow = {
values = ["EXTERNAL_1"]
}
},
{
condition = {
expression = "resource.matchTagId(\\"tagKeys/12345\\", \\"tagValues/12345\\")"
title = "condition2"
description = "test condition2"
location = "xxx"
}
allow = {
all = true
}
}
]
}
}'''
_, resources = plan_runner(org_policies=policies)
assert len(resources) == 4 assert len(resources) == 4
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
@ -193,7 +229,7 @@ def test_policy_list(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -208,14 +244,10 @@ def test_policy_list(plan_runner):
assert p3['rules'][2] == { assert p3['rules'][2] == {
'allow_all': 'TRUE', 'allow_all': 'TRUE',
'condition': [{ 'condition': [{
'description': 'description': 'test condition2',
'test condition2', 'expression': 'resource.matchTagId(cc, dd)',
'expression': 'location': 'xxx',
'resource.matchTagId("tagKeys/12345", "tagValues/12345")', 'title': 'condition2'
'location':
'xxx',
'title':
'condition2'
}], }],
'deny_all': None, 'deny_all': None,
'enforce': None, 'enforce': None,

View File

@ -29,6 +29,7 @@ module "test" {
logging_sinks = var.logging_sinks logging_sinks = var.logging_sinks
logging_exclusions = var.logging_exclusions logging_exclusions = var.logging_exclusions
org_policies = var.org_policies org_policies = var.org_policies
org_policies_data_path = var.org_policies_data_path
tag_bindings = var.tag_bindings tag_bindings = var.tag_bindings
tags = var.tags tags = var.tags
} }

View File

@ -74,6 +74,11 @@ variable "org_policies" {
default = {} default = {}
} }
variable "org_policies_data_path" {
type = any
default = null
}
variable "tag_bindings" { variable "tag_bindings" {
type = any type = any
default = null default = null

View File

@ -15,31 +15,106 @@
import difflib import difflib
from pathlib import Path from pathlib import Path
import hcl2
import yaml
BOOLEAN_POLICIES = '''{
"iam.disableServiceAccountKeyCreation" = {
enforce = true
}
"iam.disableServiceAccountKeyUpload" = {
enforce = false
rules = [
{
condition = {
expression = "resource.matchTagId(aa, bb)"
title = "condition"
description = "test condition"
location = "xxx"
}
enforce = true
}
]
}
}'''
LIST_POLICIES = '''{
"compute.vmExternalIpAccess" = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
}
"compute.restrictLoadBalancerCreationForTypes" = {
deny = { values = ["in:EXTERNAL"] }
rules = [
{
condition = {
expression = "resource.matchTagId(aa, bb)"
title = "condition"
description = "test condition"
location = "xxx"
}
allow = {
values = ["EXTERNAL_1"]
}
},
{
condition = {
expression = "resource.matchTagId(cc, dd)"
title = "condition2"
description = "test condition2"
location = "xxx"
}
allow = {
all = true
}
}
]
}
}'''
def test_policy_boolean(plan_runner): def test_policy_boolean(plan_runner):
"Test boolean org policy." "Test boolean org policy."
policies = '''{ _, resources = plan_runner(org_policies=BOOLEAN_POLICIES)
"iam.disableServiceAccountKeyCreation" = { validate_policy_boolean_resources(resources)
enforce = true
}
"iam.disableServiceAccountKeyUpload" = {
enforce = false
rules = [
{
condition = {
expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
title = "condition"
description = "test condition"
location = "xxx"
}
enforce = true
}
]
}
}'''
_, resources = plan_runner(org_policies=policies)
assert len(resources) == 2
def test_policy_list(plan_runner):
"Test list org policy."
_, resources = plan_runner(org_policies=LIST_POLICIES)
validate_policy_list_resources(resources)
def test_policy_boolean_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {BOOLEAN_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_boolean_resources(resources)
def test_policy_list_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {LIST_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_list_resources(resources)
def validate_policy_boolean_resources(resources):
assert len(resources) == 2
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
assert len(policies) == 2 assert len(policies) == 2
assert all( assert all(
@ -81,7 +156,7 @@ def test_policy_boolean(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -91,46 +166,7 @@ def test_policy_boolean(plan_runner):
} }
def test_policy_list(plan_runner): def validate_policy_list_resources(resources):
"Test list org policy."
policies = '''{
"compute.vmExternalIpAccess" = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
}
"compute.restrictLoadBalancerCreationForTypes" = {
deny = { values = ["in:EXTERNAL"] }
rules = [
{
condition = {
expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
title = "condition"
description = "test condition"
location = "xxx"
}
allow = {
values = ["EXTERNAL_1"]
}
},
{
condition = {
expression = "resource.matchTagId(\\"tagKeys/12345\\", \\"tagValues/12345\\")"
title = "condition2"
description = "test condition2"
location = "xxx"
}
allow = {
all = true
}
}
]
}
}'''
_, resources = plan_runner(org_policies=policies)
assert len(resources) == 3 assert len(resources) == 3
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
@ -200,7 +236,7 @@ def test_policy_list(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -215,14 +251,10 @@ def test_policy_list(plan_runner):
assert p3['rules'][2] == { assert p3['rules'][2] == {
'allow_all': 'TRUE', 'allow_all': 'TRUE',
'condition': [{ 'condition': [{
'description': 'description': 'test condition2',
'test condition2', 'expression': 'resource.matchTagId(cc, dd)',
'expression': 'location': 'xxx',
'resource.matchTagId("tagKeys/12345", "tagValues/12345")', 'title': 'condition2'
'location':
'xxx',
'title':
'condition2'
}], }],
'deny_all': None, 'deny_all': None,
'enforce': None, 'enforce': None,
@ -244,7 +276,7 @@ def test_policy_implementation(plan_runner):
assert list(diff1) == [ assert list(diff1) == [
'--- \n', '--- \n',
'+++ \n', '+++ \n',
'@@ -14,14 +14,14 @@\n', '@@ -14,7 +14,7 @@\n',
' * limitations under the License.\n', ' * limitations under the License.\n',
' */\n', ' */\n',
' \n', ' \n',
@ -252,8 +284,10 @@ def test_policy_implementation(plan_runner):
'+# tfdoc:file:description Folder-level organization policies.\n', '+# tfdoc:file:description Folder-level organization policies.\n',
' \n', ' \n',
' locals {\n', ' locals {\n',
' _factory_data_raw = (\n',
'@@ -69,8 +69,8 @@\n',
' org_policies = {\n', ' org_policies = {\n',
' for k, v in var.org_policies :\n', ' for k, v in local._org_policies :\n',
' k => merge(v, {\n', ' k => merge(v, {\n',
'- name = "projects/${local.project.project_id}/policies/${k}"\n', '- name = "projects/${local.project.project_id}/policies/${k}"\n',
'- parent = "projects/${local.project.project_id}"\n', '- parent = "projects/${local.project.project_id}"\n',
@ -268,7 +302,7 @@ def test_policy_implementation(plan_runner):
assert list(diff2) == [ assert list(diff2) == [
'--- \n', '--- \n',
'+++ \n', '+++ \n',
'@@ -14,14 +14,14 @@\n', '@@ -14,7 +14,7 @@\n',
' * limitations under the License.\n', ' * limitations under the License.\n',
' */\n', ' */\n',
' \n', ' \n',
@ -276,8 +310,10 @@ def test_policy_implementation(plan_runner):
'+# tfdoc:file:description Organization-level organization policies.\n', '+# tfdoc:file:description Organization-level organization policies.\n',
' \n', ' \n',
' locals {\n', ' locals {\n',
' _factory_data_raw = (\n',
'@@ -69,8 +69,8 @@\n',
' org_policies = {\n', ' org_policies = {\n',
' for k, v in var.org_policies :\n', ' for k, v in local._org_policies :\n',
' k => merge(v, {\n', ' k => merge(v, {\n',
'- name = "${local.folder.name}/policies/${k}"\n', '- name = "${local.folder.name}/policies/${k}"\n',
'- parent = local.folder.name\n', '- parent = local.folder.name\n',
@ -286,7 +322,7 @@ def test_policy_implementation(plan_runner):
' \n', ' \n',
' is_boolean_policy = v.allow == null && v.deny == null\n', ' is_boolean_policy = v.allow == null && v.deny == null\n',
' has_values = (\n', ' has_values = (\n',
'@@ -94,4 +94,12 @@\n', '@@ -143,4 +143,12 @@\n',
' }\n', ' }\n',
' }\n', ' }\n',
' }\n', ' }\n',

View File

@ -1,13 +0,0 @@
# 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
#
# http://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.

View File

@ -1,22 +0,0 @@
/**
* 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
*
* http://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.
*/
module "org-policy" {
source = "../../../../modules/organization-policy"
config_directory = var.config_directory
policies = var.policies
}

View File

@ -1,40 +0,0 @@
# 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
#
# http://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.
organizations/1234567890:
constraints/compute.vmExternalIpAccess:
rules:
- deny_all: true
folders/1234567890:
compute.vmCanIpForward:
inherit_from_parent: false
reset: false
rules:
- allow: []
projects/my-project-id:
run.allowedIngress:
inherit_from_parent: true
rules:
- allow: ['internal']
condition:
description: allow internal ingress
expression: resource.matchTag("123456789/environment", "prod")
location: test.log
title: allow-for-prod
iam.allowServiceAccountCredentialLifetimeExtension:
rules:
- deny: []
compute.disableGlobalLoadBalancing:
reset: true

View File

@ -1,46 +0,0 @@
/**
* 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
*
* http://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.
*/
variable "config_directory" {
description = "Paths to a folder where organization policy configs are stored in yaml format. Files suffix must be `.yaml`."
type = string
default = null
}
variable "policies" {
description = "Organization policies keyed by parent in format `projects/project-id`, `folders/1234567890` or `organizations/1234567890`."
type = map(map(object({
inherit_from_parent = optional(bool) # List policy only.
reset = optional(bool)
rules = optional(
list(object({
allow = optional(list(string)) # List policy only. Stands for `allow_all` if set to empty list `[]` or to `values.allowed_values` if set to a list of values
deny = optional(list(string)) # List policy only. Stands for `deny_all` if set to empty list `[]` or to `values.denied_values` if set to a list of values
enforce = optional(bool) # Boolean policy only.
condition = optional(
object({
description = optional(string)
expression = optional(string)
location = optional(string)
title = optional(string)
})
)
}))
)
})))
default = {}
}

View File

@ -1,89 +0,0 @@
# 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
#
# http://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.
def test_org_policy_simple(plan_runner):
"Test vpc with no extra options."
org_policies = (
'{'
'"folders/1234567890" = {'
' "constraints/iam.disableServiceAccountKeyUpload" = {'
' rules = ['
' {'
' enforce = true,'
' }'
' ]'
' }'
' },'
' "organizations/1234567890" = {'
' "run.allowedIngress" = {'
' rules = ['
' {'
' allow = ["internal"],'
' condition = {'
' description= "allow ingress",'
' expression = "resource.matchTag(\'123456789/environment\', \'prod\')",'
' title = "allow-for-prod-org"'
' }'
' }'
' ]'
' }'
' }'
'}'
)
_, resources = plan_runner(
policies = org_policies
)
assert len(resources) == 2
org_policy = [r for r in resources if r["values"]
["name"].endswith('iam.disableServiceAccountKeyUpload')][0]["values"]
assert org_policy["parent"] == "folders/1234567890"
assert org_policy["spec"][0]["rules"][0]["enforce"] == "TRUE"
def test_org_policy_factory(plan_runner):
"Test yaml based configuration"
_, resources = plan_runner(
config_directory="./policies",
)
assert len(resources) == 5
org_policy = [r for r in resources if r["values"]
["name"].endswith('run.allowedIngress')][0]["values"]["spec"][0]
assert org_policy["inherit_from_parent"] == True
assert org_policy["rules"][0]["condition"][0]["title"] == "allow-for-prod"
assert set(org_policy["rules"][0]["values"][0]["allowed_values"]) == set(["internal"])
def test_combined_org_policy_config(plan_runner):
"Test combined (yaml, hcl) policy configuration"
org_policies = (
'{'
'"folders/3456789012" = {'
' "constraints/iam.disableServiceAccountKeyUpload" = {'
' rules = ['
' {'
' enforce = true'
' }'
' ]'
' }'
' }'
'}'
)
_, resources = plan_runner(
config_directory="./policies",
policies = org_policies
)
assert len(resources) == 6

View File

@ -26,6 +26,7 @@ module "test" {
labels = var.labels labels = var.labels
lien_reason = var.lien_reason lien_reason = var.lien_reason
org_policies = var.org_policies org_policies = var.org_policies
org_policies_data_path = var.org_policies_data_path
oslogin = var.oslogin oslogin = var.oslogin
oslogin_admins = var.oslogin_admins oslogin_admins = var.oslogin_admins
oslogin_users = var.oslogin_users oslogin_users = var.oslogin_users

View File

@ -69,6 +69,11 @@ variable "org_policies" {
default = {} default = {}
} }
variable "org_policies_data_path" {
type = any
default = null
}
variable "oslogin" { variable "oslogin" {
type = bool type = bool
default = false default = false

View File

@ -12,31 +12,106 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import hcl2
import yaml
BOOLEAN_POLICIES = '''{
"iam.disableServiceAccountKeyCreation" = {
enforce = true
}
"iam.disableServiceAccountKeyUpload" = {
enforce = false
rules = [
{
condition = {
expression = "resource.matchTagId(aa, bb)"
title = "condition"
description = "test condition"
location = "xxx"
}
enforce = true
}
]
}
}'''
LIST_POLICIES = '''{
"compute.vmExternalIpAccess" = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
}
"compute.restrictLoadBalancerCreationForTypes" = {
deny = { values = ["in:EXTERNAL"] }
rules = [
{
condition = {
expression = "resource.matchTagId(aa, bb)"
title = "condition"
description = "test condition"
location = "xxx"
}
allow = {
values = ["EXTERNAL_1"]
}
},
{
condition = {
expression = "resource.matchTagId(cc, dd)"
title = "condition2"
description = "test condition2"
location = "xxx"
}
allow = {
all = true
}
}
]
}
}'''
def test_policy_boolean(plan_runner): def test_policy_boolean(plan_runner):
"Test boolean org policy." "Test boolean org policy."
policies = '''{ _, resources = plan_runner(org_policies=BOOLEAN_POLICIES)
"iam.disableServiceAccountKeyCreation" = { validate_policy_boolean_resources(resources)
enforce = true
}
"iam.disableServiceAccountKeyUpload" = {
enforce = false
rules = [
{
condition = {
expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
title = "condition"
description = "test condition"
location = "xxx"
}
enforce = true
}
]
}
}'''
_, resources = plan_runner(org_policies=policies)
assert len(resources) == 6
def test_policy_list(plan_runner):
"Test list org policy."
_, resources = plan_runner(org_policies=LIST_POLICIES)
validate_policy_list_resources(resources)
def test_policy_boolean_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {BOOLEAN_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_boolean_resources(resources)
def test_policy_list_factory(plan_runner, tmp_path):
# convert hcl policies to yaml
hcl_policies = f'p = {LIST_POLICIES}'
yaml_policies = yaml.dump(hcl2.loads(hcl_policies)['p'])
yaml_file = tmp_path / 'policies.yaml'
yaml_file.write_text(yaml_policies)
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_list_resources(resources)
def validate_policy_boolean_resources(resources):
assert len(resources) == 6
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
assert len(policies) == 2 assert len(policies) == 2
assert all(x['values']['parent'] == 'projects/my-project' for x in policies) assert all(x['values']['parent'] == 'projects/my-project' for x in policies)
@ -77,7 +152,7 @@ def test_policy_boolean(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -87,46 +162,7 @@ def test_policy_boolean(plan_runner):
} }
def test_policy_list(plan_runner): def validate_policy_list_resources(resources):
"Test list org policy."
policies = '''{
"compute.vmExternalIpAccess" = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
}
"compute.restrictLoadBalancerCreationForTypes" = {
deny = { values = ["in:EXTERNAL"] }
rules = [
{
condition = {
expression = "resource.matchTagId(\\"tagKeys/1234\\", \\"tagValues/1234\\")"
title = "condition"
description = "test condition"
location = "xxx"
}
allow = {
values = ["EXTERNAL_1"]
}
},
{
condition = {
expression = "resource.matchTagId(\\"tagKeys/12345\\", \\"tagValues/12345\\")"
title = "condition2"
description = "test condition2"
location = "xxx"
}
allow = {
all = true
}
}
]
}
}'''
_, resources = plan_runner(org_policies=policies)
assert len(resources) == 7 assert len(resources) == 7
policies = [r for r in resources if r['type'] == 'google_org_policy_policy'] policies = [r for r in resources if r['type'] == 'google_org_policy_policy']
@ -195,7 +231,7 @@ def test_policy_list(plan_runner):
'allow_all': None, 'allow_all': None,
'condition': [{ 'condition': [{
'description': 'test condition', 'description': 'test condition',
'expression': 'resource.matchTagId("tagKeys/1234", "tagValues/1234")', 'expression': 'resource.matchTagId(aa, bb)',
'location': 'xxx', 'location': 'xxx',
'title': 'condition' 'title': 'condition'
}], }],
@ -210,14 +246,10 @@ def test_policy_list(plan_runner):
assert p3['rules'][2] == { assert p3['rules'][2] == {
'allow_all': 'TRUE', 'allow_all': 'TRUE',
'condition': [{ 'condition': [{
'description': 'description': 'test condition2',
'test condition2', 'expression': 'resource.matchTagId(cc, dd)',
'expression': 'location': 'xxx',
'resource.matchTagId("tagKeys/12345", "tagValues/12345")', 'title': 'condition2'
'location':
'xxx',
'title':
'condition2'
}], }],
'deny_all': None, 'deny_all': None,
'enforce': None, 'enforce': None,

View File

@ -3,3 +3,4 @@ PyYAML>=6.0
tftest>=1.7.6 tftest>=1.7.6
marko>=1.2.0 marko>=1.2.0
deepdiff>=5.7.0 deepdiff>=5.7.0
python-hcl2>=3.0.5