Merge branch 'master' into fast/gke2

This commit is contained in:
Julio Castillo 2022-07-12 12:16:04 +02:00
commit 25955b158a
75 changed files with 2527 additions and 78 deletions

3
.gitignore vendored
View File

@ -31,3 +31,6 @@ fast/stages/**/globals.auto.tfvars.json
cloud_sql_proxy
examples/cloud-operations/binauthz/tenant-setup.yaml
examples/cloud-operations/binauthz/app/app.yaml
env/
examples/cloud-operations/adfs/ansible/vars/vars.yaml
examples/cloud-operations/adfs/ansible/gssh.sh

View File

@ -14,7 +14,7 @@ Contributors are the engine that keeps Fabric alive so if you were or are planni
- [Design principles in action](#design-principles-in-action)
- [FAST stage design](#fast-stage-design)
- [Style guide reference](#style-guide-reference)
- [Checks, tests and tools](#checks-tests-and-tools)
- [Checks, tests and tools](#interacting-with-checks-tests-and-tools)
## I just found a bug / have a feature request

View File

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

View File

@ -0,0 +1,76 @@
# AD FS
This example does the following:
Terraform:
- (Optional) Creates a project.
- (Optional) Creates a VPC.
- Sets up managed AD
- Creates a server where AD FS will be installed. This machine will also act as admin workstation for AD.
- Exposes AD FS using GLB.
Ansible:
- Installs the required Windows features and joins the computer to the AD domain.
- Provisions some tests users, groups and group memberships in AD. The data to provision is in the ifles directory of the ad-provisioning ansible role. There is script available in the scripts/ad-provisioning folder that you can use to generate an alternative users or memberships file.
- Installs AD FS
In addition to this, we also include a Powershell script that facilitates the configuration required for Anthos when authenticating users with AD FS as IdP.
The diagram below depicts the architecture of the example:
![Architecture](architecture.png)
## Running the example
Clone this repository or [open it in cloud shell](https://ssh.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2Fterraform-google-modules%2Fcloud-foundation-fabric&cloudshell_print=cloud-shell-readme.txt&cloudshell_working_dir=examples%2Fcloud-operations%2Fadfs), then go through the following steps to create resources:
* `terraform init`
* `terraform apply -var project_id=my-project-id -var ad_dns_domain_name=my-domain.org -var adfs_dns_domain_name=adfs.my-domain.org`
Once the resources have been created, do the following:
1. Create an A record to point the AD FS DNS domain name to the public IP address returned after the terraform configuration was applied.
2. Run the ansible playbook
ansible-playbook playbook.yaml
# Testing the example
1. In your browser open the following URL:
https://adfs.my-domain.org/adfs/ls/IdpInitiatedSignOn.aspx
2. Enter the username and password of one of the users provisioned. The username has to be in the format: username@my-domain.org
3. Verify that you have successfuly signed in.
Once done testing, you can clean up resources by running `terraform destroy`.
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [ad_dns_domain_name](variables.tf#L44) | AD DNS domain name. | <code>string</code> | ✓ | |
| [adfs_dns_domain_name](variables.tf#L49) | ADFS DNS domain name. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L24) | Host project ID. | <code>string</code> | ✓ | |
| [ad_ip_cidr_block](variables.tf#L90) | Managed AD IP CIDR block. | <code>string</code> | | <code>&#34;10.0.0.0&#47;24&#34;</code> |
| [disk_size](variables.tf#L54) | Disk size. | <code>number</code> | | <code>50</code> |
| [disk_type](variables.tf#L60) | Disk type. | <code>string</code> | | <code>&#34;pd-ssd&#34;</code> |
| [image](variables.tf#L66) | Image. | <code>string</code> | | <code>&#34;projects&#47;windows-cloud&#47;global&#47;images&#47;family&#47;windows-2022&#34;</code> |
| [instance_type](variables.tf#L72) | Instance type. | <code>string</code> | | <code>&#34;n1-standard-2&#34;</code> |
| [network_config](variables.tf#L35) | Network configuration | <code title="object&#40;&#123;&#10; network &#61; string&#10; subnet &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [prefix](variables.tf#L29) | Prefix for the resources created. | <code>string</code> | | <code>null</code> |
| [project_create](variables.tf#L15) | Parameters for the creation of the new project. | <code title="object&#40;&#123;&#10; billing_account_id &#61; string&#10; parent &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [region](variables.tf#L78) | Region. | <code>string</code> | | <code>&#34;europe-west1&#34;</code> |
| [subnet_ip_cidr_block](variables.tf#L96) | Subnet IP CIDR block. | <code>string</code> | | <code>&#34;10.0.1.0&#47;28&#34;</code> |
| [zone](variables.tf#L84) | Zone. | <code>string</code> | | <code>&#34;europe-west1-c&#34;</code> |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [ip_address](outputs.tf#L15) | IP address. | |
<!-- END TFDOC -->

View File

@ -0,0 +1,8 @@
[defaults]
inventory = inventory/hosts.ini
[ssh_connection]
pipelining = True
ssh_executable = ./gssh.sh
transfer_method = piped

View File

@ -0,0 +1 @@
adfs ansible_connection=ssh ansible_shell_type=powershell

View File

@ -0,0 +1,53 @@
# 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.
- name: Prepare
hosts: adfs
gather_facts: yes
vars_files:
- vars/vars.yaml
roles:
- role: server-setup
- name: Provision organizational units users, groups and memberships
hosts: adfs
gather_facts: no
vars_files:
- vars/vars.yaml
vars:
ansible_become: yes
ansible_become_method: runas
ansible_become_user: "SetupAdmin@{{ ad_dns_domain_name }}"
ansible_become_password: "{{ setupadmin_password }}"
roles:
- role: ad-provisioning
- name: Install AD FS
hosts: adfs
gather_facts: no
vars_files:
- vars/vars.yaml
vars:
ansible_become: yes
ansible_become_method: runas
adfssvc_password: "{{ lookup('ansible.builtin.password', '~/.adfssvc-password.txt chars=ascii_letters,digits') }}"
roles:
- role: adfs-prerequisites
vars:
ansible_become_user: "SetupAdmin@{{ ad_dns_domain_name }}"
ansible_become_password: "{{ setupadmin_password }}"
- role: adfs-installation
vars:
ansible_become_user: "adfssvc@{{ ad_dns_domain_name }}"
ansible_become_password: "{{ adfssvc_password }}"

View File

@ -0,0 +1,8 @@
[
"gcp-billing-admins",
"gcp-devops",
"gcp-network-admins",
"gcp-organization-admins",
"gcp-security-admins",
"gcp-support"
]

View File

@ -0,0 +1,82 @@
[
{
"group": "gcp-devops",
"member": "pamela.reed"
},
{
"group": "gcp-devops",
"member": "joshua.banks"
},
{
"group": "gcp-devops",
"member": "clayton.espinoza"
},
{
"group": "gcp-devops",
"member": "maureen.morgan"
},
{
"group": "gcp-network-admins",
"member": "pamela.reed"
},
{
"group": "gcp-network-admins",
"member": "william.bowen"
},
{
"group": "gcp-network-admins",
"member": "clayton.espinoza"
},
{
"group": "gcp-network-admins",
"member": "stacy.holland"
},
{
"group": "gcp-network-admins",
"member": "joshua.banks"
},
{
"group": "gcp-network-admins",
"member": "charlene.mckenzie"
},
{
"group": "gcp-network-admins",
"member": "lisa.harris"
},
{
"group": "gcp-organization-admins",
"member": "maureen.morgan"
},
{
"group": "gcp-organization-admins",
"member": "pamela.reed"
},
{
"group": "gcp-support",
"member": "maureen.morgan"
},
{
"group": "gcp-support",
"member": "pamela.reed"
},
{
"group": "gcp-support",
"member": "lisa.harris"
},
{
"group": "gcp-support",
"member": "tina.ferguson"
},
{
"group": "gcp-support",
"member": "stacy.holland"
},
{
"group": "gcp-support",
"member": "william.bowen"
},
{
"group": "gcp-support",
"member": "clayton.espinoza"
}
]

View File

@ -0,0 +1,56 @@
[
{
"first_name": "Pamela",
"last_name": "Reed",
"username": "pamela.reed",
"password": "Ig_17BbZVu"
},
{
"first_name": "Charlene",
"last_name": "Mckenzie",
"username": "charlene.mckenzie",
"password": "$y0IsMLPy5"
},
{
"first_name": "William",
"last_name": "Bowen",
"username": "william.bowen",
"password": "y882QxMHE@"
},
{
"first_name": "Joshua",
"last_name": "Banks",
"username": "joshua.banks",
"password": ")00+LN!r0$"
},
{
"first_name": "Clayton",
"last_name": "Espinoza",
"username": "clayton.espinoza",
"password": "gIf@52FqUY"
},
{
"first_name": "Stacy",
"last_name": "Holland",
"username": "stacy.holland",
"password": "da4PLSQDb^"
},
{
"first_name": "Maureen",
"last_name": "Morgan",
"username": "maureen.morgan",
"password": "V)c2Vfc%i#"
},
{
"first_name": "Lisa",
"last_name": "Harris",
"username": "lisa.harris",
"password": "0@1Oid71co"
},
{
"first_name": "Tina",
"last_name": "Ferguson",
"username": "tina.ferguson",
"password": "+f#0C#_oi6"
}
]

View File

@ -0,0 +1,58 @@
# 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.
- name: Read files
set_fact:
ad_users: "{{ lookup('file','users.json') | from_json }}"
ad_groups: "{{ lookup('file','groups.json') | from_json }}"
ad_memberships: "{{ lookup('file','memberships.json') | from_json }}"
- name: Create organizational units
community.windows.win_domain_ou:
name: "{{ item }}"
path: "{{ cloud_path }}"
state: present
protected: true
with_items:
- "Users"
- "Groups"
- name: Create users
community.windows.win_domain_user:
name: "{{ item.username }}"
firstname: "{{ item.first_name }}"
surname: "{{ item.last_name }}"
email: "{{ item.username }}@{{ ad_dns_domain_name }}"
sam_account_name: "{{ item.username }}"
upn: "{{ item.username }}@{{ ad_dns_domain_name }}"
password: "{{ item.password }}"
path: "OU=Users,{{ cloud_path }}"
state: present
with_items: "{{ ad_users }}"
- name: Create groups
community.windows.win_domain_group:
name: "{{ item }}"
path: "OU=Groups,{{ cloud_path }}"
scope: global
state: present
with_items: "{{ ad_groups }}"
- name: Create memberships
community.windows.win_domain_group_membership:
name: "{{ item.group }}"
members:
- "{{ item.member }}"
state: present
with_items: "{{ ad_memberships }}"

View File

@ -0,0 +1,104 @@
# 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.
- name: Create server certificate
ansible.windows.win_powershell:
script: |
$Certificate = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Subject -eq "CN={{ adfs_dns_domain_name }}"}
if(-not $Certificate) {
$Certificate = New-SelfSignedCertificate `
-Subject {{ adfs_dns_domain_name }} `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-KeyExportPolicy NonExportable `
-KeyUsage DigitalSignature, KeyEncipherment `
-Provider 'Microsoft Platform Crypto Provider' `
-NotAfter (Get-Date).AddDays(365) `
-Type SSLServerAuthentication `
-CertStoreLocation 'Cert:\LocalMachine\My' `
-DnsName {{ adfs_dns_domain_name }}
}
$Certificate.Thumbprint
register: server_cert
- name: Create token signing certificate
ansible.windows.win_powershell:
script: |
$Certificate = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Subject -eq "CN=ADFS Signing"}
if(-not $Certificate) {
$Certificate = New-SelfSignedCertificate `
-Subject "ADFS Signing" `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-KeyExportPolicy NonExportable `
-KeyUsage DigitalSignature, KeyEncipherment `
-Provider 'Microsoft RSA SChannel Cryptographic Provider' `
-NotAfter (Get-Date).AddDays(365) `
-DnsName {{ adfs_dns_domain_name }} `
-CertStoreLocation 'Cert:\LocalMachine\My'
}
$Certificate.Thumbprint
register: token_signing_cert
- name: Create AD FS DKM container
ansible.windows.win_powershell:
script: |
$DkmContainer = Get-ADObject -LDAPFilter "(Objectclass=container)" -SearchBase "CN=ADFS Data,{{ cloud_path }}" -SearchScope 1
if(-not $DkmContainer) {
$DkmContainer.DistinguishedName
$Name = (New-Guid).Guid
$DkmContainer = New-ADObject `
-Name $Name `
-Type Container `
-Path "CN=ADFS Data,{{ cloud_path }}" `
-PassThru
}
$DkmContainer.DistinguishedName
register: adfs_dkm_container
- name: Install ADFS
ansible.windows.win_powershell:
script: |
try {
$AdfsFarm = Get-AdfsFarmInformation
} catch [System.ServiceModel.EndpointNotFoundException] {
$AdfsCredential = New-Object `
-TypeName System.Management.Automation.PSCredential `
-ArgumentList "$env:userdomain\adfssvc", (ConvertTo-SecureString {{ adfssvc_password }} -AsPlainText -Force)
Install-ADFSFarm `
-CertificateThumbprint {{ server_cert.output[0] }} `
-SigningCertificateThumbprint {{ token_signing_cert.output[0] }} `
-DecryptionCertificateThumbprint {{ token_signing_cert.output[0] }}`
-FederationServiceName {{ adfs_dns_domain_name }} `
-ServiceAccountCredential $AdfsCredential `
-OverwriteConfiguration `
-AdminConfiguration @{"DKMContainerDn"="{{ adfs_dkm_container.output[0] }}"}
}
no_log: yes
- name: Configure TLS
ansible.windows.win_powershell:
script: |
netsh http show sslcert ipport=0.0.0.0:443
if($LastExitCode -gt 0) {
netsh http add sslcert ipport=0.0.0.0:443 certhash={{ server_cert.output[0] }} appid="{5d89a20c-beab-4389-9447-324788eb944a}" certstorename=MY
}
- name: Restart computer
ansible.windows.win_reboot:
- name: Enable the Idp-Initiated Sign on page
ansible.windows.win_powershell:
script: |
Set-AdfsProperties -EnableIdpInitiatedSignonPage $true

View File

@ -0,0 +1,45 @@
# 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.
- name: Create AD FS service user
community.windows.win_domain_user:
name: "adfssvc"
password: "{{ adfssvc_password }}"
spn: "http/{{ adfs_dns_domain_name }}"
path: "OU=Users,{{ cloud_path }}"
state: present
- name: Add AD FS service user to local Administrators group
ansible.windows.win_group_membership:
name: Administrators
members:
- "adfssvc@{{ ad_dns_domain_name }}"
state: present
- name: Create AD FS Data container
ansible.windows.win_powershell:
script: |
try {
Get-ADObject -Identity "CN=ADFS Data,{{ cloud_path }}"
} catch [Microsoft.ActiveDirectory.Management.ADIdentityResolutionException] {
New-ADObject `
-Name "ADFS Data" `
-Type Container `
-Path "{{ cloud_path }}"
}
- name: Grant the AD FS user full control on the container
ansible.windows.win_powershell:
script: |
dsacls.exe "CN=ADFS Data,{{ cloud_path }}" /G $env:userdomain\adfssvc:GA /I:T

View File

@ -0,0 +1,67 @@
# 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.
$ApplicationGroup = Get-AdfsApplicationGroup -Name Anthos
$ApplicationGroupName = "Anthos"
$ApplicationGroupIdentifier = (New-Guid).Guid
New-AdfsApplicationGroup -Name $ApplicationGroupName `
-ApplicationGroupIdentifier $ApplicationGroupIdentifier
$ServerApplicationName = "$ApplicationGroupName Server App"
$ServerApplicationIdentifier = (New-Guid).Guid
$RelyingPartyTrustName = "Anthos"
$RelyingPartyTrustIdentifier = (New-Guid).Guid
$RedirectURI1 = "http://localhost:1025/callback"
$RedirectURI2 = "https://console.cloud.google.com/kubernetes/oidc"
$ADFSApp = Add-AdfsServerApplication -Name $ServerApplicationName `
-ApplicationGroupIdentifier $ApplicationGroupIdentifier `
-RedirectUri $RedirectURI1,$RedirectURI2 `
-Identifier $ServerApplicationIdentifier `
-GenerateClientSecret
$IssuanceTransformRules = @'
@RuleTemplate = "LdapClaims"
@RuleName = "groups"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/claims/Group"), query = ";tokenGroups(domainQualifiedName);{0}", param = c.Value);
'@
Add-AdfsRelyingPartyTrust -Name $RelyingPartyTrustName `
-Identifier $RelyingPartyTrustIdentifier `
-AccessControlPolicyName "Permit everyone" `
-IssuanceTransformRules "$IssuanceTransformRules"
Grant-ADFSApplicationPermission -ClientRoleIdentifier $ServerApplicationIdentifier `
-ServerRoleIdentifier $RelyingPartyTrustIdentifier `
-ScopeName "allatclaims", "openid"
$ClientId = $ADFSApp.Identifier
$ClientSecret = $ADFSApp.ClientSecret
@"
authentication:
oidc:
clientID: $ADFSApp.Identifier
clientSecret: $ADFSApp.ClientSecret
extraParams: resource=$RelyingPartyTrustIdentifier
group: groups
groupPrefix: ""
issuerURI: https://{{ adfs_dns_domain_name }}/adfs
kubectlRedirectURL: $RedirectURI1
scopes: openid
username: upn
usernamePrefix: ""
"@

View File

@ -0,0 +1,86 @@
# 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.
- name: Install Windows features
ansible.windows.win_feature:
name: "{{ item.feature }}"
include_mamangement_tools: "{{ item.include_management_tools }}"
state: present
with_items:
- { "feature": "RSAT-AD-Tools", "include_management_tools": false }
- { "feature": "GPMC", "include_management_tools": false }
- { "feature": "RSAT-DNS-Server", "include_management_tools": false }
- { "feature": "ADFS-Federation", "include_management_tools": true }
- { "feature": "RSAT-AD-PowerShell", "include_management_tools": false }
- { "feature": "RSAT-ADDS-Tools", "include_management_tools": false }
- name: Check if SetupAdmin password has already been reset
stat:
path: ~/.setupadmin-password.txt
register: setupadmin_password_file_check
delegate_to: localhost
- name: Set AD SetupAdmin password fact
set_fact:
setupadmin_password: "{{ lookup('file', '~/.setupadmin-password.txt') }}"
no_log: true
when: setupadmin_password_file_check.stat.exists
delegate_to: localhost
- name: Reset AD deletegated admin password
shell: >
gcloud active-directory domains reset-admin-password {{ ad_dns_domain_name }}
--project={{ project_id }}
--quiet
--format "value(password)"
register: setupadmin_password_reset
no_log: yes
when: not setupadmin_password_file_check.stat.exists
delegate_to: localhost
- name: Set AD SetupAdmin password fact
set_fact:
setupadmin_password: "{{ setupadmin_password_reset.stdout }}"
no_log: yes
when: not setupadmin_password_file_check.stat.exists
- name: Creating a file setupadmin password
copy:
dest: ~/.setupadmin-password.txt
content: "{{ setupadmin_password }}"
when: not setupadmin_password_file_check.stat.exists
delegate_to: localhost
- name: Add computer to domain
ansible.windows.win_domain_membership:
dns_domain_name: "{{ ad_dns_domain_name }}"
domain_admin_user: "SetupAdmin@{{ ad_dns_domain_name }}"
domain_admin_password: "{{ setupadmin_password }}"
state: domain
register: domain_state
- name: Restart computer
ansible.windows.win_reboot:
when: domain_state.reboot_required
- name: Get Domain info
community.windows.win_domain_object_info:
filter: ObjectClass -eq 'domain'
domain_username: "SetupAdmin@{{ ad_dns_domain_name }}"
domain_password: "{{ setupadmin_password }}"
register: ad_domain
- name: Set facts
set_fact:
cloud_path: "OU=Cloud,{{ ad_domain.objects[0].DistinguishedName }}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,191 @@
# 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.
locals {
prefix = (var.prefix == null || var.prefix == "") ? "" : "${var.prefix}-"
}
module "project" {
source = "../../../modules/project"
billing_account = (
var.project_create != null
? var.project_create.billing_account_id
: null
)
parent = (
var.project_create != null
? var.project_create.parent
: null
)
prefix = var.project_create == null ? null : var.prefix
name = var.project_id
services = [
"compute.googleapis.com",
"dns.googleapis.com",
"managedidentities.googleapis.com"
]
}
module "vpc" {
count = var.network_config == null ? 1 : 0
source = "../../../modules/net-vpc"
project_id = module.project.project_id
name = "${local.prefix}vpc"
subnets = [
{
ip_cidr_range = var.subnet_ip_cidr_block
name = "subnet"
region = var.region
secondary_ip_range = null
}
]
}
resource "google_active_directory_domain" "ad_domain" {
project = module.project.project_id
domain_name = var.ad_dns_domain_name
locations = [var.region]
authorized_networks = [module.vpc[0].network.id]
reserved_ip_range = var.ad_ip_cidr_block
}
module "server" {
source = "../../../modules/compute-vm"
project_id = module.project.project_id
zone = var.zone
name = "adfs"
instance_type = var.instance_type
network_interfaces = [{
network = var.network_config == null ? module.vpc[0].self_link : var.network_config.network
subnetwork = var.network_config == null ? module.vpc[0].subnet_self_links["${var.region}/subnet"] : var.network_config.subnet
nat = false
addresses = null
}]
metadata = {
# Enables OpenSSH in the Windows instance
sysprep-specialize-script-cmd = "googet -noconfirm=true update && googet -noconfirm=true install google-compute-engine-ssh"
enable-windows-ssh = "TRUE"
# Set the default OpenSSH shell to Powershell
windows-startup-script-ps1 = <<EOT
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" `
-Name DefaultShell `
-Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" `
-PropertyType String `
-Force
EOT
}
service_account_create = true
boot_disk = {
image = var.image
type = var.disk_type
size = var.disk_size
}
group = {
named_ports = {
http = 443
}
}
tags = ["https-server"]
}
module "glb" {
source = "../../../modules/net-glb"
name = "${local.prefix}glb"
project_id = module.project.project_id
https = true
reserve_ip_address = true
ssl_certificates_config = {
adfs-domain = {
domains = [
"${var.adfs_dns_domain_name}"
],
unmanaged_config = null
}
}
target_proxy_https_config = {
ssl_certificates = [
"adfs-domain"
]
}
backend_services_config = {
adfs-group-backend = {
bucket_config = null
enable_cdn = false
cdn_config = null
group_config = {
backends = [
{
group = module.server.group.id
options = null
}
],
health_checks = ["hc"]
log_config = {
enable = true
sample_rate = 1
}
options = {
affinity_cookie_ttl_sec = null
custom_request_headers = null
custom_response_headers = null
connection_draining_timeout_sec = null
load_balancing_scheme = null
locality_lb_policy = null
port_name = null
security_policy = null
session_affinity = null
timeout_sec = null
circuits_breakers = null
consistent_hash = null
iap = null
protocol = "HTTPS"
}
}
}
}
health_checks_config = {
hc = {
type = "tcp"
logging = true
options = null
check = {
port_name = "http"
port_specification = "USE_NAMED_PORT"
}
}
}
}
resource "local_file" "vars_file" {
content = templatefile("${path.module}/templates/vars.yaml.tpl", {
project_id = var.project_id
ad_dns_domain_name = var.ad_dns_domain_name
adfs_dns_domain_name = var.adfs_dns_domain_name
})
filename = "${path.module}/ansible/vars/vars.yaml"
file_permission = "0666"
}
resource "local_file" "gssh_file" {
content = templatefile("${path.module}/templates/gssh.sh.tpl", {
zone = var.zone
project_id = var.project_id
})
filename = "${path.module}/ansible/gssh.sh"
file_permission = "0777"
}

View File

@ -0,0 +1,18 @@
# 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.
output "ip_address" {
description = "IP address."
value = module.glb.ip_address
}

View File

@ -0,0 +1,98 @@
# 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.
from textwrap import indent
import click
import json
import random
from faker import Faker
ENCODING = 'UTF8'
FIELD_USER_FIRST_NAME = 'first_name'
FIELD_USER_LAST_NAME = 'last_name'
FIELD_USER_USERNAME = 'username'
FIELD_USER_PASSWORD = 'password'
FIELD_MEMBERSHIP_GROUP = 'group'
FIELD_MEMBERSHIP_MEMBER = 'member'
fake = Faker()
@ click.group()
def cli():
pass
@ cli.command()
@ click.option(
"--num-users",
help="Number of users to create",
default=10,
)
@click.option(
"--output-file",
help="Output file",
default="users.json",
)
def create_users(num_users, output_file):
rows = []
for i in range(1, num_users):
row = {}
row[FIELD_USER_FIRST_NAME] = fake.first_name()
row[FIELD_USER_LAST_NAME] = fake.last_name()
row[FIELD_USER_USERNAME] = row[FIELD_USER_FIRST_NAME].lower() + "." + \
row[FIELD_USER_LAST_NAME].lower()
row[FIELD_USER_PASSWORD] = fake.password()
rows.append(row)
write_json(output_file, rows)
@cli.command()
@click.option(
"--users-file",
help="Users file",
default="users.json",
)
@click.option(
"--groups-file",
help="Groups file",
default="groups.json",
)
@click.option(
"--output-file",
help="Output file",
default="memberships.json",
)
def create_memberships(users_file, groups_file, output_file):
users = read_json(users_file)
groups = read_json(groups_file)
rows = []
for group in groups:
members = random.sample(users, random.randint(0, len(users) - 1))
for member in members:
row = {}
row[FIELD_MEMBERSHIP_GROUP] = group
row[FIELD_MEMBERSHIP_MEMBER] = member[FIELD_USER_USERNAME]
rows.append(row)
write_json(output_file, rows)
def write_json(file, rows):
with open(file, 'w') as f:
json.dump(rows, f, indent=2)
def read_json(file):
with open(file, 'r', encoding='UTF8') as f:
return json.load(f)
if __name__ == "__main__":
cli()

View File

@ -0,0 +1,3 @@
argparse==1.4.0
Faker==13.3.2
click==8.0.4

View File

@ -0,0 +1,66 @@
# 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.
param($DnsName)
$ApplicationGroup = Get-AdfsApplicationGroup -Name Anthos
$ApplicationGroupName = "Anthos"
$ApplicationGroupIdentifier = (New-Guid).Guid
New-AdfsApplicationGroup -Name $ApplicationGroupName `
-ApplicationGroupIdentifier $ApplicationGroupIdentifier
$ServerApplicationName = "$ApplicationGroupName Server App"
$ServerApplicationIdentifier = (New-Guid).Guid
$RelyingPartyTrustName = "Anthos"
$RelyingPartyTrustIdentifier = (New-Guid).Guid
$RedirectURI1 = "http://localhost:1025/callback"
$RedirectURI2 = "https://console.cloud.google.com/kubernetes/oidc"
$ADFSApp = Add-AdfsServerApplication -Name $ServerApplicationName `
-ApplicationGroupIdentifier $ApplicationGroupIdentifier `
-RedirectUri $RedirectURI1,$RedirectURI2 `
-Identifier $ServerApplicationIdentifier `
-GenerateClientSecret
$IssuanceTransformRules = @'
@RuleTemplate = "LdapClaims"
@RuleName = "groups"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/claims/Group"), query = ";tokenGroups(domainQualifiedName);{0}", param = c.Value);
'@
Add-AdfsRelyingPartyTrust -Name $RelyingPartyTrustName `
-Identifier $RelyingPartyTrustIdentifier `
-AccessControlPolicyName "Permit everyone" `
-IssuanceTransformRules "$IssuanceTransformRules"
Grant-ADFSApplicationPermission -ClientRoleIdentifier $ServerApplicationIdentifier `
-ServerRoleIdentifier $RelyingPartyTrustIdentifier `
-ScopeName "allatclaims", "openid"
@"
authentication:
oidc:
clientID: $($ADFSApp.Identifier)
clientSecret: $($ADFSApp.ClientSecret)
extraParams: resource=$RelyingPartyTrustIdentifier
group: groups
groupPrefix: ""
issuerURI: https://$DnsName/adfs
kubectlRedirectURL: $RedirectURI1
scopes: openid
username: upn
usernamePrefix: ""
"@

View File

@ -0,0 +1,30 @@
#!/bin/bash
#
# 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.
host="$${@: -2: 1}"
cmd="$${@: -1: 1}"
gcloud_args="
--tunnel-through-iap
--zone=${zone}
--project=${project_id}
--quiet
--no-user-output-enabled
--
-C
"
exec gcloud compute ssh "$host" $gcloud_args "$cmd"

View File

@ -0,0 +1,17 @@
# 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.
project_id: ${project_id}
ad_dns_domain_name: ${ad_dns_domain_name}
adfs_dns_domain_name: ${adfs_dns_domain_name}

View File

@ -0,0 +1,100 @@
# 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.
variable "project_create" {
description = "Parameters for the creation of the new project."
type = object({
billing_account_id = string
parent = string
})
default = null
}
variable "project_id" {
description = "Host project ID."
type = string
}
variable "prefix" {
description = "Prefix for the resources created."
type = string
default = null
}
variable "network_config" {
description = "Network configuration"
type = object({
network = string
subnet = string
})
default = null
}
variable "ad_dns_domain_name" {
description = "AD DNS domain name."
type = string
}
variable "adfs_dns_domain_name" {
description = "ADFS DNS domain name."
type = string
}
variable "disk_size" {
description = "Disk size."
type = number
default = 50
}
variable "disk_type" {
description = "Disk type."
type = string
default = "pd-ssd"
}
variable "image" {
description = "Image."
type = string
default = "projects/windows-cloud/global/images/family/windows-2022"
}
variable "instance_type" {
description = "Instance type."
type = string
default = "n1-standard-2"
}
variable "region" {
description = "Region."
type = string
default = "europe-west1"
}
variable "zone" {
description = "Zone."
type = string
default = "europe-west1-c"
}
variable "ad_ip_cidr_block" {
description = "Managed AD IP CIDR block."
type = string
default = "10.0.0.0/24"
}
variable "subnet_ip_cidr_block" {
description = "Subnet IP CIDR block."
type = string
default = "10.0.1.0/28"
}

View File

@ -0,0 +1,32 @@
# 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.1.0"
required_providers {
local = {
version = ">= 2.2.3"
}
google = {
source = "hashicorp/google"
version = ">= 4.17.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.17.0"
}
}
}

View File

@ -33,3 +33,9 @@ This [example](./data-platform-foundations/) implements SQL Server Always On Ava
<a href="./cloudsql-multiregion/" title="Cloud SQL instance with multi-region read replicas"><img src="./cloudsql-multiregion/diagram.png" align="left" width="280px"></a>
This [example](./cloudsql-multiregion/) creates a [Cloud SQL instance](https://cloud.google.com/sql) with multi-region read replicas as described in the [Cloud SQL for PostgreSQL disaster recovery](https://cloud.google.com/architecture/cloud-sql-postgres-disaster-recovery-complete-failover-fallback) article.
<br clear="left">
### Data Playground starter with Cloud Vertex AI Notebook and GCS
<a href="./data-playground/" title="Data Playground starter with Cloud Vertex AI Notebook and GCS"><img src="./data-playground/diagram.png" align="left" width="280px"></a>
This [example](./data-playground/) creates a [Vertex AI Notebook](https://cloud.google.com/vertex-ai/docs/workbench/introduction) running under a VPC network and a starter GCS bucket to store inputs and outputs of data experiments.
<br clear="left">

View File

@ -0,0 +1,43 @@
# Data Playground
This example creates a minimum viable template for a data experimentation project with the needed APIs enabled, basic VPC and Firewall set in place, GCS bucket and an AI notebook to get started.
This is the high level diagram:
![High-level diagram](diagram.png "High-level diagram")
## Managed resources and services
This sample creates several distinct groups of resources:
- projects
- Service Project configured for GCE instances and GCS buckets
- networking
- VPC network
- One default subnet
- Firewall rules for [SSH access via IAP](https://cloud.google.com/iap/docs/using-tcp-forwarding) and open communication within the VPC
- Vertex AI notebook
- One Jupyter lab notebook instance with public access
- GCS
- One bucket initial bucket
## Variables
| name | description | type | required | default |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------- | ------------ |
| project\_id | Project id, references existing project if \`project\_create\` is null. | string | ✓ | |
| location | The location where resources will be deployed | string | | europe |
| region | The region where resources will be deployed. | string | | europe-west1 |
| project\_create | Provide values if project creation is needed, uses existing project if null. Parent format: folders/folder\_id or organizations/org\_id | object({…}) | | null |
| prefix | Unique prefix used for resource names. Not used for project if 'project\_create' is null. | string | | dp |
| service\_encryption\_keys | Cloud KMS to use to encrypt different services. Key location should match service region. | object({…}) | | null |
| vpc\_config | Parameters to create a simple VPC for the Data Playground | object({…}) | | {...} |
## Outputs
| Name | Description |
| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| bucket | GCS Bucket URL. |
| project | Project id |
| vpc | VPC Network name |
| notebook | Vertex AI notebook name |

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,113 @@
# 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.
###############################################################################
# Project #
###############################################################################
locals {
service_encryption_keys = var.service_encryption_keys
}
module "project" {
source = "../../../modules/project"
name = var.project_id
parent = try(var.project_create.parent, null)
billing_account = try(var.project_create.billing_account_id, null)
project_create = var.project_create != null
prefix = var.project_create == null ? null : var.prefix
services = [
"stackdriver.googleapis.com",
"compute.googleapis.com",
"storage-component.googleapis.com",
"storage.googleapis.com",
"servicenetworking.googleapis.com",
"bigquery.googleapis.com",
"bigquerystorage.googleapis.com",
"bigqueryreservation.googleapis.com",
"dataflow.googleapis.com",
"notebooks.googleapis.com",
"composer.googleapis.com"
]
policy_boolean = {
# "constraints/compute.requireOsLogin" = false
# Example of applying a project wide policy, mainly useful for Composer
}
service_encryption_key_ids = {
storage = [try(local.service_encryption_keys.storage, null)]
}
}
###############################################################################
# Networking #
###############################################################################
module "vpc" {
source = "../../../modules/net-vpc"
project_id = module.project.project_id
name = var.vpc_config.vpc_name
subnets = [
{
ip_cidr_range = var.vpc_config.ip_cidr_range
name = var.vpc_config.subnet_name
region = var.region
secondary_ip_range = {}
}
]
}
module "vpc-firewall" {
source = "../../../modules/net-vpc-firewall"
project_id = module.project.project_id
network = module.vpc.name
admin_ranges = [var.vpc_config.ip_cidr_range]
}
###############################################################################
# GCS #
###############################################################################
module "base-gcs-bucket" {
source = "../../../modules/gcs"
project_id = module.project.project_id
prefix = module.project.project_id
name = "base"
encryption_key = try(local.service_encryption_keys.storage, null) # Example assignment of an encryption key
}
###############################################################################
# Vertex AI Notebook #
###############################################################################
# TODO: Add encryption_key to Vertex AI notebooks as well
# TODO: Add shared VPC support
resource "google_notebooks_instance" "playground" {
name = "data-play-notebook"
location = format("%s-%s", var.region, "b")
machine_type = "e2-medium"
project = module.project.project_id
container_image {
repository = "gcr.io/deeplearning-platform-release/base-cpu"
tag = "latest"
}
install_gpu_driver = true
boot_disk_type = "PD_SSD"
boot_disk_size_gb = 110
no_public_ip = false
no_proxy_access = false
network = module.vpc.network.id
subnet = module.vpc.subnets[format("%s/%s", var.region, var.vpc_config.subnet_name)].id
}

View File

@ -0,0 +1,33 @@
# 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.
output "bucket" {
description = "GCS Bucket URL."
value = module.base-gcs-bucket.url
}
output "notebook" {
description = "Vertex AI notebook"
value = resource.google_notebooks_instance.playground.name
}
output "project" {
description = "Project id"
value = module.project.project_id
}
output "vpc" {
description = "VPC Network"
value = module.vpc.name
}

View File

@ -0,0 +1,68 @@
# 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.
variable "location" {
description = "The location where resources will be deployed."
type = string
default = "europe"
}
variable "project_id" {
description = "Project id, references existing project if `project_create` is null."
type = string
}
variable "project_create" {
description = "Provide values if project creation is needed, uses existing project if null. Parent format: folders/folder_id or organizations/org_id"
type = object({
billing_account_id = string
parent = string
})
default = null
}
variable "prefix" {
description = "Unique prefix used for resource names. Not used for project if 'project_create' is null."
type = string
default = "dp"
}
variable "region" {
description = "The region where resources will be deployed."
type = string
default = "europe-west1"
}
variable "service_encryption_keys" { # service encription key
description = "Cloud KMS to use to encrypt different services. Key location should match service region."
type = object({
storage = string
})
default = null
}
variable "vpc_config" {
description = "Parameters to create a simple VPC for the Data Playground"
type = object({
ip_cidr_range = string
subnet_name = string
vpc_name = string
})
default = {
ip_cidr_range = "10.0.0.0/20"
subnet_name = "default-subnet"
vpc_name = "data-playground-vpc"
}
}

View File

@ -0,0 +1,27 @@
# 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.1.0"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.17.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.17.0"
}
}
}

View File

@ -12,7 +12,7 @@ Nested folder structure for yaml configurations is optionally supported, which a
```hcl
module "prod-firewall" {
source = "./modules/net-vpc-firewall-yaml"
source = "./examples/factories/net-vpc-firewall-yaml"
project_id = "my-prod-project"
network = "my-prod-network"
@ -27,7 +27,7 @@ module "prod-firewall" {
}
module "dev-firewall" {
source = "./modules/net-vpc-firewall-yaml"
source = "./examples/factories/net-vpc-firewall-yaml"
project_id = "my-dev-project"
network = "my-dev-network"

View File

@ -7,12 +7,13 @@ Legend: <code>+</code> additive, <code>•</code> conditional.
| members | roles |
|---|---|
|<b>GCP organization domain</b><br><small><i>domain</i></small>|[roles/browser](https://cloud.google.com/iam/docs/understanding-roles#browser) <br>[roles/resourcemanager.organizationViewer](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.organizationViewer) |
|<b>gcp-billing-admins</b><br><small><i>group</i></small>|[roles/billing.admin](https://cloud.google.com/iam/docs/understanding-roles#billing.admin) <code>+</code><br>[roles/billing.costsManager](https://cloud.google.com/iam/docs/understanding-roles#billing.costsManager) <code>+</code>|
|<b>gcp-network-admins</b><br><small><i>group</i></small>|[roles/cloudasset.owner](https://cloud.google.com/iam/docs/understanding-roles#cloudasset.owner) <br>[roles/cloudsupport.techSupportEditor](https://cloud.google.com/iam/docs/understanding-roles#cloudsupport.techSupportEditor) <br>[roles/compute.orgFirewallPolicyAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.orgFirewallPolicyAdmin) <code>+</code><br>[roles/compute.xpnAdmin](https://cloud.google.com/iam/docs/understanding-roles#compute.xpnAdmin) <code>+</code>|
|<b>gcp-organization-admins</b><br><small><i>group</i></small>|[roles/cloudasset.owner](https://cloud.google.com/iam/docs/understanding-roles#cloudasset.owner) <br>[roles/cloudsupport.admin](https://cloud.google.com/iam/docs/understanding-roles#cloudsupport.admin) <br>[roles/compute.osAdminLogin](https://cloud.google.com/iam/docs/understanding-roles#compute.osAdminLogin) <br>[roles/compute.osLoginExternalUser](https://cloud.google.com/iam/docs/understanding-roles#compute.osLoginExternalUser) <br>[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner) <br>[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin) <br>[roles/resourcemanager.organizationAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.organizationAdmin) <br>[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) <br>[roles/billing.admin](https://cloud.google.com/iam/docs/understanding-roles#billing.admin) <code>+</code><br>[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) <code>+</code>|
|<b>gcp-organization-admins</b><br><small><i>group</i></small>|[roles/cloudasset.owner](https://cloud.google.com/iam/docs/understanding-roles#cloudasset.owner) <br>[roles/cloudsupport.admin](https://cloud.google.com/iam/docs/understanding-roles#cloudsupport.admin) <br>[roles/compute.osAdminLogin](https://cloud.google.com/iam/docs/understanding-roles#compute.osAdminLogin) <br>[roles/compute.osLoginExternalUser](https://cloud.google.com/iam/docs/understanding-roles#compute.osLoginExternalUser) <br>[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner) <br>[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin) <br>[roles/resourcemanager.organizationAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.organizationAdmin) <br>[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) <br>[roles/billing.admin](https://cloud.google.com/iam/docs/understanding-roles#billing.admin) <code>+</code><br>[roles/billing.costsManager](https://cloud.google.com/iam/docs/understanding-roles#billing.costsManager) <code>+</code><br>[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) <code>+</code>|
|<b>gcp-security-admins</b><br><small><i>group</i></small>|[roles/cloudasset.owner](https://cloud.google.com/iam/docs/understanding-roles#cloudasset.owner) <br>[roles/cloudsupport.techSupportEditor](https://cloud.google.com/iam/docs/understanding-roles#cloudsupport.techSupportEditor) <br>[roles/iam.securityReviewer](https://cloud.google.com/iam/docs/understanding-roles#iam.securityReviewer) <br>[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin) <br>[roles/securitycenter.admin](https://cloud.google.com/iam/docs/understanding-roles#securitycenter.admin) <br>[roles/accesscontextmanager.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#accesscontextmanager.policyAdmin) <code>+</code><br>[roles/iam.organizationRoleAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.organizationRoleAdmin) <code>+</code><br>[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) <code>+</code>|
|<b>gcp-support</b><br><small><i>group</i></small>|[roles/cloudsupport.techSupportEditor](https://cloud.google.com/iam/docs/understanding-roles#cloudsupport.techSupportEditor) <br>[roles/logging.viewer](https://cloud.google.com/iam/docs/understanding-roles#logging.viewer) <br>[roles/monitoring.viewer](https://cloud.google.com/iam/docs/understanding-roles#monitoring.viewer) |
|<b>prod-bootstrap-0</b><br><small><i>serviceAccount</i></small>|[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin) <br>[roles/resourcemanager.organizationAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.organizationAdmin) <br>[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) <br>[roles/billing.admin](https://cloud.google.com/iam/docs/understanding-roles#billing.admin) <code>+</code><br>[roles/iam.organizationRoleAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.organizationRoleAdmin) <code>+</code>|
|<b>prod-resman-0</b><br><small><i>serviceAccount</i></small>|organizations/[org_id #0]/roles/organizationIamAdmin <code></code><br>[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin) <br>[roles/resourcemanager.tagAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.tagAdmin) <br>[roles/resourcemanager.tagUser](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.tagUser) <br>[roles/billing.admin](https://cloud.google.com/iam/docs/understanding-roles#billing.admin) <code>+</code><br>[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) <code>+</code>|
|<b>prod-bootstrap-0</b><br><small><i>serviceAccount</i></small>|[roles/logging.admin](https://cloud.google.com/iam/docs/understanding-roles#logging.admin) <br>[roles/resourcemanager.organizationAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.organizationAdmin) <br>[roles/resourcemanager.projectCreator](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.projectCreator) <br>[roles/billing.admin](https://cloud.google.com/iam/docs/understanding-roles#billing.admin) <code>+</code><br>[roles/billing.costsManager](https://cloud.google.com/iam/docs/understanding-roles#billing.costsManager) <code>+</code><br>[roles/iam.organizationRoleAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.organizationRoleAdmin) <code>+</code>|
|<b>prod-resman-0</b><br><small><i>serviceAccount</i></small>|organizations/[org_id #0]/roles/organizationIamAdmin <code></code><br>[roles/resourcemanager.folderAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.folderAdmin) <br>[roles/resourcemanager.tagAdmin](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.tagAdmin) <br>[roles/resourcemanager.tagUser](https://cloud.google.com/iam/docs/understanding-roles#resourcemanager.tagUser) <br>[roles/billing.admin](https://cloud.google.com/iam/docs/understanding-roles#billing.admin) <code>+</code><br>[roles/billing.costsManager](https://cloud.google.com/iam/docs/understanding-roles#billing.costsManager) <code>+</code><br>[roles/orgpolicy.policyAdmin](https://cloud.google.com/iam/docs/understanding-roles#orgpolicy.policyAdmin) <code>+</code>|
## Project <i>prod-audit-logs-0</i>
@ -31,7 +32,9 @@ Legend: <code>+</code> additive, <code>•</code> conditional.
| members | roles |
|---|---|
|<b>gcp-devops</b><br><small><i>group</i></small>|[roles/iam.serviceAccountAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountAdmin) <br>[roles/iam.serviceAccountTokenCreator](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountTokenCreator) |
|<b>gcp-organization-admins</b><br><small><i>group</i></small>|[roles/iam.serviceAccountTokenCreator](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountTokenCreator) |
|<b>gcp-organization-admins</b><br><small><i>group</i></small>|[roles/iam.serviceAccountTokenCreator](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountTokenCreator) <br>[roles/iam.workloadIdentityPoolAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.workloadIdentityPoolAdmin) |
|<b>SERVICE_IDENTITY_service-networking</b><br><small><i>serviceAccount</i></small>|[roles/servicenetworking.serviceAgent](https://cloud.google.com/iam/docs/understanding-roles#servicenetworking.serviceAgent) <code>+</code>|
|<b>prod-bootstrap-0</b><br><small><i>serviceAccount</i></small>|[roles/owner](https://cloud.google.com/iam/docs/understanding-roles#owner) |
|<b>prod-resman-0</b><br><small><i>serviceAccount</i></small>|[roles/iam.serviceAccountAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountAdmin) <br>[roles/iam.workloadIdentityPoolAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.workloadIdentityPoolAdmin) <br>[roles/storage.admin](https://cloud.google.com/iam/docs/understanding-roles#storage.admin) |
|<b>prod-bootstrap-1</b><br><small><i>serviceAccount</i></small>|[roles/logging.logWriter](https://cloud.google.com/iam/docs/understanding-roles#logging.logWriter) <code>+</code>|
|<b>prod-resman-0</b><br><small><i>serviceAccount</i></small>|[roles/cloudbuild.builds.editor](https://cloud.google.com/iam/docs/understanding-roles#cloudbuild.builds.editor) <br>[roles/iam.serviceAccountAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.serviceAccountAdmin) <br>[roles/iam.workloadIdentityPoolAdmin](https://cloud.google.com/iam/docs/understanding-roles#iam.workloadIdentityPoolAdmin) <br>[roles/source.admin](https://cloud.google.com/iam/docs/understanding-roles#source.admin) <br>[roles/storage.admin](https://cloud.google.com/iam/docs/understanding-roles#storage.admin) |
|<b>prod-resman-1</b><br><small><i>serviceAccount</i></small>|[roles/logging.logWriter](https://cloud.google.com/iam/docs/understanding-roles#logging.logWriter) <code>+</code>|

View File

@ -19,6 +19,7 @@
locals {
# used here for convenience, in organization.tf members are explicit
billing_ext_admins = [
local.groups_iam.gcp-billing-admins,
local.groups_iam.gcp-organization-admins,
module.automation-tf-bootstrap-sa.iam_email,
module.automation-tf-resman-sa.iam_email
@ -103,3 +104,12 @@ resource "google_billing_account_iam_member" "billing_ext_admin" {
role = "roles/billing.admin"
member = each.key
}
resource "google_billing_account_iam_member" "billing_ext_cost_manager" {
for_each = toset(
local.billing_ext ? local.billing_ext_admins : []
)
billing_account_id = var.billing_account.id
role = "roles/billing.costsManager"
member = each.key
}

View File

@ -76,6 +76,13 @@ locals {
},
local.billing_org ? {
"roles/billing.admin" = [
local.groups_iam.gcp-billing-admins,
local.groups_iam.gcp-organization-admins,
module.automation-tf-bootstrap-sa.iam_email,
module.automation-tf-resman-sa.iam_email
],
"roles/billing.costsManager" = [
local.groups_iam.gcp-billing-admins,
local.groups_iam.gcp-organization-admins,
module.automation-tf-bootstrap-sa.iam_email,
module.automation-tf-resman-sa.iam_email

View File

@ -42,7 +42,8 @@ module "billing-organization-ext" {
count = local.billing_org_ext ? 1 : 0
organization_id = "organizations/${var.billing_account.organization_id}"
iam_additive = {
"roles/billing.user" = local.billing_ext_users
"roles/billing.user" = local.billing_ext_users
"roles/billing.costsManager" = local.billing_ext_users
}
}
@ -56,3 +57,12 @@ resource "google_billing_account_iam_member" "billing_ext_admin" {
role = "roles/billing.user"
member = each.key
}
resource "google_billing_account_iam_member" "billing_ext_costsmanager" {
for_each = toset(
local.billing_ext ? local.billing_ext_users : []
)
billing_account_id = var.billing_account.id
role = "roles/billing.costsManager"
member = each.key
}

View File

@ -122,10 +122,12 @@ module "branch-pf-dev-sa-cicd" {
each.value.branch == null
? format(
local.identity_providers[each.value.identity_provider].principalset_tpl,
var.automation.federated_identity_pool,
each.value.name
)
: format(
local.identity_providers[each.value.identity_provider].principal_tpl,
var.automation.federated_identity_pool,
each.value.name,
each.value.branch
)

View File

@ -38,10 +38,12 @@ module "landing-project" {
service_projects = []
}
iam = {
"roles/dns.admin" = [local.service_accounts.project-factory-prod]
(local.custom_roles.service_project_network_admin) = [
local.service_accounts.project-factory-prod
]
"roles/dns.admin" = compact([
try(local.service_accounts.project-factory-prod, null)
])
(local.custom_roles.service_project_network_admin) = compact([
try(local.service_accounts.project-factory-prod, null)
])
}
}

View File

@ -25,7 +25,8 @@ locals {
})]
}
service_accounts = {
for k, v in coalesce(var.service_accounts, {}) : k => "serviceAccount:${v}"
for k, v in coalesce(var.service_accounts, {}) :
k => "serviceAccount:${v}" if v != null
}
stage3_sas_delegated_grants = [
"roles/composer.sharedVpcAgent",

View File

@ -40,7 +40,9 @@ module "dev-spoke-project" {
}
metric_scopes = [module.landing-project.project_id]
iam = {
"roles/dns.admin" = compact([local.service_accounts.project-factory-dev])
"roles/dns.admin" = compact([
try(local.service_accounts.project-factory-dev, null)
])
}
}
@ -124,9 +126,9 @@ resource "google_project_iam_binding" "dev_spoke_project_iam_delegated" {
project = module.dev-spoke-project.project_id
role = "roles/resourcemanager.projectIamAdmin"
members = compact([
local.service_accounts.data-platform-dev,
local.service_accounts.project-factory-dev,
local.service_accounts.gke-dev,
try(local.service_accounts.data-platform-dev, null),
try(local.service_accounts.project-factory-dev, null),
try(local.service_accounts.gke-dev, null),
])
condition {
title = "dev_stage3_sa_delegated_grants"

View File

@ -40,7 +40,9 @@ module "prod-spoke-project" {
}
metric_scopes = [module.landing-project.project_id]
iam = {
"roles/dns.admin" = compact([local.service_accounts.project-factory-prod])
"roles/dns.admin" = compact([
try(local.service_accounts.project-factory-prod, null)
])
}
}
@ -124,9 +126,9 @@ resource "google_project_iam_binding" "prod_spoke_project_iam_delegated" {
project = module.prod-spoke-project.project_id
role = "roles/resourcemanager.projectIamAdmin"
members = compact([
local.service_accounts.data-platform-prod,
local.service_accounts.project-factory-prod,
local.service_accounts.gke-prod,
try(local.service_accounts.data-platform-prod, null),
try(local.service_accounts.project-factory-prod, null),
try(local.service_accounts.gke-prod, null),
])
condition {
title = "prod_stage3_sa_delegated_grants"

View File

@ -38,10 +38,12 @@ module "landing-project" {
service_projects = []
}
iam = {
"roles/dns.admin" = [local.service_accounts.project-factory-prod]
(local.custom_roles.service_project_network_admin) = [
local.service_accounts.project-factory-prod
]
"roles/dns.admin" = compact([
try(local.service_accounts.project-factory-prod, null)
])
(local.custom_roles.service_project_network_admin) = compact([
try(local.service_accounts.project-factory-prod, null)
])
}
}

View File

@ -36,7 +36,8 @@ locals {
"roles/vpcaccess.user",
]
service_accounts = {
for k, v in coalesce(var.service_accounts, {}) : k => "serviceAccount:${v}"
for k, v in coalesce(var.service_accounts, {}) :
k => "serviceAccount:${v}" if v != null
}
}

View File

@ -41,7 +41,9 @@ module "dev-spoke-project" {
}
metric_scopes = [module.landing-project.project_id]
iam = {
"roles/dns.admin" = compact([local.service_accounts.project-factory-dev])
"roles/dns.admin" = compact([
try(local.service_accounts.project-factory-dev, null)
])
}
}
@ -101,9 +103,9 @@ resource "google_project_iam_binding" "dev_spoke_project_iam_delegated" {
project = module.dev-spoke-project.project_id
role = "roles/resourcemanager.projectIamAdmin"
members = compact([
local.service_accounts.data-platform-dev,
local.service_accounts.project-factory-dev,
local.service_accounts.gke-dev,
try(local.service_accounts.data-platform-dev, null),
try(local.service_accounts.project-factory-dev, null),
try(local.service_accounts.gke-dev, null),
])
condition {
title = "dev_stage3_sa_delegated_grants"

View File

@ -41,7 +41,9 @@ module "prod-spoke-project" {
}
metric_scopes = [module.landing-project.project_id]
iam = {
"roles/dns.admin" = compact([local.service_accounts.project-factory-prod])
"roles/dns.admin" = compact([
try(local.service_accounts.project-factory-prod, null)
])
}
}
@ -101,9 +103,9 @@ resource "google_project_iam_binding" "prod_spoke_project_iam_delegated" {
project = module.prod-spoke-project.project_id
role = "roles/resourcemanager.projectIamAdmin"
members = compact([
local.service_accounts.data-platform-prod,
local.service_accounts.project-factory-prod,
local.service_accounts.gke-prod,
try(local.service_accounts.data-platform-prod, null),
try(local.service_accounts.project-factory-prod, null),
try(local.service_accounts.gke-prod, null),
])
condition {
title = "prod_stage3_sa_delegated_grants"

View File

@ -38,10 +38,12 @@ module "landing-project" {
service_projects = []
}
iam = {
"roles/dns.admin" = [local.service_accounts.project-factory-prod]
(local.custom_roles.service_project_network_admin) = [
local.service_accounts.project-factory-prod
]
"roles/dns.admin" = compact([
try(local.service_accounts.project-factory-prod, null)
])
(local.custom_roles.service_project_network_admin) = compact([
try(local.service_accounts.project-factory-prod, null)
])
}
}

View File

@ -36,7 +36,8 @@ locals {
"roles/vpcaccess.user",
]
service_accounts = {
for k, v in coalesce(var.service_accounts, {}) : k => "serviceAccount:${v}"
for k, v in coalesce(var.service_accounts, {}) :
k => "serviceAccount:${v}" if v != null
}
}

View File

@ -41,7 +41,9 @@ module "dev-spoke-project" {
}
metric_scopes = [module.landing-project.project_id]
iam = {
"roles/dns.admin" = compact([local.service_accounts.project-factory-dev])
"roles/dns.admin" = compact([
try(local.service_accounts.project-factory-dev, null)
])
}
}
@ -101,9 +103,9 @@ resource "google_project_iam_binding" "dev_spoke_project_iam_delegated" {
project = module.dev-spoke-project.project_id
role = "roles/resourcemanager.projectIamAdmin"
members = compact([
local.service_accounts.data-platform-dev,
local.service_accounts.project-factory-dev,
local.service_accounts.gke-dev,
try(local.service_accounts.data-platform-dev, null),
try(local.service_accounts.project-factory-dev, null),
try(local.service_accounts.gke-dev, null)
])
condition {
title = "dev_stage3_sa_delegated_grants"

View File

@ -41,7 +41,9 @@ module "prod-spoke-project" {
}
metric_scopes = [module.landing-project.project_id]
iam = {
"roles/dns.admin" = compact([local.service_accounts.project-factory-prod])
"roles/dns.admin" = compact([
try(local.service_accounts.project-factory-prod, null)
])
}
}
@ -101,9 +103,9 @@ resource "google_project_iam_binding" "prod_spoke_project_iam_delegated" {
project = module.prod-spoke-project.project_id
role = "roles/resourcemanager.projectIamAdmin"
members = compact([
local.service_accounts.data-platform-prod,
local.service_accounts.project-factory-prod,
local.service_accounts.gke-prod,
try(local.service_accounts.data-platform-prod, null),
try(local.service_accounts.project-factory-prod, null),
tru(local.service_accounts.gke-prod, null),
])
condition {
title = "prod_stage3_sa_delegated_grants"

View File

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

View File

@ -15,6 +15,21 @@
*/
locals {
annotations = merge(
var.ingress_settings == null ? {} : {
"run.googleapis.com/ingress" = var.ingress_settings
},
var.vpc_connector == null ? {} : {
"run.googleapis.com/vpc-access-connector" = (
try(var.vpc_connector.create, false)
? google_vpc_access_connector.connector.0.id
: var.vpc_connector.name
)
},
try(var.vpc_connector.egress_settings, null) == null ? {} : {
"run.googleapis.com/vpc-access-egress" = var.vpc_connector.egress_settings
}
)
prefix = var.prefix == null ? "" : "${var.prefix}-"
service_account_email = (
var.service_account_create
@ -25,21 +40,10 @@ locals {
)
: var.service_account
)
annotations = merge(var.ingress_settings == null ? {} : { "run.googleapis.com/ingress" = var.ingress_settings },
var.vpc_connector == null
? {}
: try(var.vpc_connector.create, false)
? { "run.googleapis.com/vpc-access-connector" = var.vpc_connector.name }
: { "run.googleapis.com/vpc-access-connector" = google_vpc_access_connector.connector.0.id }
,
try(var.vpc_connector.egress_settings, null) == null
? {}
: { "run.googleapis.com/vpc-access-egress" = var.vpc_connector.egress_settings })
}
resource "google_vpc_access_connector" "connector" {
count = try(var.vpc_connector.create, false) == false ? 0 : 1
count = try(var.vpc_connector.create, false) ? 1 : 0
project = var.project_id
name = var.vpc_connector.name
region = var.region
@ -56,20 +60,30 @@ resource "google_cloud_run_service" "service" {
template {
spec {
dynamic "containers" {
for_each = var.containers == null ? {} : { for i, container in var.containers : i => container }
for_each = var.containers == null ? {} : {
for i, container in var.containers : i => container
}
content {
image = containers.value["image"]
command = try(containers.value["options"]["command"], null)
args = try(containers.value["options"]["args"], null)
dynamic "env" {
for_each = try(containers.value["options"]["env"], null) == null ? {} : containers.value["options"]["env"]
for_each = (
try(containers.value["options"]["env"], null) == null
? {}
: containers.value["options"]["env"]
)
content {
name = env.key
value = env.value
}
}
dynamic "env" {
for_each = try(containers.value["options"]["env_from"], null) == null ? {} : containers.value["options"]["env_from"]
for_each = (
try(containers.value["options"]["env_from"], null) == null
? {}
: containers.value["options"]["env_from"]
)
content {
name = env.key
value_from {
@ -81,7 +95,14 @@ resource "google_cloud_run_service" "service" {
}
}
dynamic "ports" {
for_each = containers.value["ports"] == null ? {} : { for port in containers.value["ports"] : "${port.name}-${port.container_port}" => port }
for_each = (
containers.value["ports"] == null
? {}
: {
for port in containers.value["ports"] :
"${port.name}-${port.container_port}" => port
}
)
content {
name = ports.value["name"]
protocol = ports.value["protocol"]
@ -96,7 +117,11 @@ resource "google_cloud_run_service" "service" {
}
}
dynamic "volume_mounts" {
for_each = containers.value["volume_mounts"] == null ? {} : containers.value["volume_mounts"]
for_each = (
containers.value["volume_mounts"] == null
? {}
: containers.value["volume_mounts"]
)
content {
name = volume_mounts.key
mount_path = volume_mounts.value
@ -112,7 +137,11 @@ resource "google_cloud_run_service" "service" {
secret {
secret_name = volumes.value["secret_name"]
dynamic "items" {
for_each = volumes.value["items"] == null ? [] : volumes.value["items"]
for_each = (
volumes.value["items"] == null
? []
: volumes.value["items"]
)
content {
key = items.value["key"]
path = items.value["path"]
@ -130,7 +159,6 @@ resource "google_cloud_run_service" "service" {
}
}
metadata {
annotations = local.annotations
}
@ -162,7 +190,10 @@ resource "google_service_account" "service_account" {
}
resource "google_eventarc_trigger" "audit_log_triggers" {
for_each = var.audit_log_triggers == null ? {} : { for trigger in var.audit_log_triggers : "${trigger.service_name}-${trigger.method_name}" => trigger }
for_each = var.audit_log_triggers == null ? {} : {
for trigger in var.audit_log_triggers :
"${trigger.service_name}-${trigger.method_name}" => trigger
}
name = "${local.prefix}${each.key}-audit-log-trigger"
location = google_cloud_run_service.service.location
project = google_cloud_run_service.service.project
@ -188,7 +219,11 @@ resource "google_eventarc_trigger" "audit_log_triggers" {
resource "google_eventarc_trigger" "pubsub_triggers" {
for_each = var.pubsub_triggers == null ? [] : toset(var.pubsub_triggers)
name = each.value == "" ? "${local.prefix}default-pubsub-trigger" : "${local.prefix}${each.value}-pubsub-trigger"
name = (
each.value == ""
? "${local.prefix}default-pubsub-trigger"
: "${local.prefix}${each.value}-pubsub-trigger"
)
location = google_cloud_run_service.service.location
project = google_cloud_run_service.service.project
matching_criteria {

View File

@ -531,11 +531,12 @@ Target proxies leverage [url-maps](url-map.tf): set of L7 rules, which create a
| name | description | sensitive |
|---|---|:---:|
| [backend_services](outputs.tf#L22) | Backend service resources. | |
| [global_forwarding_rule](outputs.tf#L57) | The global forwarding rule. | |
| [global_forwarding_rule](outputs.tf#L62) | The global forwarding rule. | |
| [health_checks](outputs.tf#L17) | Health-check resources. | |
| [ip_address](outputs.tf#L44) | The reserved global IP address. | |
| [ip_address_self_link](outputs.tf#L49) | The URI of the reserved global IP address. | |
| [ssl_certificates](outputs.tf#L35) | The SSL certificate. | |
| [target_proxy](outputs.tf#L49) | The target proxy. | |
| [target_proxy](outputs.tf#L54) | The target proxy. | |
| [url_map](outputs.tf#L30) | The url-map. | |
<!-- END TFDOC -->

View File

@ -43,6 +43,11 @@ output "ssl_certificates" {
output "ip_address" {
description = "The reserved global IP address."
value = try(google_compute_global_address.static_ip[0].address, null)
}
output "ip_address_self_link" {
description = "The URI of the reserved global IP address."
value = google_compute_global_forwarding_rule.forwarding_rule.ip_address
}

View File

@ -0,0 +1,166 @@
# 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 = "./modules/organization-policy"
config_directory = "./policies"
}
# using configuration provided in the module variable
module "org-policy" {
source = "./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

@ -0,0 +1,19 @@
# 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 {
# TODO: Remove once Terraform 1.3 is released https://github.com/hashicorp/terraform/releases/tag/v1.3.0-alpha20220622
experiments = [module_variable_optional_attrs]
}

View File

@ -0,0 +1,102 @@
/**
* 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

@ -0,0 +1,20 @@
/**
* 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

@ -0,0 +1,45 @@
/**
* 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

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

View File

@ -0,0 +1,23 @@
/**
* 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 "test" {
source = "../../../../../examples/cloud-operations/adfs"
project_create = var.project_create
project_id = var.project_id
ad_dns_domain_name = var.ad_dns_domain_name
adfs_dns_domain_name = var.adfs_dns_domain_name
}

View File

@ -0,0 +1,103 @@
# 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.
# 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.
variable "project_create" {
type = object({
billing_account_id = string
parent = string
})
default = null
}
variable "project_id" {
type = string
default = "my-project"
}
variable "prefix" {
type = string
default = null
}
variable "network_config" {
type = object({
network = string
subnet = string
})
default = null
}
variable "ad_dns_domain_name" {
type = string
default = "example.com"
}
variable "adfs_dns_domain_name" {
type = string
default = "adfs.example.com"
}
variable "disk_size" {
type = number
default = 50
}
variable "disk_type" {
type = string
default = "pd-ssd"
}
variable "image" {
type = string
default = "projects/windows-cloud/global/images/family/windows-2022"
}
variable "instance_type" {
type = string
default = "n1-standard-2"
}
variable "region" {
type = string
default = "europe-west1"
}
variable "zone" {
type = string
default = "europe-west1-c"
}
variable "ad_ip_cidr_block" {
type = string
default = "10.0.0.0/24"
}
variable "subnet_ip_cidr_block" {
type = string
default = "10.0.1.0/28"
}

View File

@ -0,0 +1,19 @@
# 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_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner()
assert len(modules) == 4
assert len(resources) == 16

View File

@ -0,0 +1,13 @@
# 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

@ -0,0 +1,24 @@
/**
* 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 "test" {
source = "../../../../../examples/data-solutions/data-playground/"
project_id = "sampleproject"
project_create = {
billing_account_id = "123456-123456-123456",
parent = "folders/467898377"
}
}

View File

@ -0,0 +1,26 @@
# 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.
import os
import pytest
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner(FIXTURES_DIR)
assert len(modules) == 4
assert len(resources) == 23

View File

@ -12,6 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
variable "vpc_connector" {
type = any
default = null
}
variable "vpc_connector_config" {
type = any
default = null
}
module "cloud_run" {
source = "../../../../modules/cloud-run"
project_id = "my-project"
@ -37,4 +47,6 @@ module "cloud_run" {
iam = {
"roles/run.invoker" = ["allUsers"]
}
vpc_connector = var.vpc_connector
vpc_connector_config = var.vpc_connector_config
}

View File

@ -28,21 +28,54 @@ def test_resource_count(resources):
def test_iam(resources):
"Test IAM binding resources."
bindings = [r['values'] for r in resources if r['type']
== 'google_cloud_run_service_iam_binding']
bindings = [
r['values']
for r in resources
if r['type'] == 'google_cloud_run_service_iam_binding'
]
assert len(bindings) == 1
assert bindings[0]['role'] == 'roles/run.invoker'
def test_audit_log_triggers(resources):
"Test audit logs Eventarc trigger resources."
audit_log_triggers = [r['values'] for r in resources if r['type']
== 'google_eventarc_trigger' and r['name'] == 'audit_log_triggers']
audit_log_triggers = [
r['values']
for r in resources
if r['type'] == 'google_eventarc_trigger' and
r['name'] == 'audit_log_triggers'
]
assert len(audit_log_triggers) == 1
def test_pubsub_triggers(resources):
"Test Pub/Sub Eventarc trigger resources."
pubsub_triggers = [r['values'] for r in resources if r['type']
== 'google_eventarc_trigger' and r['name'] == 'pubsub_triggers']
pubsub_triggers = [
r['values'] for r in resources if
r['type'] == 'google_eventarc_trigger' and r['name'] == 'pubsub_triggers'
]
assert len(pubsub_triggers) == 2
def test_vpc_connector_none(plan_runner):
"Test VPC connector creation."
_, resources = plan_runner()
assert len(
[r for r in resources if r['type'] == 'google_vpc_access_connector']) == 0
def test_vpc_connector_nocreate(plan_runner):
"Test VPC connector creation."
_, resources = plan_runner(
vpc_connector='{create=false, name="foo", egress_settings=null}')
assert len(
[r for r in resources if r['type'] == 'google_vpc_access_connector']) == 0
def test_vpc_connector_create(plan_runner):
"Test VPC connector creation."
_, resources = plan_runner(
vpc_connector='{create=true, name="foo", egress_settings=null}',
vpc_connector_config='{ip_cidr_range="10.0.0.0/28", network="default"}')
assert len(
[r for r in resources if r['type'] == 'google_vpc_access_connector']) == 1

View File

@ -0,0 +1,13 @@
# 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

@ -0,0 +1,18 @@
# 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 {
# TODO: Remove once Terraform 1.3 is released https://github.com/hashicorp/terraform/releases/tag/v1.3.0-alpha20220622
experiments = [module_variable_optional_attrs]
}

View File

@ -0,0 +1,22 @@
/**
* 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

@ -0,0 +1,40 @@
# 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

@ -0,0 +1,46 @@
/**
* 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

@ -0,0 +1,89 @@
# 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