Skip to content

Commit 269c1ca

Browse files
bharathkkbmorgante
andauthored
chore: Add Safer Cluster with IAP bastion host access pattern example (#544)
* add safer cluster access pattern * Apply suggestions from code review address comments Co-authored-by: Morgante Pell <[email protected]> * remove redundant var * add e2e test * fix e2e * fix sandbox-enabled tests Co-authored-by: Morgante Pell <[email protected]>
1 parent 3ba3559 commit 269c1ca

File tree

18 files changed

+621
-2
lines changed

18 files changed

+621
-2
lines changed

.kitchen.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,10 @@ suites:
221221
systems:
222222
- name: sandbox_enabled
223223
backend: local
224+
- name: "safer_cluster_iap_bastion"
225+
driver:
226+
root_module_directory: test/fixtures/safer_cluster_iap_bastion
227+
verifier:
228+
systems:
229+
- name: safer_cluster_iap_bastion
230+
backend: local

build/int.cloudbuild.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,26 @@ steps:
384384
- verify workload-identity-local
385385
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
386386
args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do destroy workload-identity-local']
387+
- id: create safer-cluster-iap-bastion-local
388+
waitFor:
389+
- prepare
390+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
391+
args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do create safer-cluster-iap-bastion-local']
392+
- id: converge safer-cluster-iap-bastion-local
393+
waitFor:
394+
- create safer-cluster-iap-bastion-local
395+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
396+
args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do converge safer-cluster-iap-bastion-local']
397+
- id: verify safer-cluster-iap-bastion-local
398+
waitFor:
399+
- converge safer-cluster-iap-bastion-local
400+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
401+
args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do verify safer-cluster-iap-bastion-local']
402+
- id: destroy safer-cluster-iap-bastion-local
403+
waitFor:
404+
- verify safer-cluster-iap-bastion-local
405+
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
406+
args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do destroy safer-cluster-iap-bastion-local']
387407
tags:
388408
- 'ci'
389409
- 'integration'
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Safer Cluster Access with IAP Bastion Host
2+
3+
This end to end example aims to showcase access patterns to a [Safer Cluster](../../modules/safer-cluster/README.md), which is a hardened GKE Private Cluster, through a bastion host utilizing [Identity Awareness Proxy](https://cloud.google.com/iap/) without an external ip address. Access to this cluster's control plane is restricted to the bastion host's internal IP using [authorized networks](https://cloud.google.com/kubernetes-engine/docs/how-to/authorized-networks#overview).
4+
5+
Additionally we deploy a [tinyproxy](https://tinyproxy.github.io/) daemon which allows `kubectl` commands to be piped through the bastion host allowing ease of development from a local machine with the security of GKE Private Clusters.
6+
7+
## Setup
8+
9+
To deploy this example:
10+
11+
1. Run `terraform init`.
12+
13+
2. Create a `terraform.tfvars` to provide values for `project_id`, `bastion_members`. Optionally override any variables if necessary.
14+
15+
3. Run `terraform apply`.
16+
17+
4. After apply is complete, generate kubeconfig for the private cluster. _The command with the right parameters will displayed as the Terraform output `get_credentials_command`._
18+
19+
```sh
20+
gcloud container clusters get-credentials --project $PROJECT_ID --zone $ZONE --internal-ip $CLUSTER_NAME
21+
```
22+
23+
5. SSH to the Bastion Host while port forwarding to the bastion host through an IAP tunnel. _The command with the right parameters will displayed by running `terraform output bastion_ssh_command`._
24+
25+
```sh
26+
gcloud beta compute ssh $BASTION_VM_NAME --tunnel-through-iap --project $PROJECT_ID --zone $ZONE -- -L8888:127.0.0.1:8888
27+
```
28+
29+
6. You can now run `kubectl` commands though the proxy. _An example command will displayed as the Terraform output `bastion_kubectl_command`._
30+
31+
```sh
32+
HTTPS_PROXY=localhost:8888 kubectl get pods --all-namespaces
33+
```
34+
35+
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
36+
## Inputs
37+
38+
| Name | Description | Type | Default | Required |
39+
|------|-------------|:----:|:-----:|:-----:|
40+
| bastion\_members | List of users, groups, SAs who need access to the bastion host | list(string) | `<list>` | no |
41+
| cluster\_name | The name of the cluster | string | `"safer-cluster-iap-bastion"` | no |
42+
| ip\_range\_pods\_name | The secondary ip range to use for pods | string | `"ip-range-pods"` | no |
43+
| ip\_range\_services\_name | The secondary ip range to use for pods | string | `"ip-range-svc"` | no |
44+
| ip\_source\_ranges\_ssh | Additional source ranges to allow for ssh to bastion host. 35.235.240.0/20 allowed by default for IAP tunnel. | list(string) | `<list>` | no |
45+
| network\_name | The name of the network being created to host the cluster in | string | `"safer-cluster-network"` | no |
46+
| project\_id | The project ID to host the cluster in | string | n/a | yes |
47+
| region | The region to host the cluster in | string | `"us-central1"` | no |
48+
| subnet\_ip | The cidr range of the subnet | string | `"10.10.10.0/24"` | no |
49+
| subnet\_name | The name of the subnet being created to host the cluster in | string | `"safer-cluster-subnet"` | no |
50+
51+
## Outputs
52+
53+
| Name | Description |
54+
|------|-------------|
55+
| bastion\_kubectl\_command | kubectl command using the local proxy once the bastion_ssh command is running |
56+
| bastion\_name | Name of the bastion host |
57+
| bastion\_ssh\_command | gcloud command to ssh and port forward to the bastion host command |
58+
| bastion\_zone | Location of bastion host |
59+
| ca\_certificate | Cluster ca certificate (base64 encoded) |
60+
| cluster\_name | Cluster name |
61+
| endpoint | Cluster endpoint |
62+
| get\_credentials\_command | gcloud get-credentials command to generate kubeconfig for the private cluster |
63+
| location | Cluster location (region if regional cluster, zone if zonal cluster) |
64+
| master\_authorized\_networks\_config | Networks from which access to master is permitted |
65+
| network\_name | The name of the VPC being created |
66+
| region | Subnet/Router/Bastion Host region |
67+
| router\_name | Name of the router that was created |
68+
| subnet\_name | The name of the VPC subnet being created |
69+
70+
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
module "enabled_google_apis" {
18+
source = "terraform-google-modules/project-factory/google//modules/project_services"
19+
version = "~> 8.0"
20+
21+
project_id = var.project_id
22+
disable_services_on_destroy = false
23+
24+
activate_apis = [
25+
"iam.googleapis.com",
26+
"compute.googleapis.com",
27+
"logging.googleapis.com",
28+
"monitoring.googleapis.com",
29+
"containerregistry.googleapis.com",
30+
"container.googleapis.com",
31+
"binaryauthorization.googleapis.com",
32+
"stackdriver.googleapis.com",
33+
"iap.googleapis.com",
34+
]
35+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
locals {
18+
bastion_name = format("%s-bastion", var.cluster_name)
19+
bastion_zone = format("%s-a", var.region)
20+
}
21+
22+
data "template_file" "startup_script" {
23+
template = <<-EOF
24+
sudo apt-get update -y
25+
sudo apt-get install -y tinyproxy
26+
EOF
27+
}
28+
29+
module "bastion" {
30+
source = "terraform-google-modules/bastion-host/google"
31+
version = "~> 2.0"
32+
network = module.vpc.network_self_link
33+
subnet = module.vpc.subnets_self_links[0]
34+
project = module.enabled_google_apis.project_id
35+
host_project = module.enabled_google_apis.project_id
36+
name = local.bastion_name
37+
zone = local.bastion_zone
38+
image_project = "debian-cloud"
39+
image_family = "debian-9"
40+
machine_type = "g1-small"
41+
startup_script = data.template_file.startup_script.rendered
42+
members = var.bastion_members
43+
shielded_vm = "false"
44+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
module "gke" {
18+
source = "terraform-google-modules/kubernetes-engine/google//modules/safer-cluster"
19+
version = "~> 9.0"
20+
project_id = module.enabled_google_apis.project_id
21+
name = var.cluster_name
22+
region = var.region
23+
network = module.vpc.network_name
24+
subnetwork = module.vpc.subnets_names[0]
25+
ip_range_pods = module.vpc.subnets_secondary_ranges[0].*.range_name[0]
26+
ip_range_services = module.vpc.subnets_secondary_ranges[0].*.range_name[1]
27+
enable_private_endpoint = false
28+
master_authorized_networks = [{
29+
cidr_block = "${module.bastion.ip_address}/32"
30+
display_name = "Bastion Host"
31+
}]
32+
grant_registry_access = true
33+
node_pools = [
34+
{
35+
name = "safer-pool"
36+
min_count = 1
37+
max_count = 4
38+
machine_type = "n1-standard-2"
39+
auto_upgrade = true
40+
node_metadata = "GKE_METADATA_SERVER"
41+
}
42+
]
43+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
18+
module "vpc" {
19+
source = "terraform-google-modules/network/google"
20+
version = "~> 2.3"
21+
22+
project_id = module.enabled_google_apis.project_id
23+
network_name = var.network_name
24+
routing_mode = "GLOBAL"
25+
26+
subnets = [
27+
{
28+
subnet_name = var.subnet_name
29+
subnet_ip = var.subnet_ip
30+
subnet_region = var.region
31+
subnet_private_access = true
32+
description = "This subnet is managed by Terraform"
33+
}
34+
]
35+
secondary_ranges = {
36+
"${var.subnet_name}" = [
37+
{
38+
range_name = var.ip_range_pods_name
39+
ip_cidr_range = "192.168.0.0/18"
40+
},
41+
{
42+
range_name = var.ip_range_services_name
43+
ip_cidr_range = "192.168.64.0/18"
44+
},
45+
]
46+
}
47+
}
48+
49+
50+
module "cloud-nat" {
51+
source = "terraform-google-modules/cloud-nat/google"
52+
version = "~> 1.2"
53+
project_id = module.enabled_google_apis.project_id
54+
region = var.region
55+
router = "safer-router"
56+
network = module.vpc.network_self_link
57+
create_router = true
58+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Copyright 2018 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
output "cluster_name" {
18+
description = "Cluster name"
19+
value = module.gke.name
20+
}
21+
22+
output "location" {
23+
description = "Cluster location (region if regional cluster, zone if zonal cluster)"
24+
value = module.gke.location
25+
}
26+
27+
output "region" {
28+
description = "Subnet/Router/Bastion Host region"
29+
value = var.region
30+
}
31+
32+
output "endpoint" {
33+
sensitive = true
34+
description = "Cluster endpoint"
35+
value = module.gke.endpoint
36+
}
37+
38+
output "master_authorized_networks_config" {
39+
description = "Networks from which access to master is permitted"
40+
value = module.gke.master_authorized_networks_config
41+
}
42+
43+
output "router_name" {
44+
description = "Name of the router that was created"
45+
value = module.cloud-nat.router_name
46+
}
47+
48+
output "ca_certificate" {
49+
sensitive = true
50+
description = "Cluster ca certificate (base64 encoded)"
51+
value = module.gke.ca_certificate
52+
}
53+
54+
output "network_name" {
55+
value = module.vpc.network_name
56+
description = "The name of the VPC being created"
57+
}
58+
59+
output "subnet_name" {
60+
value = module.vpc.subnets_names[0]
61+
description = "The name of the VPC subnet being created"
62+
}
63+
64+
output "get_credentials_command" {
65+
description = "gcloud get-credentials command to generate kubeconfig for the private cluster"
66+
value = format("gcloud container clusters get-credentials --project %s --zone %s --internal-ip %s", var.project_id, module.gke.location, module.gke.name)
67+
}
68+
69+
output "bastion_name" {
70+
description = "Name of the bastion host"
71+
value = module.bastion.hostname
72+
}
73+
74+
output "bastion_zone" {
75+
description = "Location of bastion host"
76+
value = local.bastion_zone
77+
}
78+
79+
output "bastion_ssh_command" {
80+
description = "gcloud command to ssh and port forward to the bastion host command"
81+
value = format("gcloud beta compute ssh %s --tunnel-through-iap --project %s --zone %s -- -L8888:127.0.0.1:8888", module.bastion.hostname, var.project_id, local.bastion_zone)
82+
}
83+
84+
output "bastion_kubectl_command" {
85+
description = "kubectl command using the local proxy once the bastion_ssh command is running"
86+
value = "HTTPS_PROXY=localhost:8888 kubectl get pods --all-namespaces"
87+
}

0 commit comments

Comments
 (0)