Add support for org policies to folder and project modules (#58)
* modules/folders: add support for org policies * update README * update cloud config modules READMEs * modules/project: add org policies
This commit is contained in:
parent
2e2d5f27c6
commit
a280dd880d
|
@ -75,12 +75,12 @@ module "cos-coredns" {
|
||||||
| name | description | type | required | default |
|
| name | description | type | required | default |
|
||||||
|---|---|:---: |:---:|:---:|
|
|---|---|:---: |:---:|:---:|
|
||||||
| *cloud_config* | Cloud config template path. If null default will be used. | <code title="">string</code> | | <code title="">null</code> |
|
| *cloud_config* | Cloud config template path. If null default will be used. | <code title="">string</code> | | <code title="">null</code> |
|
||||||
| *config_variables* | Additional variables used to render the cloud-config template. | <code title="map(any)">map(any)</code> | | <code title="">{}</code> |
|
| *config_variables* | Additional variables used to render the cloud-config and CoreDNS templates. | <code title="map(any)">map(any)</code> | | <code title="">{}</code> |
|
||||||
| *coredns_config* | CoreDNS configuration path, if null default will be used. | <code title="">string</code> | | <code title="">null</code> |
|
| *coredns_config* | CoreDNS configuration path, if null default will be used. | <code title="">string</code> | | <code title="">null</code> |
|
||||||
| *file_defaults* | Default owner and permissions for files. | <code title="object({ owner = string permissions = string })">object({...})</code> | | <code title="{ owner = "root" permissions = "0644" }">...</code> |
|
| *file_defaults* | Default owner and permissions for files. | <code title="object({ owner = string permissions = string })">object({...})</code> | | <code title="{ owner = "root" permissions = "0644" }">...</code> |
|
||||||
| *files* | Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null. | <code title="map(object({ content = string owner = string permissions = string }))">map(object({...}))</code> | | <code title="">{}</code> |
|
| *files* | Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null. | <code title="map(object({ content = string owner = string permissions = string }))">map(object({...}))</code> | | <code title="">{}</code> |
|
||||||
| *test_instance* | Test/development instance attributes, leave null to skip creation. | <code title="object({ project_id = string zone = string name = string type = string network = string subnetwork = string })">object({...})</code> | | <code title="">null</code> |
|
| *test_instance* | Test/development instance attributes, leave null to skip creation. | <code title="object({ project_id = string zone = string name = string type = string network = string subnetwork = string })">object({...})</code> | | <code title="">null</code> |
|
||||||
| *test_instance_defaults* | Test/development instance defaults used for optional configuration. | <code title="object({ disks = map(object({ read_only = bool size = number })) metadata = map(string) service_account_roles = list(string) tags = list(string) })">object({...})</code> | | <code title="{ disks = {} metadata = {} service_account_roles = [ "roles/logging.logWriter", "roles/monitoring.metricWriter" ] tags = ["ssh"] }">...</code> |
|
| *test_instance_defaults* | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | <code title="object({ disks = map(object({ read_only = bool size = number })) image = string metadata = map(string) nat = bool service_account_roles = list(string) tags = list(string) })">object({...})</code> | | <code title="{ disks = {} image = null metadata = {} nat = false service_account_roles = [ "roles/logging.logWriter", "roles/monitoring.metricWriter" ] tags = ["ssh"] }">...</code> |
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ module "cos-mysql" {
|
||||||
| *mysql_config* | MySQL configuration file content, if null container default will be used. | <code title="">string</code> | | <code title="">null</code> |
|
| *mysql_config* | MySQL configuration file content, if null container default will be used. | <code title="">string</code> | | <code title="">null</code> |
|
||||||
| *mysql_data_disk* | MySQL data disk name in /dev/disk/by-id/ including the google- prefix. If null the boot disk will be used for data. | <code title="">string</code> | | <code title="">null</code> |
|
| *mysql_data_disk* | MySQL data disk name in /dev/disk/by-id/ including the google- prefix. If null the boot disk will be used for data. | <code title="">string</code> | | <code title="">null</code> |
|
||||||
| *test_instance* | Test/development instance attributes, leave null to skip creation. | <code title="object({ project_id = string zone = string name = string type = string network = string subnetwork = string })">object({...})</code> | | <code title="">null</code> |
|
| *test_instance* | Test/development instance attributes, leave null to skip creation. | <code title="object({ project_id = string zone = string name = string type = string network = string subnetwork = string })">object({...})</code> | | <code title="">null</code> |
|
||||||
| *test_instance_defaults* | Test/development instance defaults used for optional configuration. | <code title="object({ disks = map(object({ read_only = bool size = number })) metadata = map(string) service_account_roles = list(string) tags = list(string) })">object({...})</code> | | <code title="{ disks = {} metadata = {} service_account_roles = [ "roles/logging.logWriter", "roles/monitoring.metricWriter" ] tags = ["ssh"] }">...</code> |
|
| *test_instance_defaults* | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | <code title="object({ disks = map(object({ read_only = bool size = number })) image = string metadata = map(string) nat = bool service_account_roles = list(string) tags = list(string) })">object({...})</code> | | <code title="{ disks = {} image = null metadata = {} nat = false service_account_roles = [ "roles/logging.logWriter", "roles/monitoring.metricWriter" ] tags = ["ssh"] }">...</code> |
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
|
|
|
@ -65,10 +65,11 @@ module "on-prem" {
|
||||||
| name | description | type | required | default |
|
| name | description | type | required | default |
|
||||||
|---|---|:---: |:---:|:---:|
|
|---|---|:---: |:---:|:---:|
|
||||||
| vpn_config | VPN configuration, type must be one of 'dynamic' or 'static'. | <code title="object({ peer_ip = string shared_secret = string type = string })">object({...})</code> | ✓ | |
|
| vpn_config | VPN configuration, type must be one of 'dynamic' or 'static'. | <code title="object({ peer_ip = string shared_secret = string type = string })">object({...})</code> | ✓ | |
|
||||||
|
| *config_variables* | Additional variables used to render the cloud-config and CoreDNS templates. | <code title="map(any)">map(any)</code> | | <code title="">{}</code> |
|
||||||
| *coredns_config* | CoreDNS configuration path, if null default will be used. | <code title="">string</code> | | <code title="">null</code> |
|
| *coredns_config* | CoreDNS configuration path, if null default will be used. | <code title="">string</code> | | <code title="">null</code> |
|
||||||
| *local_ip_cidr_range* | IP CIDR range used for the Docker onprem network. | <code title="">string</code> | | <code title="">192.168.192.0/24</code> |
|
| *local_ip_cidr_range* | IP CIDR range used for the Docker onprem network. | <code title="">string</code> | | <code title="">192.168.192.0/24</code> |
|
||||||
| *test_instance* | Test/development instance attributes, leave null to skip creation. | <code title="object({ project_id = string zone = string name = string type = string network = string subnetwork = string })">object({...})</code> | | <code title="">null</code> |
|
| *test_instance* | Test/development instance attributes, leave null to skip creation. | <code title="object({ project_id = string zone = string name = string type = string network = string subnetwork = string })">object({...})</code> | | <code title="">null</code> |
|
||||||
| *test_instance_defaults* | Test/development instance defaults used for optional configuration. | <code title="object({ disks = map(object({ read_only = bool size = number })) metadata = map(string) service_account_roles = list(string) tags = list(string) })">object({...})</code> | | <code title="{ disks = {} metadata = {} service_account_roles = [ "roles/logging.logWriter", "roles/monitoring.metricWriter" ] tags = ["ssh"] }">...</code> |
|
| *test_instance_defaults* | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | <code title="object({ disks = map(object({ read_only = bool size = number })) image = string metadata = map(string) nat = bool service_account_roles = list(string) tags = list(string) })">object({...})</code> | | <code title="{ disks = {} image = null metadata = {} nat = false service_account_roles = [ "roles/logging.logWriter", "roles/monitoring.metricWriter" ] tags = ["ssh"] }">...</code> |
|
||||||
| *vpn_dynamic_config* | BGP configuration for dynamic VPN, ignored if VPN type is 'static'. | <code title="object({ local_bgp_asn = number local_bgp_address = string peer_bgp_asn = number peer_bgp_address = string })">object({...})</code> | | <code title="{ local_bgp_asn = 65002 local_bgp_address = "169.254.0.2" peer_bgp_asn = 65001 peer_bgp_address = "169.254.0.1" }">...</code> |
|
| *vpn_dynamic_config* | BGP configuration for dynamic VPN, ignored if VPN type is 'static'. | <code title="object({ local_bgp_asn = number local_bgp_address = string peer_bgp_asn = number peer_bgp_address = string })">object({...})</code> | | <code title="{ local_bgp_asn = 65002 local_bgp_address = "169.254.0.2" peer_bgp_asn = 65001 peer_bgp_address = "169.254.0.1" }">...</code> |
|
||||||
| *vpn_static_ranges* | Remote CIDR ranges for static VPN, ignored if VPN type is 'dynamic'. | <code title="list(string)">list(string)</code> | | <code title="">["10.0.0.0/8"]</code> |
|
| *vpn_static_ranges* | Remote CIDR ranges for static VPN, ignored if VPN type is 'dynamic'. | <code title="list(string)">list(string)</code> | | <code title="">["10.0.0.0/8"]</code> |
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
# Google Cloud Folder Module
|
# Google Cloud Folder Module
|
||||||
|
|
||||||
This module allow creation and management of sets of folders sharing a common parent, and their individual IAM bindings.
|
This module allow creation and management of sets of folders sharing a common parent, and their individual IAM bindings. It also allows setting a common set of organization policies on all folders.
|
||||||
|
|
||||||
## Example
|
## Examples
|
||||||
|
|
||||||
|
### IAM bindings
|
||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
module "folder" {
|
module "folder" {
|
||||||
|
@ -20,6 +22,28 @@ module "folder" {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Organization policies
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
module "folder" {
|
||||||
|
source = "./modules/folder"
|
||||||
|
parent = "organizations/1234567890"
|
||||||
|
names = ["Folder one", "Folder two]
|
||||||
|
policy_boolean = {
|
||||||
|
"constraints/compute.disableGuestAttributesAccess" = true
|
||||||
|
"constraints/compute.skipDefaultNetworkCreation" = true
|
||||||
|
}
|
||||||
|
policy_list = {
|
||||||
|
"constraints/compute.trustedImageProjects" = {
|
||||||
|
inherit_from_parent = null
|
||||||
|
suggested_value = null
|
||||||
|
status = true
|
||||||
|
values = ["projects/my-project"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
<!-- BEGIN TFDOC -->
|
<!-- BEGIN TFDOC -->
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
|
@ -29,6 +53,8 @@ module "folder" {
|
||||||
| *iam_members* | List of IAM members keyed by folder name and role. | <code title="map(map(list(string)))">map(map(list(string)))</code> | | <code title="">null</code> |
|
| *iam_members* | List of IAM members keyed by folder name and role. | <code title="map(map(list(string)))">map(map(list(string)))</code> | | <code title="">null</code> |
|
||||||
| *iam_roles* | List of IAM roles keyed by folder name. | <code title="map(list(string))">map(list(string))</code> | | <code title="">null</code> |
|
| *iam_roles* | List of IAM roles keyed by folder name. | <code title="map(list(string))">map(list(string))</code> | | <code title="">null</code> |
|
||||||
| *names* | Folder names. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
| *names* | Folder names. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
||||||
|
| *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | <code title="map(bool)">map(bool)</code> | | <code title="">{}</code> |
|
||||||
|
| *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | <code title="map(object({ inherit_from_parent = bool suggested_value = string status = bool values = list(string) }))">map(object({...}))</code> | | <code title="">{}</code> |
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,22 @@ locals {
|
||||||
"${pair.name}-${pair.role}" => pair
|
"${pair.name}-${pair.role}" => pair
|
||||||
}
|
}
|
||||||
iam_members = var.iam_members == null ? {} : var.iam_members
|
iam_members = var.iam_members == null ? {} : var.iam_members
|
||||||
|
policy_boolean_pairs = {
|
||||||
|
for pair in setproduct(var.names, keys(var.policy_boolean)) :
|
||||||
|
"${pair.0}-${pair.1}" => {
|
||||||
|
folder = pair.0,
|
||||||
|
policy = pair.1,
|
||||||
|
policy_data = var.policy_boolean[pair.1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
policy_list_pairs = {
|
||||||
|
for pair in setproduct(var.names, keys(var.policy_list)) :
|
||||||
|
"${pair.0}-${pair.1}" => {
|
||||||
|
folder = pair.0,
|
||||||
|
policy = pair.1,
|
||||||
|
policy_data = var.policy_list[pair.1]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_folder" "folders" {
|
resource "google_folder" "folders" {
|
||||||
|
@ -48,3 +64,75 @@ resource "google_folder_iam_binding" "authoritative" {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "google_folder_organization_policy" "boolean" {
|
||||||
|
for_each = local.policy_boolean_pairs
|
||||||
|
folder = google_folder.folders[each.value.folder].id
|
||||||
|
constraint = each.value.policy
|
||||||
|
|
||||||
|
dynamic boolean_policy {
|
||||||
|
for_each = each.value.policy_data == null ? [] : [each.value.policy_data]
|
||||||
|
iterator = policy
|
||||||
|
content {
|
||||||
|
enforced = policy.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic restore_policy {
|
||||||
|
for_each = each.value.policy_data == null ? [""] : []
|
||||||
|
content {
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_folder_organization_policy" "list" {
|
||||||
|
for_each = local.policy_list_pairs
|
||||||
|
folder = google_folder.folders[each.value.folder].id
|
||||||
|
constraint = each.value.policy
|
||||||
|
|
||||||
|
dynamic list_policy {
|
||||||
|
for_each = each.value.policy_data.status == null ? [] : [each.value.policy_data]
|
||||||
|
iterator = policy
|
||||||
|
content {
|
||||||
|
inherit_from_parent = policy.value.inherit_from_parent
|
||||||
|
suggested_value = policy.value.suggested_value
|
||||||
|
dynamic allow {
|
||||||
|
for_each = policy.value.status ? [""] : []
|
||||||
|
content {
|
||||||
|
values = (
|
||||||
|
try(length(policy.value.values) > 0, false)
|
||||||
|
? policy.value.values
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
all = (
|
||||||
|
try(length(policy.value.values) > 0, false)
|
||||||
|
? null
|
||||||
|
: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamic deny {
|
||||||
|
for_each = policy.value.status ? [] : [""]
|
||||||
|
content {
|
||||||
|
values = (
|
||||||
|
try(length(policy.value.values) > 0, false)
|
||||||
|
? policy.value.values
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
all = (
|
||||||
|
try(length(policy.value.values) > 0, false)
|
||||||
|
? null
|
||||||
|
: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic restore_policy {
|
||||||
|
for_each = each.value.policy_data.status == null ? [true] : []
|
||||||
|
content {
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,11 @@ output "folder" {
|
||||||
output "id" {
|
output "id" {
|
||||||
description = "Folder id (for single use)."
|
description = "Folder id (for single use)."
|
||||||
value = local.has_folders ? local.folders[0].name : null
|
value = local.has_folders ? local.folders[0].name : null
|
||||||
|
depends_on = [
|
||||||
|
google_folder_iam_binding.authoritative,
|
||||||
|
google_folder_organization_policy.boolean,
|
||||||
|
google_folder_organization_policy.list
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
output "name" {
|
output "name" {
|
||||||
|
@ -41,6 +46,11 @@ output "ids" {
|
||||||
? zipmap(var.names, [for f in local.folders : f.name])
|
? zipmap(var.names, [for f in local.folders : f.name])
|
||||||
: {}
|
: {}
|
||||||
)
|
)
|
||||||
|
depends_on = [
|
||||||
|
google_folder_iam_binding.authoritative,
|
||||||
|
google_folder_organization_policy.boolean,
|
||||||
|
google_folder_organization_policy.list
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
output "names" {
|
output "names" {
|
||||||
|
@ -55,6 +65,11 @@ output "names" {
|
||||||
output "ids_list" {
|
output "ids_list" {
|
||||||
description = "List of folder ids."
|
description = "List of folder ids."
|
||||||
value = [for f in local.folders : f.name]
|
value = [for f in local.folders : f.name]
|
||||||
|
depends_on = [
|
||||||
|
google_folder_iam_binding.authoritative,
|
||||||
|
google_folder_organization_policy.boolean,
|
||||||
|
google_folder_organization_policy.list
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
output "names_list" {
|
output "names_list" {
|
||||||
|
|
|
@ -36,3 +36,20 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "policy_boolean" {
|
||||||
|
description = "Map of boolean org policies and enforcement value, set value to null for policy restore."
|
||||||
|
type = map(bool)
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "policy_list" {
|
||||||
|
description = "Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny."
|
||||||
|
type = map(object({
|
||||||
|
inherit_from_parent = bool
|
||||||
|
suggested_value = string
|
||||||
|
status = bool
|
||||||
|
values = list(string)
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
|
@ -1,23 +1,54 @@
|
||||||
# Project Module
|
# Project Module
|
||||||
|
|
||||||
## Example
|
## Examples
|
||||||
|
|
||||||
|
### Minimal example with IAM
|
||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
module "project" {
|
module "project" {
|
||||||
source = "./modules/project"
|
source = "./modules/project"
|
||||||
parent = var.folder.id
|
billing_account = "123456-123456-123456"
|
||||||
billing_account = var.billing_account_id
|
|
||||||
prefix = "foo"
|
|
||||||
name = "project-example"
|
name = "project-example"
|
||||||
oslogin = true
|
parent = "folders/1234567890"
|
||||||
oslogin_admins = var.admins
|
prefix = "foo"
|
||||||
services = concat(var.project_services, [
|
services = [
|
||||||
"cloudkms.googleapis.com", "accesscontextmanager.googleapis.com"
|
"resourceviews.googleapis.com",
|
||||||
])
|
"stackdriver.googleapis.com"
|
||||||
|
]
|
||||||
iam_roles = ["roles/container.hostServiceAgentUser"]
|
iam_roles = ["roles/container.hostServiceAgentUser"]
|
||||||
iam_members = { "roles/container.hostServiceAgentUser" = [
|
iam_members = {
|
||||||
|
"roles/container.hostServiceAgentUser" = [
|
||||||
"serviceAccount:${var.gke_service_account}"
|
"serviceAccount:${var.gke_service_account}"
|
||||||
] }
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Organization policies
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
module "project" {
|
||||||
|
source = "./modules/project"
|
||||||
|
billing_account = "123456-123456-123456"
|
||||||
|
name = "project-example"
|
||||||
|
parent = "folders/1234567890"
|
||||||
|
prefix = "foo"
|
||||||
|
services = [
|
||||||
|
"resourceviews.googleapis.com",
|
||||||
|
"stackdriver.googleapis.com"
|
||||||
|
]
|
||||||
|
policy_boolean = {
|
||||||
|
"constraints/compute.disableGuestAttributesAccess" = true
|
||||||
|
"constraints/compute.skipDefaultNetworkCreation" = true
|
||||||
|
}
|
||||||
|
policy_list = {
|
||||||
|
"constraints/compute.trustedImageProjects" = {
|
||||||
|
inherit_from_parent = null
|
||||||
|
suggested_value = null
|
||||||
|
status = true
|
||||||
|
values = ["projects/my-project"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -40,6 +71,8 @@ module "project" {
|
||||||
| *oslogin* | Enable OS Login. | <code title="">bool</code> | | <code title="">false</code> |
|
| *oslogin* | Enable OS Login. | <code title="">bool</code> | | <code title="">false</code> |
|
||||||
| *oslogin_admins* | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
| *oslogin_admins* | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
||||||
| *oslogin_users* | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
| *oslogin_users* | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
||||||
|
| *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | <code title="map(bool)">map(bool)</code> | | <code title="">{}</code> |
|
||||||
|
| *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | <code title="map(object({ inherit_from_parent = bool suggested_value = string status = bool values = list(string) }))">map(object({...}))</code> | | <code title="">{}</code> |
|
||||||
| *prefix* | Prefix used to generate project id and name. | <code title="">string</code> | | <code title="">null</code> |
|
| *prefix* | Prefix used to generate project id and name. | <code title="">string</code> | | <code title="">null</code> |
|
||||||
| *services* | Service APIs to enable. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
| *services* | Service APIs to enable. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
||||||
|
|
||||||
|
@ -47,13 +80,12 @@ module "project" {
|
||||||
|
|
||||||
| name | description | sensitive |
|
| name | description | sensitive |
|
||||||
|---|---|:---:|
|
|---|---|:---:|
|
||||||
| cloudsvc_service_account | Cloud services service account (depends on services). | |
|
| cloudsvc_service_account | Cloud services service account. | |
|
||||||
| custom_roles | Ids of the created custom roles. | |
|
| custom_roles | Ids of the created custom roles. | |
|
||||||
| gce_service_account | Default GCE service account (depends on services). | |
|
| gce_service_account | Default GCE service account. | |
|
||||||
| gcr_service_account | Default GCR service account (depends on services). | |
|
| gcr_service_account | Default GCR service account. | |
|
||||||
| gke_service_account | Default GKE service account (depends on services). | |
|
| gke_service_account | Default GKE service account. | |
|
||||||
| iam_project_id | Project id (depends on services and IAM bindings). | |
|
| name | Project ame. | |
|
||||||
| name | Name (depends on services). | |
|
| number | Project number. | |
|
||||||
| number | Project number (depends on services). | |
|
| project_id | Project id. | |
|
||||||
| project_id | Project id (depends on services). | |
|
|
||||||
<!-- END TFDOC -->
|
<!-- END TFDOC -->
|
||||||
|
|
|
@ -88,6 +88,10 @@ resource "google_project_iam_binding" "authoritative" {
|
||||||
project = google_project.project.project_id
|
project = google_project.project.project_id
|
||||||
role = each.value
|
role = each.value
|
||||||
members = lookup(var.iam_members, each.value, [])
|
members = lookup(var.iam_members, each.value, [])
|
||||||
|
depends_on = [
|
||||||
|
google_project_service.project_services,
|
||||||
|
google_project_iam_custom_role.roles
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_project_iam_member" "additive" {
|
resource "google_project_iam_member" "additive" {
|
||||||
|
@ -124,3 +128,76 @@ resource "google_project_iam_member" "oslogin_users" {
|
||||||
role = "roles/compute.osLogin"
|
role = "roles/compute.osLogin"
|
||||||
member = each.value
|
member = each.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "google_project_organization_policy" "boolean" {
|
||||||
|
for_each = var.policy_boolean
|
||||||
|
project = google_project.project.project_id
|
||||||
|
constraint = each.key
|
||||||
|
|
||||||
|
dynamic boolean_policy {
|
||||||
|
for_each = each.value == null ? [] : [each.value]
|
||||||
|
iterator = policy
|
||||||
|
content {
|
||||||
|
enforced = policy.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic restore_policy {
|
||||||
|
for_each = each.value == null ? [""] : []
|
||||||
|
content {
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_project_organization_policy" "list" {
|
||||||
|
for_each = var.policy_list
|
||||||
|
project = google_project.project.project_id
|
||||||
|
constraint = each.key
|
||||||
|
|
||||||
|
dynamic list_policy {
|
||||||
|
for_each = each.value.status == null ? [] : [each.value]
|
||||||
|
iterator = policy
|
||||||
|
content {
|
||||||
|
inherit_from_parent = policy.value.inherit_from_parent
|
||||||
|
suggested_value = policy.value.suggested_value
|
||||||
|
dynamic allow {
|
||||||
|
for_each = policy.value.status ? [""] : []
|
||||||
|
content {
|
||||||
|
values = (
|
||||||
|
try(length(policy.value.values) > 0, false)
|
||||||
|
? policy.value.values
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
all = (
|
||||||
|
try(length(policy.value.values) > 0, false)
|
||||||
|
? null
|
||||||
|
: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamic deny {
|
||||||
|
for_each = policy.value.status ? [] : [""]
|
||||||
|
content {
|
||||||
|
values = (
|
||||||
|
try(length(policy.value.values) > 0, false)
|
||||||
|
? policy.value.values
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
all = (
|
||||||
|
try(length(policy.value.values) > 0, false)
|
||||||
|
? null
|
||||||
|
: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic restore_policy {
|
||||||
|
for_each = each.value.status == null ? [true] : []
|
||||||
|
content {
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,55 +15,55 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
output "project_id" {
|
output "project_id" {
|
||||||
description = "Project id (depends on services)."
|
description = "Project id."
|
||||||
value = google_project.project.project_id
|
value = google_project.project.project_id
|
||||||
depends_on = [
|
depends_on = [
|
||||||
|
google_project_organization_policy.boolean,
|
||||||
|
google_project_organization_policy.list,
|
||||||
google_project_service.project_services
|
google_project_service.project_services
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
output "iam_project_id" {
|
output "name" {
|
||||||
description = "Project id (depends on services and IAM bindings)."
|
description = "Project ame."
|
||||||
value = google_project.project.project_id
|
value = google_project.project.name
|
||||||
depends_on = [
|
depends_on = [
|
||||||
google_project_service.project_services,
|
google_project_organization_policy.boolean,
|
||||||
google_project_iam_binding.authoritative,
|
google_project_organization_policy.list,
|
||||||
google_project_iam_member.non_authoritative
|
google_project_service.project_services
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
output "name" {
|
|
||||||
description = "Name (depends on services)."
|
|
||||||
value = google_project.project.name
|
|
||||||
depends_on = [google_project_service.project_services]
|
|
||||||
}
|
|
||||||
|
|
||||||
output "number" {
|
output "number" {
|
||||||
description = "Project number (depends on services)."
|
description = "Project number."
|
||||||
value = google_project.project.number
|
value = google_project.project.number
|
||||||
depends_on = [google_project_service.project_services]
|
depends_on = [
|
||||||
|
google_project_organization_policy.boolean,
|
||||||
|
google_project_organization_policy.list,
|
||||||
|
google_project_service.project_services
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
output "cloudsvc_service_account" {
|
output "cloudsvc_service_account" {
|
||||||
description = "Cloud services service account (depends on services)."
|
description = "Cloud services service account."
|
||||||
value = "${local.cloudsvc_service_account}"
|
value = "${local.cloudsvc_service_account}"
|
||||||
depends_on = [google_project_service.project_services]
|
depends_on = [google_project_service.project_services]
|
||||||
}
|
}
|
||||||
|
|
||||||
output "gce_service_account" {
|
output "gce_service_account" {
|
||||||
description = "Default GCE service account (depends on services)."
|
description = "Default GCE service account."
|
||||||
value = local.gce_service_account
|
value = local.gce_service_account
|
||||||
depends_on = [google_project_service.project_services]
|
depends_on = [google_project_service.project_services]
|
||||||
}
|
}
|
||||||
|
|
||||||
output "gcr_service_account" {
|
output "gcr_service_account" {
|
||||||
description = "Default GCR service account (depends on services)."
|
description = "Default GCR service account."
|
||||||
value = local.gcr_service_account
|
value = local.gcr_service_account
|
||||||
depends_on = [google_project_service.project_services]
|
depends_on = [google_project_service.project_services]
|
||||||
}
|
}
|
||||||
|
|
||||||
output "gke_service_account" {
|
output "gke_service_account" {
|
||||||
description = "Default GKE service account (depends on services)."
|
description = "Default GKE service account."
|
||||||
value = local.gke_service_account
|
value = local.gke_service_account
|
||||||
depends_on = [google_project_service.project_services]
|
depends_on = [google_project_service.project_services]
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,23 @@ variable "parent" {
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "policy_boolean" {
|
||||||
|
description = "Map of boolean org policies and enforcement value, set value to null for policy restore."
|
||||||
|
type = map(bool)
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "policy_list" {
|
||||||
|
description = "Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny."
|
||||||
|
type = map(object({
|
||||||
|
inherit_from_parent = bool
|
||||||
|
suggested_value = string
|
||||||
|
status = bool
|
||||||
|
values = list(string)
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
variable "prefix" {
|
variable "prefix" {
|
||||||
description = "Prefix used to generate project id and name."
|
description = "Prefix used to generate project id and name."
|
||||||
type = string
|
type = string
|
||||||
|
|
|
@ -20,4 +20,6 @@ module "test" {
|
||||||
names = ["folder-a", "folder-b"]
|
names = ["folder-a", "folder-b"]
|
||||||
iam_members = var.iam_members
|
iam_members = var.iam_members
|
||||||
iam_roles = var.iam_roles
|
iam_roles = var.iam_roles
|
||||||
|
policy_boolean = var.policy_boolean
|
||||||
|
policy_list = var.policy_list
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,3 +23,18 @@ variable "iam_roles" {
|
||||||
type = map(list(string))
|
type = map(list(string))
|
||||||
default = null
|
default = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "policy_boolean" {
|
||||||
|
type = map(bool)
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "policy_list" {
|
||||||
|
type = map(object({
|
||||||
|
inherit_from_parent = bool
|
||||||
|
suggested_value = string
|
||||||
|
status = bool
|
||||||
|
values = list(string)
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Copyright 2020 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.
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
|
||||||
|
|
||||||
|
|
||||||
|
def test_policy_boolean(plan_runner):
|
||||||
|
"Test boolean folder policy."
|
||||||
|
policy_boolean = '{policy-a = true, policy-b = false, policy-c = null}'
|
||||||
|
_, resources = plan_runner(FIXTURES_DIR, policy_boolean=policy_boolean)
|
||||||
|
assert len(resources) == 8
|
||||||
|
resources = [r for r in resources if r['type']
|
||||||
|
== 'google_folder_organization_policy']
|
||||||
|
assert sorted([r['index'] for r in resources]) == [
|
||||||
|
'folder-a-policy-a',
|
||||||
|
'folder-a-policy-b',
|
||||||
|
'folder-a-policy-c',
|
||||||
|
'folder-b-policy-a',
|
||||||
|
'folder-b-policy-b',
|
||||||
|
'folder-b-policy-c'
|
||||||
|
]
|
||||||
|
policy_values = []
|
||||||
|
for resource in resources:
|
||||||
|
for policy in ('boolean_policy', 'restore_policy'):
|
||||||
|
value = resource['values'][policy]
|
||||||
|
if value:
|
||||||
|
policy_values.append((resource['index'], policy,) + value[0].popitem())
|
||||||
|
assert sorted(policy_values) == [
|
||||||
|
('folder-a-policy-a', 'boolean_policy', 'enforced', True),
|
||||||
|
('folder-a-policy-b', 'boolean_policy', 'enforced', False),
|
||||||
|
('folder-a-policy-c', 'restore_policy', 'default', True),
|
||||||
|
('folder-b-policy-a', 'boolean_policy', 'enforced', True),
|
||||||
|
('folder-b-policy-b', 'boolean_policy', 'enforced', False),
|
||||||
|
('folder-b-policy-c', 'restore_policy', 'default', True)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_policy_list(plan_runner):
|
||||||
|
"Test list org policy."
|
||||||
|
policy_list = (
|
||||||
|
'{'
|
||||||
|
'policy-a = {inherit_from_parent = true, suggested_value = null, status = true, values = []}, '
|
||||||
|
'policy-b = {inherit_from_parent = null, suggested_value = "foo", status = false, values = ["bar"]}, '
|
||||||
|
'policy-c = {inherit_from_parent = null, suggested_value = true, status = null, values = null}'
|
||||||
|
'}'
|
||||||
|
)
|
||||||
|
_, resources = plan_runner(FIXTURES_DIR, policy_list=policy_list)
|
||||||
|
assert len(resources) == 8
|
||||||
|
resources = [r for r in resources if r['type']
|
||||||
|
== 'google_folder_organization_policy']
|
||||||
|
assert sorted([r['index'] for r in resources]) == [
|
||||||
|
'folder-a-policy-a',
|
||||||
|
'folder-a-policy-b',
|
||||||
|
'folder-a-policy-c',
|
||||||
|
'folder-b-policy-a',
|
||||||
|
'folder-b-policy-b',
|
||||||
|
'folder-b-policy-c'
|
||||||
|
]
|
||||||
|
values = [r['values'] for r in resources]
|
||||||
|
assert [r['constraint'] for r in values] == [
|
||||||
|
'policy-a', 'policy-b', 'policy-c', 'policy-a', 'policy-b', 'policy-c'
|
||||||
|
]
|
||||||
|
for i in (0, 3):
|
||||||
|
assert values[i]['list_policy'][0]['allow'] == [
|
||||||
|
{'all': True, 'values': None}]
|
||||||
|
for i in (1, 4):
|
||||||
|
assert values[i]['list_policy'][0]['deny'] == [
|
||||||
|
{'all': False, 'values': ["bar"]}]
|
||||||
|
for i in (2, 5):
|
||||||
|
assert values[i]['restore_policy'] == [{'default': True}]
|
|
@ -30,6 +30,8 @@ module "test" {
|
||||||
oslogin_admins = var.oslogin_admins
|
oslogin_admins = var.oslogin_admins
|
||||||
oslogin_users = var.oslogin_users
|
oslogin_users = var.oslogin_users
|
||||||
parent = var.parent
|
parent = var.parent
|
||||||
|
policy_boolean = var.policy_boolean
|
||||||
|
policy_list = var.policy_list
|
||||||
prefix = var.prefix
|
prefix = var.prefix
|
||||||
services = var.services
|
services = var.services
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,21 @@ variable "parent" {
|
||||||
default = "folders/12345678"
|
default = "folders/12345678"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "policy_boolean" {
|
||||||
|
type = map(bool)
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "policy_list" {
|
||||||
|
type = map(object({
|
||||||
|
inherit_from_parent = bool
|
||||||
|
suggested_value = string
|
||||||
|
status = bool
|
||||||
|
values = list(string)
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
variable "prefix" {
|
variable "prefix" {
|
||||||
type = string
|
type = string
|
||||||
default = null
|
default = null
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Copyright 2020 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.
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
|
||||||
|
|
||||||
|
|
||||||
|
def test_policy_boolean(plan_runner):
|
||||||
|
"Test boolean org policy."
|
||||||
|
policy_boolean = '{policy-a = true, policy-b = false, policy-c = null}'
|
||||||
|
_, resources = plan_runner(FIXTURES_DIR, policy_boolean=policy_boolean)
|
||||||
|
assert len(resources) == 4
|
||||||
|
resources = [r for r in resources if r['type']
|
||||||
|
== 'google_project_organization_policy']
|
||||||
|
assert sorted([r['index'] for r in resources]) == [
|
||||||
|
'policy-a', 'policy-b', 'policy-c'
|
||||||
|
]
|
||||||
|
policy_values = []
|
||||||
|
for resource in resources:
|
||||||
|
for policy in ('boolean_policy', 'restore_policy'):
|
||||||
|
value = resource['values'][policy]
|
||||||
|
if value:
|
||||||
|
policy_values.append((policy,) + value[0].popitem())
|
||||||
|
assert sorted(policy_values) == [
|
||||||
|
('boolean_policy', 'enforced', False),
|
||||||
|
('boolean_policy', 'enforced', True),
|
||||||
|
('restore_policy', 'default', True)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_policy_list(plan_runner):
|
||||||
|
"Test list org policy."
|
||||||
|
policy_list = (
|
||||||
|
'{'
|
||||||
|
'policy-a = {inherit_from_parent = true, suggested_value = null, status = true, values = []}, '
|
||||||
|
'policy-b = {inherit_from_parent = null, suggested_value = "foo", status = false, values = ["bar"]}, '
|
||||||
|
'policy-c = {inherit_from_parent = null, suggested_value = true, status = null, values = null}'
|
||||||
|
'}'
|
||||||
|
)
|
||||||
|
_, resources = plan_runner(FIXTURES_DIR, policy_list=policy_list)
|
||||||
|
assert len(resources) == 4
|
||||||
|
values = [r['values'] for r in resources if r['type']
|
||||||
|
== 'google_project_organization_policy']
|
||||||
|
assert [r['constraint'] for r in values] == [
|
||||||
|
'policy-a', 'policy-b', 'policy-c'
|
||||||
|
]
|
||||||
|
assert values[0]['list_policy'][0]['allow'] == [
|
||||||
|
{'all': True, 'values': None}]
|
||||||
|
assert values[1]['list_policy'][0]['deny'] == [
|
||||||
|
{'all': False, 'values': ["bar"]}]
|
||||||
|
assert values[2]['restore_policy'] == [{'default': True}]
|
Loading…
Reference in New Issue