AD FS example

This commit is contained in:
Miren Esnaola 2022-06-13 02:55:57 +02:00
parent f42b0f06e4
commit 68e56058ab
27 changed files with 1359 additions and 0 deletions

2
.gitignore vendored
View File

@ -32,3 +32,5 @@ 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

@ -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

@ -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,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