diff --git a/infrastructure/shared-vpc-gke/README.md b/infrastructure/shared-vpc-gke/README.md new file mode 100644 index 0000000000..743c9b98b7 --- /dev/null +++ b/infrastructure/shared-vpc-gke/README.md @@ -0,0 +1,73 @@ +# Shared VPC sample + +This sample creates a basic [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) setup using one host project and two service projects, each with a specific subnet in the shared VPC. The setup also includes the specific IAM-level configurations needed for [GKE on Shared VPC](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-shared-vpc) to enable cluster creation in one of the two service projects. + +The sample has been purposefully kept simple so that it can be used as a basis for different Shared VPC configurations. 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 + - host project + - service project configured for GKE clusters + - service project configured for GCE instances +- networking + - the VPC network + - one subnet with secondary ranges for GKE clusters + - one subnet for GCE instances + - firewall rules for SSH access via IAP and open communication within the VPC + - Cloud NAT service +- IAM + - one service account for the bastion CGE instance + - one service account for the GKE nodes + - optional owner role bindings on each project + - optional [OS Login](https://cloud.google.com/compute/docs/oslogin/) role bindings on the GCE service project + - role bindings to allow the GCE instance and GKE nodes logging and monitoring write access + - role binding to allow the GCE instance cluster access +- DNS + - one private zone +- GCE + - one instance used to access the internal GKE cluster +- GKE + - one private cluster with one nodepool + +## Accessing the bastion instance and GKE cluster + +The bastion VM has no public address so access is mediated via [IAP](https://cloud.google.com/iap/docs), which is supported transparently in the `gcloud compute ssh` command. Authentication is via OS Login set as a project default. + +Cluster access from the bastion can leverage the instance service account's `container.developer` role: the only configuration needed is to fetch cluster credentials via `gcloud container clusters get-credentials` passing the correct cluster name, location and project via command options. + +## Destroying + +There's a minor glitch that can surface running `terraform destroy`, where the service project attachments to the Shared VPC will not get destroyed even with the relevant API call succeeding. We are investigating the issue, in the meantime just manually remove the attachment in the Cloud console or via the `gcloud` command when `terraform destroy` fails, and then relaunch the command. + + +## Variables + +| name | description | type | required | default | +|---|---|:---: |:---:|:---:| +| billing_account_id | Billing account id used as default for new projects. | string | ✓ | | +| prefix | Prefix used for resources that need unique names. | string | ✓ | | +| root_node | Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'. | string | ✓ | | +| *ip_ranges* | Subnet IP CIDR ranges. | map(string) | | ... | +| *ip_secondary_ranges* | Secondary IP CIDR ranges. | map(string) | | ... | +| *owners_gce* | GCE project owners, in IAM format. | list(string) | | [] | +| *owners_gke* | GKE project owners, in IAM format. | list(string) | | [] | +| *owners_host* | Host project owners, in IAM format. | list(string) | | [] | +| *private_service_ranges* | Private service IP CIDR ranges. | map(string) | | ... | +| *project_services* | Service APIs enabled by default in new projects. | list(string) | | ... | +| *region* | Region used. | string | | europe-west1 | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| gke_clusters | GKE clusters information. | | +| projects | Project ids. | | +| service_accounts | GCE and GKE service accounts. | | +| vms | GCE VMs. | | +| vpc | Shared VPC. | | + diff --git a/infrastructure/shared-vpc/backend.tf.sample b/infrastructure/shared-vpc-gke/backend.tf.sample similarity index 100% rename from infrastructure/shared-vpc/backend.tf.sample rename to infrastructure/shared-vpc-gke/backend.tf.sample diff --git a/infrastructure/shared-vpc/diagram.gcpdraw b/infrastructure/shared-vpc-gke/diagram.gcpdraw similarity index 72% rename from infrastructure/shared-vpc/diagram.gcpdraw rename to infrastructure/shared-vpc-gke/diagram.gcpdraw index 9ed42b4f65..ff8291e67e 100644 --- a/infrastructure/shared-vpc/diagram.gcpdraw +++ b/infrastructure/shared-vpc-gke/diagram.gcpdraw @@ -7,16 +7,17 @@ elements { group host_services { name "Shared Services" background_color "#f6f6f6" - card dns - card kms + card dns { + name "Private zone" + } + card nat { + name "NAT" + } } group vpc_host { name "Shared VPC" background_color "#fff3e0" - card vpc as net_subnet { - name "Networking subnet" - } card vpc as gce_subnet { name "GCE subnet" } @@ -28,15 +29,15 @@ elements { group project_gce { name "GCE service project" - stacked_card gce as gce_instances { - name "VM instances" + card gce as gce_instances { + name "Bastion VM" } } group project_gke { name "GKE service project" - stacked_card gke as gke_clusters { - name "GKE clusters" + card gke as gke_clusters { + name "GKE cluster" } } @@ -46,4 +47,4 @@ elements { paths { gce_subnet ..> gce_instances gke_subnet ..> gke_clusters -} \ No newline at end of file +} diff --git a/infrastructure/shared-vpc-gke/diagram.png b/infrastructure/shared-vpc-gke/diagram.png new file mode 100644 index 0000000000..8dc15e9b29 Binary files /dev/null and b/infrastructure/shared-vpc-gke/diagram.png differ diff --git a/infrastructure/shared-vpc-gke/main.tf b/infrastructure/shared-vpc-gke/main.tf new file mode 100644 index 0000000000..5fdb5aa13a --- /dev/null +++ b/infrastructure/shared-vpc-gke/main.tf @@ -0,0 +1,249 @@ +# Copyright 2019 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 and service projects # +############################################################################### + +# the container.hostServiceAgentUser role is needed for GKE on shared VPC + +module "project-host" { + source = "../../modules/project" + parent = var.root_node + billing_account = var.billing_account_id + prefix = var.prefix + name = "net" + services = concat(var.project_services, ["dns.googleapis.com"]) + iam_roles = [ + "roles/container.hostServiceAgentUser", "roles/owner" + ] + iam_members = { + "roles/container.hostServiceAgentUser" = [ + "serviceAccount:${module.project-svc-gke.gke_service_account}" + ] + "roles/owner" = var.owners_host + } +} + +module "project-svc-gce" { + source = "../../modules/project" + parent = var.root_node + billing_account = var.billing_account_id + prefix = var.prefix + name = "gce" + services = var.project_services + oslogin = true + oslogin_admins = var.owners_gce + iam_roles = [ + "roles/logging.logWriter", + "roles/monitoring.metricWriter", + "roles/owner" + ] + iam_members = { + "roles/logging.logWriter" = [module.vm-bastion.service_account_iam_email], + "roles/monitoring.metricWriter" = [module.vm-bastion.service_account_iam_email], + "roles/owner" = var.owners_gce, + + } +} + +# the container.developer role assigned to the bastion instance service account +# allows to fetch GKE credentials from bastion for clusters in this project + +module "project-svc-gke" { + source = "../../modules/project" + parent = var.root_node + billing_account = var.billing_account_id + prefix = var.prefix + name = "gke" + services = var.project_services + iam_roles = [ + "roles/container.developer", + "roles/owner", + ] + iam_members = { + "roles/owner" = var.owners_gke + "roles/container.developer" = [module.vm-bastion.service_account_iam_email] + } +} + +################################################################################ +# Networking # +################################################################################ + +# the service project GKE robot needs the `hostServiceAgent` role throughout +# the entire life of its clusters; the `iam_project_id` project output is used +# here to set the project id so that the VPC depends on that binding, and any +# cluster using it then also depends on it indirectly; you can of course use +# the `project_id` output instead if you don't care about destroying + +# subnet IAM bindings control which identities can use the individual subnets + +module "vpc-shared" { + source = "../../modules/net-vpc" + project_id = module.project-host.iam_project_id + name = "shared-vpc" + shared_vpc_host = true + shared_vpc_service_projects = [ + module.project-svc-gce.project_id, + module.project-svc-gke.project_id + ] + subnets = { + gce = { + ip_cidr_range = var.ip_ranges.gce + region = var.region + secondary_ip_range = {} + } + gke = { + ip_cidr_range = var.ip_ranges.gke + region = var.region + secondary_ip_range = { + pods = var.ip_secondary_ranges.gke-pods + services = var.ip_secondary_ranges.gke-services + } + } + } + iam_roles = { + gke = ["roles/compute.networkUser", "roles/compute.securityAdmin"] + gce = ["roles/compute.networkUser"] + } + iam_members = { + gce = { + "roles/compute.networkUser" = concat(var.owners_gce, [ + "serviceAccount:${module.project-svc-gce.cloudsvc_service_account}", + ]) + } + gke = { + "roles/compute.networkUser" = concat(var.owners_gke, [ + "serviceAccount:${module.project-svc-gke.cloudsvc_service_account}", + "serviceAccount:${module.project-svc-gke.gke_service_account}", + ]) + "roles/compute.securityAdmin" = [ + "serviceAccount:${module.project-svc-gke.gke_service_account}", + ] + } + } +} + +module "vpc-shared-firewall" { + source = "../../modules/net-vpc-firewall" + project_id = module.project-host.project_id + network = module.vpc-shared.name + admin_ranges_enabled = true + admin_ranges = values(var.ip_ranges) +} + +module "nat" { + source = "../../modules/net-cloudnat" + project_id = module.project-host.project_id + region = var.region + name = "vpc-shared" + router_create = true + router_network = module.vpc-shared.name +} + +################################################################################ +# DNS # +################################################################################ + +module "host-dns" { + source = "../../modules/dns" + project_id = module.project-host.project_id + type = "private" + name = "example" + domain = "example.com." + client_networks = [module.vpc-shared.self_link] + recordsets = [ + { name = "localhost", type = "A", ttl = 300, records = ["127.0.0.1"] }, + { name = "bastion", type = "A", ttl = 300, records = module.vm-bastion.internal_ips }, + ] +} + +################################################################################ +# VM # +################################################################################ + +module "vm-bastion" { + source = "../../modules/compute-vm" + project_id = module.project-svc-gce.project_id + region = module.vpc-shared.subnet_regions.gce + zone = "${module.vpc-shared.subnet_regions.gce}-b" + name = "bastion" + network_interfaces = [{ + network = module.vpc-shared.self_link, + subnetwork = lookup(module.vpc-shared.subnet_self_links, "gce", null), + nat = false, + addresses = null + }] + instance_count = 1 + metadata = { + startup-script = join("\n", [ + "#! /bin/bash", + "apt-get update", + "apt-get install -y bash-completion kubectl dnsutils" + ]) + } + service_account_create = true +} + +################################################################################ +# GKE # +################################################################################ + +module "cluster-1" { + source = "../../modules/gke-cluster" + name = "cluster-1" + project_id = module.project-svc-gke.project_id + location = "${module.vpc-shared.subnet_regions.gke}-b" + network = module.vpc-shared.self_link + subnetwork = module.vpc-shared.subnet_self_links.gke + secondary_range_pods = "pods" + secondary_range_services = "services" + default_max_pods_per_node = 32 + labels = { + environment = "test" + } + master_authorized_ranges = { + internal-vms = var.ip_ranges.gce + } + private_cluster_config = { + enable_private_nodes = true + enable_private_endpoint = true + master_ipv4_cidr_block = var.private_service_ranges.cluster-1 + } +} + +module "cluster-1-nodepool-1" { + source = "../../modules/gke-nodepool" + name = "nodepool-1" + project_id = module.project-svc-gke.project_id + location = module.cluster-1.location + cluster_name = module.cluster-1.name + node_config_service_account = module.service-account-gke-node.email +} + +# roles assigned via this module use non-authoritative IAM bindings at the +# project level, with no risk of conflicts with pre-existing roles + +module "service-account-gke-node" { + source = "../../modules/iam-service-accounts" + project_id = module.project-svc-gke.project_id + names = ["gke-node"] + iam_project_roles = { + (module.project-svc-gke.project_id) = [ + "roles/logging.logWriter", + "roles/monitoring.metricWriter", + ] + } +} diff --git a/infrastructure/shared-vpc-gke/outputs.tf b/infrastructure/shared-vpc-gke/outputs.tf new file mode 100644 index 0000000000..fccebe77ed --- /dev/null +++ b/infrastructure/shared-vpc-gke/outputs.tf @@ -0,0 +1,54 @@ +# Copyright 2019 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 "gke_clusters" { + description = "GKE clusters information." + value = { + cluster-1 = module.cluster-1.endpoint + } +} + +output "projects" { + description = "Project ids." + value = { + host = module.project-host.project_id + service-gce = module.project-svc-gce.project_id + service-gke = module.project-svc-gke.project_id + } +} + +output "service_accounts" { + description = "GCE and GKE service accounts." + value = { + bastion = module.vm-bastion.service_account_email + gke_node = module.service-account-gke-node.email + } +} + +output "vpc" { + description = "Shared VPC." + value = { + name = module.vpc-shared.name + subnets = module.vpc-shared.subnet_ips + } +} + +output "vms" { + description = "GCE VMs." + value = { + for instance in concat(module.vm-bastion.instances) : + instance.name => instance.network_interface.0.network_ip + } +} + diff --git a/infrastructure/shared-vpc-gke/variables.tf b/infrastructure/shared-vpc-gke/variables.tf new file mode 100644 index 0000000000..a69be5b6a7 --- /dev/null +++ b/infrastructure/shared-vpc-gke/variables.tf @@ -0,0 +1,87 @@ +# Copyright 2019 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 "billing_account_id" { + description = "Billing account id used as default for new projects." + type = string +} + +variable "owners_gce" { + description = "GCE project owners, in IAM format." + type = list(string) + default = [] +} + +variable "owners_gke" { + description = "GKE project owners, in IAM format." + type = list(string) + default = [] +} + +variable "owners_host" { + description = "Host project owners, in IAM format." + type = list(string) + default = [] +} + +variable "prefix" { + description = "Prefix used for resources that need unique names." + type = string +} + +variable "region" { + description = "Region used." + type = string + default = "europe-west1" +} + +variable "root_node" { + description = "Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'." + type = string +} + +variable "ip_ranges" { + description = "Subnet IP CIDR ranges." + type = map(string) + default = { + gce = "10.0.16.0/24" + gke = "10.0.32.0/24" + } +} + +variable "ip_secondary_ranges" { + description = "Secondary IP CIDR ranges." + type = map(string) + default = { + gke-pods = "10.128.0.0/18" + gke-services = "172.16.0.0/24" + } +} + +variable "private_service_ranges" { + description = "Private service IP CIDR ranges." + type = map(string) + default = { + cluster-1 = "192.168.0.0/28" + } +} + +variable "project_services" { + description = "Service APIs enabled by default in new projects." + type = list(string) + default = [ + "resourceviews.googleapis.com", + "stackdriver.googleapis.com", + ] +} diff --git a/infrastructure/shared-vpc/versions.tf b/infrastructure/shared-vpc-gke/versions.tf similarity index 100% rename from infrastructure/shared-vpc/versions.tf rename to infrastructure/shared-vpc-gke/versions.tf diff --git a/infrastructure/shared-vpc/README.md b/infrastructure/shared-vpc/README.md deleted file mode 100644 index 9bec065a64..0000000000 --- a/infrastructure/shared-vpc/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Shared VPC sample - -This sample creates a basic [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) infrastructure, where two service projects are connected to separate subnets, and the host project exposes Cloud DNS and Cloud KMS as centralized services. The service projects are slightly different, as they are meant to illustrate the IAM-level differences that need to be taken into account when sharing subnets for GCE or GKE. - -The purpose of this sample is showing how to wire different [Cloud Foundation Fabric](https://github.com/search?q=topic%3Acft-fabric+org%3Aterraform-google-modules&type=Repositories) modules to create Shared VPC infrastructures, and as such it is meant to be used for prototyping, or to experiment with networking configurations. Additional best practices and security considerations need to be taken into account for real world usage (eg removal of default service accounts, disabling of external IPs, firewall design, etc). - -![High-level diagram](diagram.png "High-level diagram") - -## Managed resources and services - -This sample creates several distinct groups of resources: - -- three projects (Shared VPC host and two service projects) -- VPC-level resources (VPC, subnets, firewall rules, etc.) in the host project -- one internal Cloud DNS zone in the host project -- one Cloud KMS keyring with one key in the host project -- IAM roles to wire all the above resource together -- one test instance in each project, with their associated DNS records - -## Test resources - -A set of test resources are included for convenience, as they facilitate experimenting with different networking configurations (firewall rules, external connectivity via VPN, etc.). They are encapsulated in the `test-resources.tf` file, and can be safely removed as a single unit. - -SSH access to instances is configured via [OS Login](https://cloud.google.com/compute/docs/oslogin/), except for the GKE project instance since [GKE nodes do not support OS Login](https://cloud.google.com/compute/docs/instances/managing-instance-access#limitations). To access the GKe instance, use a SSH key set at the project or instance level. External access is allowed via the default SSH rule created by the firewall module, and corresponding `ssh` tags on the instances. - -The GCE instance is somewhat special, as it's configured to run a containerized MySQL server using the [`cos-mysql` module](https://github.com/terraform-google-modules/terraform-google-container-vm/tree/master/modules/cos-mysql), to show a practical example of using this module with KMS encryption for its secret, and to demonstrate how to define a custom firewall rule in the firewall module. - -The networking and GKE instances have `dig` and the `mysql` client installed via startup scripts, so that tests can be run as soon as they are created. - -## Destroying - -There's a minor glitch that can surface running `terraform destroy`, with a simple workaround. The glitch is due to a delay between the API reporting service project removal from the Shared VPC as successful (`google_compute_shared_vpc_service_project` resources destroyed), and the Shared VPC resource being aligned with that event. This results in an error that prevents disabling the Shared VPC feature: `Error disabling Shared VPC Host [...] Cannot disable project as a shared VPC host because it has active service projects.`. The workaround is to run `terraform destroy` again after a few seconds, giving the Shared VPC resource time to be in sync with service project removal. - - -## Variables - -| name | description | type | required | default | -|---|---|:---: |:---:|:---:| -| billing_account_id | Billing account id used as default for new projects. | string | ✓ | | -| prefix | Prefix used for resources that need unique names. | string | ✓ | | -| root_node | Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'. | string | ✓ | | -| *kms_keyring_location* | Location used for the KMS keyring. | string | | europe | -| *kms_keyring_name* | Name used for the KMS keyring. | string | | svpc-example | -| *oslogin_admins_gce* | GCE project oslogin admin members, in IAM format. | list(string) | | [] | -| *oslogin_users_gce* | GCE project oslogin user members, in IAM format. | list(string) | | [] | -| *owners_gce* | GCE project owners, in IAM format. | list(string) | | [] | -| *owners_gke* | GKE project owners, in IAM format. | list(string) | | [] | -| *owners_host* | Host project owners, in IAM format. | list(string) | | [] | -| *project_services* | Service APIs enabled by default in new projects. | list(string) | | ... | -| *subnet_secondary_ranges* | Shared VPC subnets secondary range definitions. | map(list(object({...}))) | | ... | -| *subnets* | Shared VPC subnet definitions. | list(object({...})) | | ... | - -## Outputs - -| name | description | sensitive | -|---|---|:---:| -| host_project_id | VPC host project id. | | -| service_project_ids | Service project ids. | | -| vpc_name | Shared VPC name | | -| vpc_subnets | Shared VPC subnets. | | - diff --git a/infrastructure/shared-vpc/diagram.png b/infrastructure/shared-vpc/diagram.png deleted file mode 100644 index 0386795405..0000000000 Binary files a/infrastructure/shared-vpc/diagram.png and /dev/null differ diff --git a/infrastructure/shared-vpc/locals.tf b/infrastructure/shared-vpc/locals.tf deleted file mode 100644 index bf46122498..0000000000 --- a/infrastructure/shared-vpc/locals.tf +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2019 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 { - # GCE service project users that need the network user role assigned on host - net_gce_users = concat( - var.owners_gce, - ["serviceAccount:${module.project-service-gce.cloudsvc_service_account}"] - ) - # GKE service project users that need the network user role assigned on host - net_gke_users = concat( - var.owners_gke, - [ - "serviceAccount:${module.project-service-gke.gke_service_account}", - "serviceAccount:${module.project-service-gke.cloudsvc_service_account}" - ] - ) - # GKE subnet primary and secondary ranges, used in firewall rules - # use lookup to prevent failure on successive destroys - net_gke_ip_ranges = compact([ - lookup(local.net_subnet_ips, "gke", ""), - element([ - for range in lookup(var.subnet_secondary_ranges, "gke", []) : - range.ip_cidr_range if range.range_name == "pods" - ], 0) - ]) - # map of subnet names => addresses - net_subnet_ips = zipmap( - module.net-vpc-host.subnets_names, - module.net-vpc-host.subnets_ips - ) - # map of subnet names => links - net_subnet_links = zipmap( - module.net-vpc-host.subnets_names, - module.net-vpc-host.subnets_self_links - ) - # map of subnet names => regions - net_subnet_regions = zipmap( - module.net-vpc-host.subnets_names, - module.net-vpc-host.subnets_regions - ) - # use svpc access module outputs to create an implicit dependency on service project registration - service_projects = zipmap( - module.net-svpc-access.service_projects, - module.net-svpc-access.service_projects - ) -} diff --git a/infrastructure/shared-vpc/main.tf b/infrastructure/shared-vpc/main.tf deleted file mode 100644 index 4816f48f3f..0000000000 --- a/infrastructure/shared-vpc/main.tf +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright 2019 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 and service projects # -############################################################################### - -# host project - -module "project-svpc-host" { - source = "terraform-google-modules/project-factory/google//modules/fabric-project" - version = "5.0.0" - parent = var.root_node - prefix = var.prefix - name = "vpc-host" - billing_account = var.billing_account_id - owners = var.owners_host - activate_apis = concat( - var.project_services, - ["dns.googleapis.com", "cloudkms.googleapis.com"] - ) -} - -# service projects - -module "project-service-gce" { - source = "terraform-google-modules/project-factory/google//modules/fabric-project" - version = "5.0.0" - parent = var.root_node - prefix = var.prefix - name = "gce" - billing_account = var.billing_account_id - oslogin = "true" - owners = var.owners_gce - oslogin_admins = var.oslogin_admins_gce - oslogin_users = var.oslogin_users_gce - activate_apis = var.project_services -} - -module "project-service-gke" { - source = "terraform-google-modules/project-factory/google//modules/fabric-project" - version = "5.0.0" - parent = var.root_node - prefix = var.prefix - name = "gke" - billing_account = var.billing_account_id - owners = var.owners_gke - activate_apis = var.project_services -} - -################################################################################ -# Networking # -################################################################################ - -# Shared VPC - -module "net-vpc-host" { - source = "terraform-google-modules/network/google" - version = "1.4.3" - project_id = module.project-svpc-host.project_id - network_name = "vpc-shared" - shared_vpc_host = true - subnets = var.subnets - secondary_ranges = var.subnet_secondary_ranges - routes = [] -} - -# Shared VPC firewall - -module "net-vpc-firewall" { - source = "terraform-google-modules/network/google//modules/fabric-net-firewall" - version = "1.4.3" - project_id = module.project-svpc-host.project_id - network = module.net-vpc-host.network_name - admin_ranges_enabled = true - admin_ranges = compact([lookup(local.net_subnet_ips, "networking", "")]) - custom_rules = { - ingress-mysql = { - description = "Allow incoming connections on the MySQL port from GKE addresses." - direction = "INGRESS" - action = "allow" - ranges = local.net_gke_ip_ranges - sources = [] - targets = ["mysql"] - use_service_accounts = false - rules = [{ protocol = "tcp", ports = [3306] }] - extra_attributes = {} - } - } -} - -# Shared VPC access - -module "net-svpc-access" { - source = "terraform-google-modules/network/google//modules/fabric-net-svpc-access" - version = "1.4.3" - host_project_id = module.project-svpc-host.project_id - service_project_num = 2 - service_project_ids = [ - module.project-service-gce.project_id, - module.project-service-gke.project_id - ] - host_subnets = ["gce", "gke"] - host_subnet_regions = compact([ - lookup(local.net_subnet_regions, "gce", ""), - lookup(local.net_subnet_regions, "gke", "") - ]) - host_subnet_users = { - gce = join(",", local.net_gce_users) - gke = join(",", local.net_gke_users) - } - host_service_agent_role = true - host_service_agent_users = [ - "serviceAccount:${module.project-service-gke.gke_service_account}" - ] -} - -################################################################################ -# DNS # -################################################################################ - -module "host-dns" { - source = "terraform-google-modules/cloud-dns/google" - version = "2.0.0" - project_id = module.project-svpc-host.project_id - type = "private" - name = "svpc-fabric-example" - domain = "svpc.fabric." - private_visibility_config_networks = [module.net-vpc-host.network_self_link] - record_names = ["localhost"] - record_data = [{ rrdatas = "127.0.0.1", type = "A" }] -} - -################################################################################ -# KMS # -################################################################################ - -module "host-kms" { - source = "terraform-google-modules/kms/google" - version = "1.1.0" - project_id = module.project-svpc-host.project_id - location = var.kms_keyring_location - keyring = var.kms_keyring_name - keys = ["mysql"] - set_decrypters_for = ["mysql"] - decrypters = ["serviceAccount:${module.project-service-gce.gce_service_account}"] - prevent_destroy = false -} diff --git a/infrastructure/shared-vpc/outputs.tf b/infrastructure/shared-vpc/outputs.tf deleted file mode 100644 index c7e021d073..0000000000 --- a/infrastructure/shared-vpc/outputs.tf +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2019 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 "vpc_name" { - description = "Shared VPC name" - value = module.net-vpc-host.network_name -} - -output "vpc_subnets" { - description = "Shared VPC subnets." - value = local.net_subnet_ips -} - -output "host_project_id" { - description = "VPC host project id." - value = module.project-svpc-host.project_id -} - -output "service_project_ids" { - description = "Service project ids." - value = { - gce = module.project-service-gce.project_id - gke = module.project-service-gke.project_id - } -} diff --git a/infrastructure/shared-vpc/test-resources.tf b/infrastructure/shared-vpc/test-resources.tf deleted file mode 100644 index b24ce58c77..0000000000 --- a/infrastructure/shared-vpc/test-resources.tf +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright 2019 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 test VM and DNS record # -############################################################################### - -resource "google_compute_instance" "test-net" { - project = module.project-svpc-host.project_id - name = "test-net" - machine_type = "f1-micro" - zone = "${local.net_subnet_regions.networking}-b" - tags = ["ssh"] - boot_disk { - initialize_params { - image = "debian-cloud/debian-9" - } - } - network_interface { - network = module.net-vpc-host.network_self_link - subnetwork = local.net_subnet_links.networking - access_config {} - } - metadata_startup_script = "apt update && apt install -y dnsutils mysql-client" -} - -resource "google_dns_record_set" "test_net" { - project = module.project-svpc-host.project_id - name = "test-net.${module.host-dns.domain}" - type = "A" - ttl = 300 - managed_zone = module.host-dns.name - rrdatas = [ - google_compute_instance.test-net.network_interface.0.network_ip - ] -} - -############################################################################### -# GKE project test VM and DNS record # -############################################################################### - -resource "google_compute_instance" "test-gke" { - depends_on = [module.net-svpc-access] - project = module.project-service-gke.project_id - name = "test-gke" - machine_type = "f1-micro" - zone = "${local.net_subnet_regions.gke}-b" - tags = ["ssh"] - boot_disk { - initialize_params { - image = "debian-cloud/debian-9" - } - } - network_interface { - network = module.net-vpc-host.network_self_link - subnetwork = local.net_subnet_links.gke - access_config {} - } - metadata_startup_script = "apt update && apt install -y dnsutils mysql-client" -} - -resource "google_dns_record_set" "test_gke" { - project = module.project-svpc-host.project_id - name = "test-gke.${module.host-dns.domain}" - type = "A" - ttl = 300 - managed_zone = module.host-dns.name - rrdatas = [ - google_compute_instance.test-gke.network_interface.0.network_ip - ] -} - -############################################################################### -# GCE project MySQL test VM and DNS record # -############################################################################### - -# random password for MySQL - -resource "random_pet" "mysql_password" {} - -# MySQL password encrypted via KMS key - -data "google_kms_secret_ciphertext" "mysql_password" { - crypto_key = module.host-kms.keys.mysql - plaintext = random_pet.mysql_password.id -} - -# work around the encrypted password always refreshing, taint to refresh - -resource "null_resource" "mysql_password" { - triggers = { - ciphertext = data.google_kms_secret_ciphertext.mysql_password.ciphertext - } - lifecycle { - ignore_changes = [triggers] - } -} - -# MySQL container on Container Optimized OS - -module "container-vm_cos-mysql" { - source = "terraform-google-modules/container-vm/google//modules/cos-mysql" - version = "1.0.4" - project_id = lookup(local.service_projects, module.project-service-gce.project_id, "") - region = "${lookup(local.net_subnet_regions, "gce", "")}" - zone = "${lookup(local.net_subnet_regions, "gce", "")}-b" - network = module.net-vpc-host.network_self_link - subnetwork = lookup(local.net_subnet_links, "gke", "") - instance_count = "1" - data_disk_size = "10" - vm_tags = ["ssh", "mysql"] - password = null_resource.mysql_password.triggers.ciphertext - # TODO(ludomagno): add a location output to the keyring module - kms_data = { - key = "mysql" - keyring = module.host-kms.keyring_name - location = var.kms_keyring_location - project_id = module.project-svpc-host.project_id - } -} - -resource "google_dns_record_set" "mysql" { - project = module.project-svpc-host.project_id - name = "mysql.${module.host-dns.domain}" - type = "A" - ttl = 300 - managed_zone = module.host-dns.name - rrdatas = [ - values(module.container-vm_cos-mysql.instances)[0] - ] -} - -############################################################################### -# test outputs # -############################################################################### - -output "test-instances" { - description = "Test instance names." - value = { - gke = map( - google_compute_instance.test-gke.name, - google_compute_instance.test-gke.network_interface.0.network_ip - ) - mysql = module.container-vm_cos-mysql.instances - networking = map( - google_compute_instance.test-net.name, - google_compute_instance.test-net.network_interface.0.network_ip - ) - } -} - -output "mysql-root-password" { - description = "Password for the test MySQL db root user." - sensitive = true - value = random_pet.mysql_password.id -} diff --git a/infrastructure/shared-vpc/variables.tf b/infrastructure/shared-vpc/variables.tf deleted file mode 100644 index 0f1f03fa30..0000000000 --- a/infrastructure/shared-vpc/variables.tf +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2019 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 "billing_account_id" { - description = "Billing account id used as default for new projects." - type = string -} - -variable "kms_keyring_location" { - description = "Location used for the KMS keyring." - type = string - default = "europe" -} - -variable "kms_keyring_name" { - description = "Name used for the KMS keyring." - type = string - default = "svpc-example" -} - -variable "oslogin_admins_gce" { - description = "GCE project oslogin admin members, in IAM format." - type = list(string) - default = [] -} - -variable "oslogin_users_gce" { - description = "GCE project oslogin user members, in IAM format." - type = list(string) - default = [] -} - -variable "owners_gce" { - description = "GCE project owners, in IAM format." - type = list(string) - default = [] -} - -variable "owners_gke" { - description = "GKE project owners, in IAM format." - type = list(string) - default = [] -} - -variable "owners_host" { - description = "Host project owners, in IAM format." - type = list(string) - default = [] -} - -variable "prefix" { - description = "Prefix used for resources that need unique names." - type = string -} - -variable "root_node" { - description = "Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'." - type = string -} - -variable "subnets" { - description = "Shared VPC subnet definitions." - type = list(object({ - subnet_name = string - subnet_ip = string - subnet_region = string - subnet_private_access = string - })) - default = [ - { - subnet_name = "networking" - subnet_ip = "10.0.0.0/24" - subnet_region = "europe-west1" - subnet_private_access = "true" - }, - { - subnet_name = "gce" - subnet_ip = "10.0.16.0/24" - subnet_region = "europe-west1" - subnet_private_access = "true" - }, - { - subnet_name = "gke" - subnet_ip = "10.0.32.0/24" - subnet_region = "europe-west1" - subnet_private_access = "true" - }, - ] -} - -variable "subnet_secondary_ranges" { - description = "Shared VPC subnets secondary range definitions." - type = map(list(object({ - range_name = string - ip_cidr_range = string - }))) - default = { - networking = [], - gce = [], - gke = [ - { - range_name = "services" - ip_cidr_range = "172.16.0.0/24" - }, - { - range_name = "pods" - ip_cidr_range = "10.128.0.0/18" - } - ] - } -} - -variable "project_services" { - description = "Service APIs enabled by default in new projects." - type = list(string) - default = [ - "resourceviews.googleapis.com", - "stackdriver.googleapis.com", - ] -} diff --git a/modules/compute-vm/README.md b/modules/compute-vm/README.md index 3e8420b19d..0ccbe704c8 100644 --- a/modules/compute-vm/README.md +++ b/modules/compute-vm/README.md @@ -103,6 +103,7 @@ module "debian-test" { | self_links | Instance self links. | | | service_account | Service account resource. | | | service_account_email | Service account email. | | +| service_account_iam_email | Service account email. | | | template | Template resource. | | | template_name | Template name. | | diff --git a/modules/compute-vm/main.tf b/modules/compute-vm/main.tf index e7360e469e..c297424b8d 100644 --- a/modules/compute-vm/main.tf +++ b/modules/compute-vm/main.tf @@ -32,7 +32,11 @@ locals { ) service_account_email = ( var.service_account_create - ? google_service_account.service_account[0].email + ? ( + length(google_service_account.service_account) > 0 + ? google_service_account.service_account[0].email + : null + ) : var.service_account ) } diff --git a/modules/compute-vm/outputs.tf b/modules/compute-vm/outputs.tf index d463594b62..e927c15965 100644 --- a/modules/compute-vm/outputs.tf +++ b/modules/compute-vm/outputs.tf @@ -83,6 +83,14 @@ output "service_account_email" { value = local.service_account_email } +output "service_account_iam_email" { + description = "Service account email." + value = join("", [ + "serviceAccount:", + local.service_account_email == null ? "" : local.service_account_email + ]) +} + output "template" { description = "Template resource." value = ( diff --git a/modules/project/README.md b/modules/project/README.md index c0da33f4fd..bd02563e1c 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -41,7 +41,7 @@ module "project" { | *oslogin* | Enable OS Login. | bool | | false | | *oslogin_admins* | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string) | | [] | | *oslogin_users* | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string) | | [] | -| *prevent_default_network_deletion* | Prevent deletion of default network. Use this if your organization has skipDefaultNetworkCreation enforced. | bool | | false | +| *prevent_default_network_deletion* | Prevent deletion of default network (use this if your organization has skipDefaultNetworkCreation enforced) | bool | | false | | *services* | Service APIs to enable. | list(string) | | [] | ## Outputs @@ -53,6 +53,7 @@ module "project" { | gce_service_account | Default GCE service account (depends on services). | | | gcr_service_account | Default GCR service account (depends on services). | | | gke_service_account | Default GKE service account (depends on services). | | +| iam_project_id | Project id (depends on services and IAM bindings). | | | name | Name (depends on services). | | | number | Project number (depends on services). | | | project_id | Project id (depends on services). | | diff --git a/modules/project/outputs.tf b/modules/project/outputs.tf index 04d040c865..7cb7021748 100644 --- a/modules/project/outputs.tf +++ b/modules/project/outputs.tf @@ -17,7 +17,19 @@ output "project_id" { description = "Project id (depends on services)." value = google_project.project.project_id - depends_on = [google_project_service.project_services] + depends_on = [ + google_project_service.project_services + ] +} + +output "iam_project_id" { + description = "Project id (depends on services and IAM bindings)." + value = google_project.project.project_id + depends_on = [ + google_project_service.project_services, + google_project_iam_binding.authoritative, + google_project_iam_member.non_authoritative + ] } output "name" {