From 89d33443201d1aba04b387f62253b54bd172aa89 Mon Sep 17 00:00:00 2001 From: Richard Hsu Date: Tue, 30 Jul 2019 17:19:31 -0400 Subject: [PATCH 1/6] initial release --- .gitignore | 45 +++++++ CHANGELOG.md | 14 +++ CONTRIBUTING.md | 110 ++++++++++++++++ LICENSE | 1 + README.md | 68 ++++++++++ examples/two_service_example/README.md | 41 ++++++ examples/two_service_example/example.tf | 31 +++++ examples/two_service_example/iam.tf | 29 +++++ examples/two_service_example/instance.tf | 71 +++++++++++ examples/two_service_example/outputs.tf | 16 +++ .../two_service_example/service_accounts.tf | 9 ++ examples/two_service_example/variables.tf | 36 ++++++ main.tf | 118 ++++++++++++++++++ outputs.tf | 15 +++ variables.tf | 36 ++++++ 15 files changed, 640 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 README.md create mode 100644 examples/two_service_example/README.md create mode 100644 examples/two_service_example/example.tf create mode 100644 examples/two_service_example/iam.tf create mode 100644 examples/two_service_example/instance.tf create mode 100644 examples/two_service_example/outputs.tf create mode 100644 examples/two_service_example/service_accounts.tf create mode 100644 examples/two_service_example/variables.tf create mode 100644 main.tf create mode 100644 outputs.tf create mode 100644 variables.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9ce300b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# OSX leaves these everywhere on SMB shares +._* + +# OSX trash +.DS_Store + +# Python +*.pyc + +# Emacs save files +*~ +\#*\# +.\#* + +# Vim-related files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist + +### https://raw.github.com/github/gitignore/90f149de451a5433aebd94d02d11b0e28843a1af/Terraform.gitignore + +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Kitchen files +**/inspec.lock +**/.kitchen +**/kitchen.local.yml +**/Gemfile.lock + +# Ignore any .tfvars files that are generated automatically for each Terraform run. Most +# .tfvars files are managed as part of configuration and so should be included in +# version control. +**/*.tfvars + +credentials.json diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..eb64c1db --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on +[Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2019-08-02 + +- Initial release + +[0.1.0]: https://github.com/terraform-google-modules/terraform-google-bastion-host/releases/tag/v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..43e610e6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,110 @@ +# Contributing + +This document provides guidelines for contributing to the module. + +## Generating Documentation for Inputs and Outputs + +The Inputs and Outputs tables in the READMEs of the root module, +submodules, and example modules are automatically generated based on +the `variables` and `outputs` of the respective modules. These tables +must be refreshed if the module interfaces are changed. + +### Dependencies + +The following dependencies must be installed on the development system: + +- [make] +- [terraform-docs] v0.6.0 + +### Execution + +Run `make generate_docs` to generate new Inputs and Outputs tables. + +## Integration Testing + +Integration tests are used to verify the behaviour of the root module, +submodules, and example modules. Additions, changes, and fixes should +be accompanied with tests. + +The integration tests are run using [Kitchen][kitchen], +[Kitchen-Terraform][kitchen-terraform], and [InSpec][inspec]. These +tools are packaged within a Docker image for convenience. + +The general strategy for these tests is to verify the behaviour of the +[example modules](./examples), thus ensuring that the root module, +submodules, and example modules are all functionally correct. + +### Dependencies + +The following dependencies must be installed on the development system: + +- [Docker Engine][docker-engine] +- [Google Cloud SDK][google-cloud-sdk] +- [make] + +### Inputs + +Test instances are defined in the +[Kitchen configuration file](./kitchen.yml). The inputs of each Kitchen +instance may be configured with the `driver.variables` key in a +local Kitchen configuration file located at `./kitchen.local.yml` or in +a Terraform variables file located at +`./test/fixtures//variables.tfvars`. + +### Credentials + +Download the key of a Service Account with the +[required roles][required-roles] to `./credentials.json`. + +### Interactive Execution + +1. Run `make docker_run` to start the testing Docker container in + interactive mode. + +1. Run `kitchen create ` to initialize the working + directory for an example module. + +1. Run `kitchen converge ` to apply the example module. + +1. Run `kitchen verify ` to test the example module. + +1. Run `kitchen destroy ` to destroy the example module + state. + +### Noninteractive Execution + +Run `make test_integration_docker` to test all of the example modules +noninteractively. + +## Linting and Formatting + +Many of the files in the repository can be linted or formatted to +maintain a standard of quality. + +### Dependencies + +The following dependencies must be installed on the development system: + +- [flake8] +- [gofmt] +- [hadolint] +- [make] +- [shellcheck] +- [Terraform][terraform] v0.11 + +### Execution + +Run `make check`. + +[docker-engine]: https://www.docker.com/products/docker-engine +[flake8]: http://flake8.pycqa.org/en/latest/ +[gofmt]: https://golang.org/cmd/gofmt/ +[google-cloud-sdk]: https://cloud.google.com/sdk/install +[hadolint]: https://github.com/hadolint/hadolint +[inspec]: https://inspec.io/ +[kitchen-terraform]: https://github.com/newcontext-oss/kitchen-terraform +[kitchen]: https://kitchen.ci/ +[make]: https://en.wikipedia.org/wiki/Make_(software) +[shellcheck]: https://www.shellcheck.net/ +[terraform-docs]: https://github.com/segmentio/terraform-docs +[terraform]: https://terraform.io/ diff --git a/LICENSE b/LICENSE index 261eeb9e..d6456956 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/README.md b/README.md new file mode 100644 index 00000000..4f77e56b --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# terraform-google-bastion-host + +This module was generated from [terraform-google-module-template](https://github.com/terraform-google-modules/terraform-google-module-template/). + +This module will generate a bastion host vm compatible with os login and IAP tunneling that can be used to access internal VMs. + +The resources/services/activations/deletions that this module will create/trigger are: + +- Creates a dedicated service account for the bastion host VM +- Creates a GCE instance of n1-standard to be the bastion +- Firewall to allow TCP:22 ssh access from the IAP to the bastion +- Firewall to allow TCP:22 ssh acccess from the bastion to other instances on the network +- IAM binding to allow members to utilize the IAP Tunnel +- IAM binding granting os login to members through the bastion host +- IAM binding granting the usage of the dedicated bastion host service account +- Creates a custom role that has limited privileges to enable OS Login on an instance level +- IAM binding to the custom role + +## Usage + +Basic usage of this module is as follows: + +```hcl +module "iap_bastion" { + source = "terraform-google-modules/terraform-google-bastion-host/" + project_id = "" + subnet = "" + network = "" + zone = "" + members = "" +} +``` + +Functional example is included in the +[examples](./examples/) directory. + +## Requirements + +These sections describe requirements for using this module. + +### Software + +The following dependencies must be available: + +- [Terraform][terraform] v0.11 +- [Terraform Provider for GCP][terraform-provider-gcp] plugin v2.0 + +### APIs + +A project with the following APIs enabled must be used to host the +resources of this module: + +- Google Cloud Storage JSON API: `storage-api.googleapis.com` +- Compute Engine API: `compute.googleapis.com` +- Cloud Identity-Aware Proxy API: `iap.googleapis.com` + +The [Project Factory module][project-factory-module] can be used to +provision a project with the necessary APIs enabled. + +## Contributing + +Refer to the [contribution guidelines](./CONTRIBUTING.md) for +information on contributing to this module. + +[iam-module]: https://registry.terraform.io/modules/terraform-google-modules/iam/google +[project-factory-module]: https://registry.terraform.io/modules/terraform-google-modules/project-factory/google +[terraform-provider-gcp]: https://www.terraform.io/docs/providers/google/index.html +[terraform]: https://www.terraform.io/downloads.html diff --git a/examples/two_service_example/README.md b/examples/two_service_example/README.md new file mode 100644 index 00000000..f573ddf7 --- /dev/null +++ b/examples/two_service_example/README.md @@ -0,0 +1,41 @@ +# Two Service Example + +This example illustrates how to use the `iap-bastion` module. It illustrate an example where there are two services being deployed in a single project. Service A is deployed to two VMS (priv-host-a-1 and priv-host-a-2) and Service B is deployed to a single VM (priv-host-b-1). The bastion host module is deployed that will allow User A to access VM's for Service A, and User B to access Service B through the shared bastion host. + +After this module is deployed, you can test ssh-ing to the private hosts by following these steps: + +1. Login as User A: + +- `gcloud auth login` login as user A + +2. If you have existing google_compute_engine ssh keys, ( ~/.ssh/google_compute_engine.pub ) back them up, otherwise continue to step 3 + +- `cd ~/.ssh` change working directory to ssh directory +- `mv google_compute_engine.pub google_compute_engine_backup.pub` backup public key +- `mv google_compute_engine google_compute_engine_backup_backup` backup private key + +3. Change project to sample project + +- `gcloud config set project ` change to project id that you used + +4. Generate new google compute engine keys and ssh over to bastion host + +- `gcloud compute ssh bastion-vm --zone=` zone defaults to us-central1-a + +5. Exit out from bastion + +- `exit` should return to local terminal + +6. Start SSH Agent + +- `eval "$(ssh-agent -s)"` + +7. Add SSH key to the ssh-agent + +- `ssh-add ~/.ssh/google_compute_engine` + +8. SSH to private VM through bastion host + +- `gcloud compute ssh bastion-vm --zone=us-central1-a --ssh-flag="-A" --command "ssh priv-host-a-1" -- -t` + +9. Can also try sshing to the other host, priv-host-a-2. Should work. Try sshing to the B host, (priv-host-b-2) should fail. Try using user B, get another user to follow above steps. If you have access to a test account, you can use that as well, but make sure to backup the ssh keys from the steps above. diff --git a/examples/two_service_example/example.tf b/examples/two_service_example/example.tf new file mode 100644 index 00000000..b5df13d2 --- /dev/null +++ b/examples/two_service_example/example.tf @@ -0,0 +1,31 @@ +/** + * Copyright 2018 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. + */ + +provider "google" { + project = "${var.project_id}" + zone = "${var.zone}" +} + +provider "google-beta" {} + +module "iap_bastion" { + source = "../.." + project_id = "${var.project_id}" + subnet = "${var.subnet}" + network = "${var.network}" + zone = "${var.zone}" + members = ["${var.user-a}", "${var.user-b}"] +} diff --git a/examples/two_service_example/iam.tf b/examples/two_service_example/iam.tf new file mode 100644 index 00000000..57400675 --- /dev/null +++ b/examples/two_service_example/iam.tf @@ -0,0 +1,29 @@ +resource "google_compute_instance_iam_member" "alice_oslogin_1" { + instance_name = "${google_compute_instance.priv_host_a_1.name}" + role = "roles/compute.osLogin" + member = "${var.user-a}" +} + +resource "google_compute_instance_iam_member" "alice_oslogin_2" { + instance_name = "${google_compute_instance.priv_host_a_2.name}" + role = "roles/compute.osLogin" + member = "${var.user-a}" +} + +resource "google_service_account_iam_member" "gce-default-account-iam" { + service_account_id = "${google_service_account.service-a.id}" + role = "roles/iam.serviceAccountUser" + member = "${var.user-a}" +} + +resource "google_compute_instance_iam_member" "bdole_oslogin" { + instance_name = "${google_compute_instance.priv_host_b_1.name}" + role = "roles/compute.osLogin" + member = "${var.user-b}" +} + +resource "google_service_account_iam_member" "bdole_use_sa" { + service_account_id = "${google_service_account.service-b.id}" + role = "roles/iam.serviceAccountUser" + member = "${var.user-b}" +} diff --git a/examples/two_service_example/instance.tf b/examples/two_service_example/instance.tf new file mode 100644 index 00000000..91afcd97 --- /dev/null +++ b/examples/two_service_example/instance.tf @@ -0,0 +1,71 @@ +resource "google_compute_instance" "priv_host_a_1" { + name = "priv-host-a-1" + machine_type = "n1-standard-1" + + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + + network_interface { + subnetwork = "${var.subnet}" + } + + service_account { + email = "${google_service_account.service-a.email}" + scopes = ["cloud-platform"] + } + + metadata = { + enable-oslogin = "TRUE" + } +} + +resource "google_compute_instance" "priv_host_a_2" { + name = "priv-host-a-2" + machine_type = "n1-standard-1" + + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + + network_interface { + subnetwork = "${var.subnet}" + } + + service_account { + email = "${google_service_account.service-a.email}" + scopes = ["cloud-platform"] + } + + metadata = { + enable-oslogin = "TRUE" + } +} + +resource "google_compute_instance" "priv_host_b_1" { + name = "priv-host-b-1" + machine_type = "n1-standard-1" + + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + + network_interface { + subnetwork = "${var.subnet}" + } + + service_account { + email = "${google_service_account.service-b.email}" + scopes = ["cloud-platform"] + } + + metadata = { + enable-oslogin = "TRUE" + } +} diff --git a/examples/two_service_example/outputs.tf b/examples/two_service_example/outputs.tf new file mode 100644 index 00000000..ee50d7d3 --- /dev/null +++ b/examples/two_service_example/outputs.tf @@ -0,0 +1,16 @@ +/** + * Copyright 2018 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. + */ + diff --git a/examples/two_service_example/service_accounts.tf b/examples/two_service_example/service_accounts.tf new file mode 100644 index 00000000..e127e360 --- /dev/null +++ b/examples/two_service_example/service_accounts.tf @@ -0,0 +1,9 @@ +resource "google_service_account" "service-a" { + account_id = "service-a" + display_name = "Service Account for Service A" +} + +resource "google_service_account" "service-b" { + account_id = "service-b" + display_name = "Service Account for Service B" +} diff --git a/examples/two_service_example/variables.tf b/examples/two_service_example/variables.tf new file mode 100644 index 00000000..756887be --- /dev/null +++ b/examples/two_service_example/variables.tf @@ -0,0 +1,36 @@ +/** + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The ID of the project in which to provision resources." + type = "string" +} + +variable "subnet" { + default = "default" +} + +variable "network" { + default = "default" +} + +variable "zone" { + default = "us-central1-a" +} + +variable "user-a" {} + +variable "user-b" {} diff --git a/main.tf b/main.tf new file mode 100644 index 00000000..afb64b06 --- /dev/null +++ b/main.tf @@ -0,0 +1,118 @@ +/** + * Copyright 2018 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. + */ + +terraform { + required_version = "~> 0.11.0" +} + +provider "google-beta" { + project = "${var.project_id}" + zone = "${var.zone}" +} + +resource "google_service_account" "bastion_host" { + account_id = "bastion" + display_name = "Service Account for Bastion" +} + +resource "google_compute_instance" "bastion-vm" { + zone = "${var.zone}" + name = "bastion-vm" + machine_type = "n1-standard-1" + + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + + network_interface { + subnetwork = "${var.subnet}" + } + + service_account { + email = "${google_service_account.bastion_host.email}" + scopes = ["cloud-platform"] + } + + metadata = { + enable-oslogin = "TRUE" + } +} + +resource "google_compute_firewall" "allow_from_bastion" { + name = "allow-ssh-from-bastion" + network = "${var.network}" + + allow { + protocol = "tcp" + ports = ["22"] + } + + source_service_accounts = ["${google_service_account.bastion_host.email}"] +} + +resource "google_compute_firewall" "allow_from_iap_to_bastion" { + name = "allow-ssh-from-iap-to-tunnel" + network = "${var.network}" + + allow { + protocol = "tcp" + ports = ["22"] + } + + # https://cloud.google.com/iap/docs/using-tcp-forwarding#before_you_begin + # This is the netblock needed to forward to the instances + source_ranges = ["35.235.240.0/20"] + + target_service_accounts = ["${google_service_account.bastion_host.email}"] +} + +resource "google_iap_tunnel_instance_iam_binding" "enable_iap" { + provider = "google-beta" + project = "${var.project_id}" + zone = "${var.zone}" + instance = "${google_compute_instance.bastion-vm.name}" + role = "roles/iap.tunnelResourceAccessor" + members = "${var.members}" +} + +resource "google_compute_instance_iam_binding" "enable_os_login" { + instance_name = "${google_compute_instance.bastion-vm.name}" + role = "roles/compute.osLogin" + members = "${var.members}" +} + +resource "google_service_account_iam_binding" "bastion_use_sa" { + service_account_id = "${google_service_account.bastion_host.id}" + role = "roles/iam.serviceAccountUser" + members = "${var.members}" +} + +#if you are practing least privilege, to enable instance level os login, you +#still need the compute.projects.get permission on the project level. The other +# predefined roles grant additional permissions that aren't needed +resource "google_project_iam_custom_role" "compute_os_login_viewer" { + role_id = "osLoginProjectGet" + title = "OS Login Project Get Role" + description = "From Terraform: iap-bastion module custom role for more fine grained scoping of permissions" + permissions = ["compute.projects.get"] +} + +resource "google_project_iam_binding" "compute_os_binding" { + role = "projects/${var.project_id}/roles/${google_project_iam_custom_role.compute_os_login_viewer.role_id}" + members = "${var.members}" +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 00000000..cfccff84 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,15 @@ +/** + * Copyright 2018 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. + */ diff --git a/variables.tf b/variables.tf new file mode 100644 index 00000000..8463ee31 --- /dev/null +++ b/variables.tf @@ -0,0 +1,36 @@ +/** + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The project ID to deploy to" +} + +variable "subnet" { + default = "default" +} + +variable "network" { + default = "default" +} + +variable "members" { + type = "list" + default = [] +} + +variable "zone" { + default = "us-central1-a" +} From 6c54a6a6422095b32273983e4d2a6aeb440b6850 Mon Sep 17 00:00:00 2001 From: Ryan Canty Date: Fri, 18 Oct 2019 10:10:05 -0700 Subject: [PATCH 2/6] Update to terraform 0.12 * Fixed some IAM issues * Support Shielded VM by default * Addressed comments in pull/2 * Added Simple Example * Polished READMEs --- CHANGELOG.md | 5 +- README.md | 39 ++++---- examples/simple_example/README.md | 31 +++++++ examples/simple_example/main.tf | 73 +++++++++++++++ examples/simple_example/variables.tf | 12 +++ examples/two_service_example/README.md | 71 +++++++++----- examples/two_service_example/example.tf | 18 ++-- examples/two_service_example/iam.tf | 29 +++--- examples/two_service_example/instance.tf | 19 ++-- examples/two_service_example/outputs.tf | 1 - .../two_service_example/service_accounts.tf | 7 +- examples/two_service_example/variables.tf | 26 ++++-- examples/two_service_example/versions.tf | 4 + main.tf | 92 +++++++++---------- outputs.tf | 3 + variables.tf | 64 +++++++++++-- versions.tf | 23 +++++ 17 files changed, 379 insertions(+), 138 deletions(-) create mode 100644 examples/simple_example/README.md create mode 100644 examples/simple_example/main.tf create mode 100644 examples/simple_example/variables.tf create mode 100644 examples/two_service_example/versions.tf create mode 100644 versions.tf diff --git a/CHANGELOG.md b/CHANGELOG.md index eb64c1db..c0a0790f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ The format is based on and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.1.0] - 2019-08-02 +## [Unreleased] + +### Fixed +### [0.1.0] - 2019-08-02 - Initial release diff --git a/README.md b/README.md index 4f77e56b..26043671 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,13 @@ # terraform-google-bastion-host -This module was generated from [terraform-google-module-template](https://github.com/terraform-google-modules/terraform-google-module-template/). +This module will generate a bastion host vm compatible with [OS Login](https://cloud.google.com/compute/docs/oslogin/) and [IAP Tunneling](https://cloud.google.com/iap/) that can be used to access internal VMs. -This module will generate a bastion host vm compatible with os login and IAP tunneling that can be used to access internal VMs. +This module will: -The resources/services/activations/deletions that this module will create/trigger are: - -- Creates a dedicated service account for the bastion host VM -- Creates a GCE instance of n1-standard to be the bastion -- Firewall to allow TCP:22 ssh access from the IAP to the bastion -- Firewall to allow TCP:22 ssh acccess from the bastion to other instances on the network -- IAM binding to allow members to utilize the IAP Tunnel -- IAM binding granting os login to members through the bastion host -- IAM binding granting the usage of the dedicated bastion host service account -- Creates a custom role that has limited privileges to enable OS Login on an instance level -- IAM binding to the custom role +- Create a dedicated service account for the bastion host +- Create a GCE instance to be the bastion host +- Create a firewall rule to allow TCP:22 SSH access from the IAP to the bastion +- Necessary IAM bindings to allow IAP and OS Logins from specified members ## Usage @@ -22,12 +15,16 @@ Basic usage of this module is as follows: ```hcl module "iap_bastion" { - source = "terraform-google-modules/terraform-google-bastion-host/" - project_id = "" - subnet = "" - network = "" - zone = "" - members = "" + source = "terraform-google-modules/bastion-host/google" + project = var.project + region = var.region + zone = var.zone + network = google_compute_network.net.self_link + subnet = google_compute_subnetwork.net.self_link + members = [ + "group:devs@example.com", + "user:me@example.com", + ] } ``` @@ -42,8 +39,8 @@ These sections describe requirements for using this module. The following dependencies must be available: -- [Terraform][terraform] v0.11 -- [Terraform Provider for GCP][terraform-provider-gcp] plugin v2.0 +- [Terraform][terraform] v0.12 +- [Terraform Provider for GCP][terraform-provider-gcp] ### APIs diff --git a/examples/simple_example/README.md b/examples/simple_example/README.md new file mode 100644 index 00000000..c14c82c5 --- /dev/null +++ b/examples/simple_example/README.md @@ -0,0 +1,31 @@ +# Simple Example + +This example will not set up the target hosts like the [Two Service Example](../two_service_example) but it will set up a basic network, subnet and bastion host for you to log into using IAP and OS Login. You'll notice that we create a firewall rule that allows the bastion to talk to the rest of the network on port 22 using the output of the bastion service account email for simplicity. This can and should be scoped down to allow access to specific hosts. + +## Deploy + +Create a `terraform.tfvars` file with required variables similar to: + +``` +members = ["user:me@example.com"] +project = "my-project" +``` + +Run the apply + +``` +terraform apply -var-file terraform.tfvars +``` + +## Usage + +``` +gcloud auth login +gcloud compute ssh bastion-vm +``` + +You should now be logged in as a user that looks like `ext_me_example_com` with the prefix of `ext` indicating you have logged in with OS Login. You should also notice the following line in standard out that indicates you are tunnelling through IAP instead of the public internet: + +``` +External IP address was not found; defaulting to using IAP tunneling. +``` \ No newline at end of file diff --git a/examples/simple_example/main.tf b/examples/simple_example/main.tf new file mode 100644 index 00000000..79aa2b7a --- /dev/null +++ b/examples/simple_example/main.tf @@ -0,0 +1,73 @@ +module "iap_bastion" { + source = "../.." + project = var.project + zone = var.zone + network = google_compute_network.network.self_link + subnet = google_compute_subnetwork.subnet.self_link + members = var.members +} + +# Address for NATing +resource "google_compute_address" "nat" { + project = var.project + region = var.region + name = "bastion-nat-external" +} + +# Create a NAT router so the nodes can reach the public Internet +resource "google_compute_router" "router" { + name = "bastion-router" + project = var.project + region = var.region + network = google_compute_network.network.self_link + bgp { + asn = 64514 + } +} + +# NAT on the main subnetwork +resource "google_compute_router_nat" "nat" { + name = "bastion-nat-1" + project = var.project + region = var.region + router = google_compute_router.router.name + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = ["${google_compute_address.nat.self_link}"] + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + + subnetwork { + name = "${google_compute_subnetwork.subnet.self_link}" + source_ip_ranges_to_nat = ["PRIMARY_IP_RANGE"] + } + +} + +resource "google_compute_network" "network" { + project = var.project + name = "test-network" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnet" { + project = var.project + name = "test-subnet" + region = var.region + ip_cidr_range = "10.127.0.0/20" + network = google_compute_network.network.self_link +} + +resource "google_compute_firewall" "allow_access_from_bastion" { + project = var.project + name = "allow-bastion-ssh" + network = google_compute_network.network.self_link + + allow { + protocol = "tcp" + ports = ["22"] + } + + # Allow SSH only from IAP Bastion + source_service_accounts = [module.iap_bastion.service_account] +} \ No newline at end of file diff --git a/examples/simple_example/variables.tf b/examples/simple_example/variables.tf new file mode 100644 index 00000000..91ae02d8 --- /dev/null +++ b/examples/simple_example/variables.tf @@ -0,0 +1,12 @@ +variable "project" {} +variable "zone" { + default = "us-west1-a" +} + +variable "region" { + default = "us-west1" +} + +variable "members" { + default = [] +} diff --git a/examples/two_service_example/README.md b/examples/two_service_example/README.md index f573ddf7..28ed402d 100644 --- a/examples/two_service_example/README.md +++ b/examples/two_service_example/README.md @@ -1,41 +1,70 @@ # Two Service Example -This example illustrates how to use the `iap-bastion` module. It illustrate an example where there are two services being deployed in a single project. Service A is deployed to two VMS (priv-host-a-1 and priv-host-a-2) and Service B is deployed to a single VM (priv-host-b-1). The bastion host module is deployed that will allow User A to access VM's for Service A, and User B to access Service B through the shared bastion host. +This example illustrates how to use the `bastion-host` module. It illustrate an example where there are two services being deployed in a single project. Service A is deployed to two VMS (priv-host-a-1 and priv-host-a-2) and Service B is deployed to a single VM (priv-host-b-1). The bastion host module is deployed that will allow User A to access VM's for Service A, and User B to access Service B through the shared bastion host. You'll notice that we create a firewall rule that allows the bastion to talk to the rest of the network on port 22 using the output of the bastion service account email for simplicity. This can and should be scoped down to allow access to specific hosts. -After this module is deployed, you can test ssh-ing to the private hosts by following these steps: -1. Login as User A: +## Deploy -- `gcloud auth login` login as user A +Create a `terraform.tfvars` file with required variables. Should look something like: -2. If you have existing google_compute_engine ssh keys, ( ~/.ssh/google_compute_engine.pub ) back them up, otherwise continue to step 3 +``` +project = "my-project" +user_a = "user:me@example.com" +user_b = "user:someone@example.com" +network = "projects/my-project/global/networks/default" +subnet = "projects/rcanty-project-0529/regions/us-west1/subnetworks/default" +``` -- `cd ~/.ssh` change working directory to ssh directory -- `mv google_compute_engine.pub google_compute_engine_backup.pub` backup public key -- `mv google_compute_engine google_compute_engine_backup_backup` backup private key +Run the apply -3. Change project to sample project +``` +terraform apply -var-file terraform.tfvars +``` -- `gcloud config set project ` change to project id that you used +## Usage -4. Generate new google compute engine keys and ssh over to bastion host +After this module is deployed, you can test SSHing to the private hosts by following these steps: -- `gcloud compute ssh bastion-vm --zone=` zone defaults to us-central1-a +Login as User A: -5. Exit out from bastion +``` +gcloud auth login +``` -- `exit` should return to local terminal +If you have existing google_compute_engine ssh keys, ( ~/.ssh/google_compute_engine.pub ) back them up, otherwise skip this step -6. Start SSH Agent +``` +cd ~/.ssh # change working directory to ssh directory +mv google_compute_engine.pub google_compute_engine_backup.pub # backup public key +mv google_compute_engine google_compute_engine_backup_backup # backup private key +``` -- `eval "$(ssh-agent -s)"` +Update your gcloud config -7. Add SSH key to the ssh-agent +``` +gcloud config set project +gcloud config set compute/region us-west1 +gcloud config set compute/zone us-west1-a +``` -- `ssh-add ~/.ssh/google_compute_engine` +Generate new google compute engine keys and ssh over to bastion host -8. SSH to private VM through bastion host +``` +gcloud compute ssh bastion-vm +``` -- `gcloud compute ssh bastion-vm --zone=us-central1-a --ssh-flag="-A" --command "ssh priv-host-a-1" -- -t` +Exit out from bastion using `exit`. Then start SSH Agent and add your key to it: -9. Can also try sshing to the other host, priv-host-a-2. Should work. Try sshing to the B host, (priv-host-b-2) should fail. Try using user B, get another user to follow above steps. If you have access to a test account, you can use that as well, but make sure to backup the ssh keys from the steps above. +``` +eval "$(ssh-agent -s)" +ssh-add ~/.ssh/google_compute_engine +``` + +SSH to private VM through bastion host + +``` +gcloud compute ssh bastion-vm --ssh-flag="-A" --command "ssh priv-host-a-1" -- -t +``` + + +You can also try SSHing to the other host, priv-host-a-2. This should work. Try sshing to the B host, (priv-host-b-2) should fail. Try using user B, get another user to follow above steps. If you have access to a test account, you can use that as well, but make sure to backup the ssh keys from the steps above. diff --git a/examples/two_service_example/example.tf b/examples/two_service_example/example.tf index b5df13d2..a4a7be87 100644 --- a/examples/two_service_example/example.tf +++ b/examples/two_service_example/example.tf @@ -15,17 +15,17 @@ */ provider "google" { - project = "${var.project_id}" - zone = "${var.zone}" } -provider "google-beta" {} +provider "google-beta" { +} module "iap_bastion" { - source = "../.." - project_id = "${var.project_id}" - subnet = "${var.subnet}" - network = "${var.network}" - zone = "${var.zone}" - members = ["${var.user-a}", "${var.user-b}"] + source = "../.." + project = var.project + subnet = var.subnet + network = var.network + zone = var.zone + members = [var.user_a, var.user_b] } + diff --git a/examples/two_service_example/iam.tf b/examples/two_service_example/iam.tf index 57400675..b333f7ab 100644 --- a/examples/two_service_example/iam.tf +++ b/examples/two_service_example/iam.tf @@ -1,29 +1,36 @@ resource "google_compute_instance_iam_member" "alice_oslogin_1" { - instance_name = "${google_compute_instance.priv_host_a_1.name}" + project = var.project + zone = var.zone + instance_name = google_compute_instance.priv_host_a_1.name role = "roles/compute.osLogin" - member = "${var.user-a}" + member = var.user_a } resource "google_compute_instance_iam_member" "alice_oslogin_2" { - instance_name = "${google_compute_instance.priv_host_a_2.name}" + project = var.project + zone = var.zone + instance_name = google_compute_instance.priv_host_a_2.name role = "roles/compute.osLogin" - member = "${var.user-a}" + member = var.user_a } -resource "google_service_account_iam_member" "gce-default-account-iam" { - service_account_id = "${google_service_account.service-a.id}" +resource "google_service_account_iam_member" "gce_default_account_iam" { + service_account_id = google_service_account.service_a.id role = "roles/iam.serviceAccountUser" - member = "${var.user-a}" + member = var.user_a } resource "google_compute_instance_iam_member" "bdole_oslogin" { - instance_name = "${google_compute_instance.priv_host_b_1.name}" + project = var.project + zone = var.zone + instance_name = google_compute_instance.priv_host_b_1.name role = "roles/compute.osLogin" - member = "${var.user-b}" + member = var.user_b } resource "google_service_account_iam_member" "bdole_use_sa" { - service_account_id = "${google_service_account.service-b.id}" + service_account_id = google_service_account.service_b.id role = "roles/iam.serviceAccountUser" - member = "${var.user-b}" + member = var.user_b } + diff --git a/examples/two_service_example/instance.tf b/examples/two_service_example/instance.tf index 91afcd97..1e6a95a2 100644 --- a/examples/two_service_example/instance.tf +++ b/examples/two_service_example/instance.tf @@ -1,4 +1,6 @@ resource "google_compute_instance" "priv_host_a_1" { + project = var.project + zone = var.zone name = "priv-host-a-1" machine_type = "n1-standard-1" @@ -9,11 +11,11 @@ resource "google_compute_instance" "priv_host_a_1" { } network_interface { - subnetwork = "${var.subnet}" + subnetwork = var.subnet } service_account { - email = "${google_service_account.service-a.email}" + email = google_service_account.service_a.email scopes = ["cloud-platform"] } @@ -23,6 +25,8 @@ resource "google_compute_instance" "priv_host_a_1" { } resource "google_compute_instance" "priv_host_a_2" { + project = var.project + zone = var.zone name = "priv-host-a-2" machine_type = "n1-standard-1" @@ -33,11 +37,11 @@ resource "google_compute_instance" "priv_host_a_2" { } network_interface { - subnetwork = "${var.subnet}" + subnetwork = var.subnet } service_account { - email = "${google_service_account.service-a.email}" + email = google_service_account.service_a.email scopes = ["cloud-platform"] } @@ -47,6 +51,8 @@ resource "google_compute_instance" "priv_host_a_2" { } resource "google_compute_instance" "priv_host_b_1" { + project = var.project + zone = var.zone name = "priv-host-b-1" machine_type = "n1-standard-1" @@ -57,11 +63,11 @@ resource "google_compute_instance" "priv_host_b_1" { } network_interface { - subnetwork = "${var.subnet}" + subnetwork = var.subnet } service_account { - email = "${google_service_account.service-b.email}" + email = google_service_account.service_b.email scopes = ["cloud-platform"] } @@ -69,3 +75,4 @@ resource "google_compute_instance" "priv_host_b_1" { enable-oslogin = "TRUE" } } + diff --git a/examples/two_service_example/outputs.tf b/examples/two_service_example/outputs.tf index ee50d7d3..cfccff84 100644 --- a/examples/two_service_example/outputs.tf +++ b/examples/two_service_example/outputs.tf @@ -13,4 +13,3 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - diff --git a/examples/two_service_example/service_accounts.tf b/examples/two_service_example/service_accounts.tf index e127e360..60cabba8 100644 --- a/examples/two_service_example/service_accounts.tf +++ b/examples/two_service_example/service_accounts.tf @@ -1,9 +1,12 @@ -resource "google_service_account" "service-a" { +resource "google_service_account" "service_a" { + project = var.project account_id = "service-a" display_name = "Service Account for Service A" } -resource "google_service_account" "service-b" { +resource "google_service_account" "service_b" { + project = var.project account_id = "service-b" display_name = "Service Account for Service B" } + diff --git a/examples/two_service_example/variables.tf b/examples/two_service_example/variables.tf index 756887be..27fe9f07 100644 --- a/examples/two_service_example/variables.tf +++ b/examples/two_service_example/variables.tf @@ -14,23 +14,29 @@ * limitations under the License. */ -variable "project_id" { +variable "project" { description = "The ID of the project in which to provision resources." - type = "string" + type = string } -variable "subnet" { - default = "default" +variable "network" { + description = "Self link for the VPC network" + type = string } -variable "network" { - default = "default" +variable "subnet" { + description = "Self link for the Subnet within var.network" + type = string } -variable "zone" { - default = "us-central1-a" +variable "user_a" { + description = "User in the IAM policy format of user:{email}" } -variable "user-a" {} +variable "user_b" { + description = "User in the IAM policy format of user:{email}" +} -variable "user-b" {} +variable "zone" { + default = "us-west1-a" +} \ No newline at end of file diff --git a/examples/two_service_example/versions.tf b/examples/two_service_example/versions.tf new file mode 100644 index 00000000..ac97c6ac --- /dev/null +++ b/examples/two_service_example/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +} diff --git a/main.tf b/main.tf index afb64b06..0fc685d6 100644 --- a/main.tf +++ b/main.tf @@ -14,60 +14,53 @@ * limitations under the License. */ -terraform { - required_version = "~> 0.11.0" -} - -provider "google-beta" { - project = "${var.project_id}" - zone = "${var.zone}" -} +provider "google-beta" {} resource "google_service_account" "bastion_host" { + project = var.project account_id = "bastion" display_name = "Service Account for Bastion" } -resource "google_compute_instance" "bastion-vm" { - zone = "${var.zone}" - name = "bastion-vm" - machine_type = "n1-standard-1" +resource "google_compute_instance" "bastion_vm" { + project = var.project + zone = var.zone + name = var.name + machine_type = var.machine_type + labels = var.labels boot_disk { initialize_params { - image = "debian-cloud/debian-9" + image = var.image } } + scratch_disk {} network_interface { - subnetwork = "${var.subnet}" + subnetwork = var.subnet } service_account { - email = "${google_service_account.bastion_host.email}" - scopes = ["cloud-platform"] + email = google_service_account.bastion_host.email + scopes = var.scopes } + metadata_startup_script = var.startup_script metadata = { enable-oslogin = "TRUE" } -} -resource "google_compute_firewall" "allow_from_bastion" { - name = "allow-ssh-from-bastion" - network = "${var.network}" - - allow { - protocol = "tcp" - ports = ["22"] + shielded_instance_config { + enable_secure_boot = var.shielded_vm + enable_vtpm = var.shielded_vm + enable_integrity_monitoring = var.shielded_vm } - - source_service_accounts = ["${google_service_account.bastion_host.email}"] } resource "google_compute_firewall" "allow_from_iap_to_bastion" { + project = var.project name = "allow-ssh-from-iap-to-tunnel" - network = "${var.network}" + network = var.network allow { protocol = "tcp" @@ -77,42 +70,43 @@ resource "google_compute_firewall" "allow_from_iap_to_bastion" { # https://cloud.google.com/iap/docs/using-tcp-forwarding#before_you_begin # This is the netblock needed to forward to the instances source_ranges = ["35.235.240.0/20"] - - target_service_accounts = ["${google_service_account.bastion_host.email}"] + target_service_accounts = [google_service_account.bastion_host.email] } resource "google_iap_tunnel_instance_iam_binding" "enable_iap" { provider = "google-beta" - project = "${var.project_id}" - zone = "${var.zone}" - instance = "${google_compute_instance.bastion-vm.name}" + project = var.project + zone = var.zone + instance = google_compute_instance.bastion_vm.name role = "roles/iap.tunnelResourceAccessor" - members = "${var.members}" + members = var.members } -resource "google_compute_instance_iam_binding" "enable_os_login" { - instance_name = "${google_compute_instance.bastion-vm.name}" - role = "roles/compute.osLogin" - members = "${var.members}" +resource "google_service_account_iam_binding" "bastion_sa_user" { + service_account_id = google_service_account.bastion_host.id + role = "roles/iam.serviceAccountUser" + members = var.members } -resource "google_service_account_iam_binding" "bastion_use_sa" { - service_account_id = "${google_service_account.bastion_host.id}" - role = "roles/iam.serviceAccountUser" - members = "${var.members}" +resource "google_project_iam_binding" "bastion_sa_bindings" { + for_each = toset(compact(concat( + var.service_account_roles, + var.service_account_roles_supplemental, + ["projects/${var.project}/roles/${google_project_iam_custom_role.compute_os_login_viewer.role_id}"] + ))) + + project = var.project + role = each.key + members = ["serviceAccount:${google_service_account.bastion_host.email}"] } -#if you are practing least privilege, to enable instance level os login, you -#still need the compute.projects.get permission on the project level. The other +# If you are practing least privilege, to enable instance level OS Login, you +# still need the compute.projects.get permission on the project level. The other # predefined roles grant additional permissions that aren't needed resource "google_project_iam_custom_role" "compute_os_login_viewer" { + project = var.project role_id = "osLoginProjectGet" title = "OS Login Project Get Role" description = "From Terraform: iap-bastion module custom role for more fine grained scoping of permissions" permissions = ["compute.projects.get"] -} - -resource "google_project_iam_binding" "compute_os_binding" { - role = "projects/${var.project_id}/roles/${google_project_iam_custom_role.compute_os_login_viewer.role_id}" - members = "${var.members}" -} +} \ No newline at end of file diff --git a/outputs.tf b/outputs.tf index cfccff84..2f433f7a 100644 --- a/outputs.tf +++ b/outputs.tf @@ -13,3 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +output "service_account" { + value = google_service_account.bastion_host.email +} diff --git a/variables.tf b/variables.tf index 8463ee31..1f5260c0 100644 --- a/variables.tf +++ b/variables.tf @@ -14,23 +14,73 @@ * limitations under the License. */ -variable "project_id" { - description = "The project ID to deploy to" +variable "image" { + description = "GCE image on which to base the Bastion. This image is supported by Shielded VM" + default = "gce-uefi-images/centos-7" } -variable "subnet" { - default = "default" +variable "instances" { + description = "Number of bastion hosts" + default = 1 } -variable "network" { - default = "default" +variable "labels" { + type = "map" + default = {} +} + +variable "machine_type" { + default = "n1-standard-1" } variable "members" { type = "list" default = [] } +variable "name" { + description = "Name of the Bastion instance" + default = "bastion-vm" +} + +variable "network" { + description = "Self link for the network on which the Bastion should live" +} + +variable "project" { + description = "The project ID to deploy to" +} + +variable "scopes" { + description = "List of scopes to attach to the bastion host" + default = ["cloud-platform"] +} + +variable "service_account_roles" { + description = "List of IAM roles to assign to the service account." + default = [ + "roles/logging.logWriter", + "roles/monitoring.metricWriter", + "roles/monitoring.viewer", + "roles/compute.osLogin", + ] +} +variable "service_account_roles_supplemental" { + description = "An additional list of roles to assign to the bastion if desired" + default = [] +} +variable "shielded_vm" { + default = true +} +variable "startup_script" { + description = "Render a startup script with a template." + default = "" +} + +variable "subnet" { + description = "Self link for the subnet on which the Bastion should live. Can be private when using IAP" +} variable "zone" { + description = "The primary zone where the bastion host will live" default = "us-central1-a" -} +} \ No newline at end of file diff --git a/versions.tf b/versions.tf new file mode 100644 index 00000000..b0720402 --- /dev/null +++ b/versions.tf @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Google Inc. + * + * 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. + */ + +terraform { + required_version = "~> 0.12" + required_providers { + google = "~> 2.17" + google-beta = "~> 2.17" + } +} \ No newline at end of file From c91fd163033737cc884e50bcd7ee441ef2061d19 Mon Sep 17 00:00:00 2001 From: Ryan Canty Date: Fri, 18 Oct 2019 11:53:51 -0700 Subject: [PATCH 3/6] Added outputs and addressed some comments --- examples/simple_example/outputs.tf | 15 +++++++++++++++ main.tf | 2 +- outputs.tf | 12 ++++++++++++ variables.tf | 5 ----- 4 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 examples/simple_example/outputs.tf diff --git a/examples/simple_example/outputs.tf b/examples/simple_example/outputs.tf new file mode 100644 index 00000000..5c66edb3 --- /dev/null +++ b/examples/simple_example/outputs.tf @@ -0,0 +1,15 @@ +output "service_account" { + value = module.iap_bastion.service_account +} + +output "hostname" { + value = module.iap_bastion.hostname +} + +output "ip_address" { + value = module.iap_bastion.ip_address +} + +output "self_link" { + value = module.iap_bastion.self_link +} \ No newline at end of file diff --git a/main.tf b/main.tf index 0fc685d6..80457635 100644 --- a/main.tf +++ b/main.tf @@ -100,7 +100,7 @@ resource "google_project_iam_binding" "bastion_sa_bindings" { members = ["serviceAccount:${google_service_account.bastion_host.email}"] } -# If you are practing least privilege, to enable instance level OS Login, you +# If you are practicing least privilege, to enable instance level OS Login, you # still need the compute.projects.get permission on the project level. The other # predefined roles grant additional permissions that aren't needed resource "google_project_iam_custom_role" "compute_os_login_viewer" { diff --git a/outputs.tf b/outputs.tf index 2f433f7a..ef723693 100644 --- a/outputs.tf +++ b/outputs.tf @@ -16,3 +16,15 @@ output "service_account" { value = google_service_account.bastion_host.email } + +output "hostname" { + value = var.name +} + +output "ip_address" { + value = google_compute_instance.bastion_vm.network_interface.0.network_ip +} + +output "self_link" { + value = google_compute_instance.bastion_vm.self_link +} \ No newline at end of file diff --git a/variables.tf b/variables.tf index 1f5260c0..700bf045 100644 --- a/variables.tf +++ b/variables.tf @@ -19,11 +19,6 @@ variable "image" { default = "gce-uefi-images/centos-7" } -variable "instances" { - description = "Number of bastion hosts" - default = 1 -} - variable "labels" { type = "map" default = {} From 89399ea15ef02cfd416e968cc0e9d5148bfdc7d2 Mon Sep 17 00:00:00 2001 From: Ryan Canty Date: Mon, 21 Oct 2019 09:41:46 -0700 Subject: [PATCH 4/6] s/google_project_iam_binding/google_project_iam_member/ --- main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.tf b/main.tf index 80457635..8952edfb 100644 --- a/main.tf +++ b/main.tf @@ -88,7 +88,7 @@ resource "google_service_account_iam_binding" "bastion_sa_user" { members = var.members } -resource "google_project_iam_binding" "bastion_sa_bindings" { +resource "google_project_iam_member" "bastion_sa_bindings" { for_each = toset(compact(concat( var.service_account_roles, var.service_account_roles_supplemental, @@ -97,7 +97,7 @@ resource "google_project_iam_binding" "bastion_sa_bindings" { project = var.project role = each.key - members = ["serviceAccount:${google_service_account.bastion_host.email}"] + member = "serviceAccount:${google_service_account.bastion_host.email}" } # If you are practicing least privilege, to enable instance level OS Login, you From 6b6f88d90f91d19e73fc559c352ae172dd348aa1 Mon Sep 17 00:00:00 2001 From: Ryan Canty Date: Mon, 21 Oct 2019 15:20:40 -0700 Subject: [PATCH 5/6] Addressed comments, fmt, and cleaned up two_service_example --- README.md | 3 ++ examples/simple_example/main.tf | 50 +++---------------- examples/simple_example/outputs.tf | 15 ------ examples/simple_example/variables.tf | 10 ++-- examples/two_service_example/iam.tf | 13 ++++- .../{instance.tf => instances.tf} | 1 - .../{example.tf => main.tf} | 1 - examples/two_service_example/outputs.tf | 15 ------ .../two_service_example/service_accounts.tf | 12 ----- examples/two_service_example/variables.tf | 8 +-- examples/two_service_example/versions.tf | 4 -- main.tf | 16 +++--- outputs.tf | 14 ++++-- variables.tf | 35 ++++++++----- versions.tf | 4 +- 15 files changed, 73 insertions(+), 128 deletions(-) delete mode 100644 examples/simple_example/outputs.tf rename examples/two_service_example/{instance.tf => instances.tf} (99%) rename examples/two_service_example/{example.tf => main.tf} (99%) delete mode 100644 examples/two_service_example/outputs.tf delete mode 100644 examples/two_service_example/service_accounts.tf delete mode 100644 examples/two_service_example/versions.tf diff --git a/README.md b/README.md index 26043671..eb8225ab 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Basic usage of this module is as follows: ```hcl module "iap_bastion" { source = "terraform-google-modules/bastion-host/google" + version = 0.1.0 + project = var.project region = var.region zone = var.zone @@ -50,6 +52,7 @@ resources of this module: - Google Cloud Storage JSON API: `storage-api.googleapis.com` - Compute Engine API: `compute.googleapis.com` - Cloud Identity-Aware Proxy API: `iap.googleapis.com` +- OS Login API: `oslogin.googleapis.com` The [Project Factory module][project-factory-module] can be used to provision a project with the necessary APIs enabled. diff --git a/examples/simple_example/main.tf b/examples/simple_example/main.tf index 79aa2b7a..953aa988 100644 --- a/examples/simple_example/main.tf +++ b/examples/simple_example/main.tf @@ -7,43 +7,6 @@ module "iap_bastion" { members = var.members } -# Address for NATing -resource "google_compute_address" "nat" { - project = var.project - region = var.region - name = "bastion-nat-external" -} - -# Create a NAT router so the nodes can reach the public Internet -resource "google_compute_router" "router" { - name = "bastion-router" - project = var.project - region = var.region - network = google_compute_network.network.self_link - bgp { - asn = 64514 - } -} - -# NAT on the main subnetwork -resource "google_compute_router_nat" "nat" { - name = "bastion-nat-1" - project = var.project - region = var.region - router = google_compute_router.router.name - - nat_ip_allocate_option = "MANUAL_ONLY" - nat_ips = ["${google_compute_address.nat.self_link}"] - - source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" - - subnetwork { - name = "${google_compute_subnetwork.subnet.self_link}" - source_ip_ranges_to_nat = ["PRIMARY_IP_RANGE"] - } - -} - resource "google_compute_network" "network" { project = var.project name = "test-network" @@ -51,11 +14,12 @@ resource "google_compute_network" "network" { } resource "google_compute_subnetwork" "subnet" { - project = var.project - name = "test-subnet" - region = var.region - ip_cidr_range = "10.127.0.0/20" - network = google_compute_network.network.self_link + project = var.project + name = "test-subnet" + region = var.region + ip_cidr_range = "10.127.0.0/20" + network = google_compute_network.network.self_link + private_ip_google_access = true } resource "google_compute_firewall" "allow_access_from_bastion" { @@ -70,4 +34,4 @@ resource "google_compute_firewall" "allow_access_from_bastion" { # Allow SSH only from IAP Bastion source_service_accounts = [module.iap_bastion.service_account] -} \ No newline at end of file +} diff --git a/examples/simple_example/outputs.tf b/examples/simple_example/outputs.tf deleted file mode 100644 index 5c66edb3..00000000 --- a/examples/simple_example/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "service_account" { - value = module.iap_bastion.service_account -} - -output "hostname" { - value = module.iap_bastion.hostname -} - -output "ip_address" { - value = module.iap_bastion.ip_address -} - -output "self_link" { - value = module.iap_bastion.self_link -} \ No newline at end of file diff --git a/examples/simple_example/variables.tf b/examples/simple_example/variables.tf index 91ae02d8..dcb4112e 100644 --- a/examples/simple_example/variables.tf +++ b/examples/simple_example/variables.tf @@ -1,12 +1,12 @@ -variable "project" {} -variable "zone" { - default = "us-west1-a" +variable "members" { + default = [] } +variable "project" {} variable "region" { default = "us-west1" } -variable "members" { - default = [] +variable "zone" { + default = "us-west1-a" } diff --git a/examples/two_service_example/iam.tf b/examples/two_service_example/iam.tf index b333f7ab..6ce7ee5f 100644 --- a/examples/two_service_example/iam.tf +++ b/examples/two_service_example/iam.tf @@ -1,3 +1,15 @@ +resource "google_service_account" "service_a" { + project = var.project + account_id = "service-a" + display_name = "Service Account for Service A" +} + +resource "google_service_account" "service_b" { + project = var.project + account_id = "service-b" + display_name = "Service Account for Service B" +} + resource "google_compute_instance_iam_member" "alice_oslogin_1" { project = var.project zone = var.zone @@ -33,4 +45,3 @@ resource "google_service_account_iam_member" "bdole_use_sa" { role = "roles/iam.serviceAccountUser" member = var.user_b } - diff --git a/examples/two_service_example/instance.tf b/examples/two_service_example/instances.tf similarity index 99% rename from examples/two_service_example/instance.tf rename to examples/two_service_example/instances.tf index 1e6a95a2..4dc8168d 100644 --- a/examples/two_service_example/instance.tf +++ b/examples/two_service_example/instances.tf @@ -75,4 +75,3 @@ resource "google_compute_instance" "priv_host_b_1" { enable-oslogin = "TRUE" } } - diff --git a/examples/two_service_example/example.tf b/examples/two_service_example/main.tf similarity index 99% rename from examples/two_service_example/example.tf rename to examples/two_service_example/main.tf index a4a7be87..167fbbea 100644 --- a/examples/two_service_example/example.tf +++ b/examples/two_service_example/main.tf @@ -28,4 +28,3 @@ module "iap_bastion" { zone = var.zone members = [var.user_a, var.user_b] } - diff --git a/examples/two_service_example/outputs.tf b/examples/two_service_example/outputs.tf deleted file mode 100644 index cfccff84..00000000 --- a/examples/two_service_example/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright 2018 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. - */ diff --git a/examples/two_service_example/service_accounts.tf b/examples/two_service_example/service_accounts.tf deleted file mode 100644 index 60cabba8..00000000 --- a/examples/two_service_example/service_accounts.tf +++ /dev/null @@ -1,12 +0,0 @@ -resource "google_service_account" "service_a" { - project = var.project - account_id = "service-a" - display_name = "Service Account for Service A" -} - -resource "google_service_account" "service_b" { - project = var.project - account_id = "service-b" - display_name = "Service Account for Service B" -} - diff --git a/examples/two_service_example/variables.tf b/examples/two_service_example/variables.tf index 27fe9f07..48980393 100644 --- a/examples/two_service_example/variables.tf +++ b/examples/two_service_example/variables.tf @@ -16,17 +16,17 @@ variable "project" { description = "The ID of the project in which to provision resources." - type = string + type = string } variable "network" { description = "Self link for the VPC network" - type = string + type = string } variable "subnet" { description = "Self link for the Subnet within var.network" - type = string + type = string } variable "user_a" { @@ -39,4 +39,4 @@ variable "user_b" { variable "zone" { default = "us-west1-a" -} \ No newline at end of file +} diff --git a/examples/two_service_example/versions.tf b/examples/two_service_example/versions.tf deleted file mode 100644 index ac97c6ac..00000000 --- a/examples/two_service_example/versions.tf +++ /dev/null @@ -1,4 +0,0 @@ - -terraform { - required_version = ">= 0.12" -} diff --git a/main.tf b/main.tf index 8952edfb..b0476e36 100644 --- a/main.tf +++ b/main.tf @@ -14,14 +14,14 @@ * limitations under the License. */ -provider "google-beta" {} - resource "google_service_account" "bastion_host" { project = var.project account_id = "bastion" display_name = "Service Account for Bastion" } +# NOTE: Use the terraform-google-vm module once Shielded VMs are supported +# https://github.com/terraform-google-modules/terraform-google-vm/pull/38 resource "google_compute_instance" "bastion_vm" { project = var.project zone = var.zone @@ -69,7 +69,7 @@ resource "google_compute_firewall" "allow_from_iap_to_bastion" { # https://cloud.google.com/iap/docs/using-tcp-forwarding#before_you_begin # This is the netblock needed to forward to the instances - source_ranges = ["35.235.240.0/20"] + source_ranges = ["35.235.240.0/20"] target_service_accounts = [google_service_account.bastion_host.email] } @@ -89,15 +89,15 @@ resource "google_service_account_iam_binding" "bastion_sa_user" { } resource "google_project_iam_member" "bastion_sa_bindings" { - for_each = toset(compact(concat( + for_each = toset(compact(concat( var.service_account_roles, var.service_account_roles_supplemental, ["projects/${var.project}/roles/${google_project_iam_custom_role.compute_os_login_viewer.role_id}"] ))) - project = var.project - role = each.key - member = "serviceAccount:${google_service_account.bastion_host.email}" + project = var.project + role = each.key + member = "serviceAccount:${google_service_account.bastion_host.email}" } # If you are practicing least privilege, to enable instance level OS Login, you @@ -109,4 +109,4 @@ resource "google_project_iam_custom_role" "compute_os_login_viewer" { title = "OS Login Project Get Role" description = "From Terraform: iap-bastion module custom role for more fine grained scoping of permissions" permissions = ["compute.projects.get"] -} \ No newline at end of file +} diff --git a/outputs.tf b/outputs.tf index ef723693..904bd4f9 100644 --- a/outputs.tf +++ b/outputs.tf @@ -14,17 +14,21 @@ * limitations under the License. */ output "service_account" { - value = google_service_account.bastion_host.email + description = "The email for the service account created for the bastion host" + value = google_service_account.bastion_host.email } output "hostname" { - value = var.name + description = "Host name of the bastion" + value = var.name } output "ip_address" { - value = google_compute_instance.bastion_vm.network_interface.0.network_ip + description = "Internal IP address of the bastion host" + value = google_compute_instance.bastion_vm.network_interface.0.network_ip } output "self_link" { - value = google_compute_instance.bastion_vm.self_link -} \ No newline at end of file + description = "Self link of the bastion host" + value = google_compute_instance.bastion_vm.self_link +} diff --git a/variables.tf b/variables.tf index 700bf045..c606862e 100644 --- a/variables.tf +++ b/variables.tf @@ -16,25 +16,29 @@ variable "image" { description = "GCE image on which to base the Bastion. This image is supported by Shielded VM" - default = "gce-uefi-images/centos-7" + default = "gce-uefi-images/centos-7" } variable "labels" { - type = "map" - default = {} + description = "Key-value map of labels to assign to the bastion host" + type = "map" + default = {} } variable "machine_type" { - default = "n1-standard-1" + description = "Instance type for the Bastion host" + default = "n1-standard-1" } variable "members" { - type = "list" - default = [] + description = "List of IAM resources to allow access to the bastion host" + type = "list" + default = [] } + variable "name" { description = "Name of the Bastion instance" - default = "bastion-vm" + default = "bastion-vm" } variable "network" { @@ -45,9 +49,14 @@ variable "project" { description = "The project ID to deploy to" } +variable "region" { + description = "The primary region where the bastion host will live" + default = "us-central1" +} + variable "scopes" { description = "List of scopes to attach to the bastion host" - default = ["cloud-platform"] + default = ["cloud-platform"] } variable "service_account_roles" { @@ -61,21 +70,23 @@ variable "service_account_roles" { } variable "service_account_roles_supplemental" { description = "An additional list of roles to assign to the bastion if desired" - default = [] + default = [] } variable "shielded_vm" { default = true } + variable "startup_script" { description = "Render a startup script with a template." - default = "" + default = "" } variable "subnet" { description = "Self link for the subnet on which the Bastion should live. Can be private when using IAP" } + variable "zone" { description = "The primary zone where the bastion host will live" - default = "us-central1-a" -} \ No newline at end of file + default = "us-central1-a" +} diff --git a/versions.tf b/versions.tf index b0720402..a5e05188 100644 --- a/versions.tf +++ b/versions.tf @@ -17,7 +17,7 @@ terraform { required_version = "~> 0.12" required_providers { - google = "~> 2.17" + google = "~> 2.17" google-beta = "~> 2.17" } -} \ No newline at end of file +} From 66a330138a98b3fc5fc7f54affb383d126dae43d Mon Sep 17 00:00:00 2001 From: Ryan Canty Date: Wed, 23 Oct 2019 16:48:04 -0700 Subject: [PATCH 6/6] Use updated terraform-google-vm/instance_template module --- examples/two_service_example/main.tf | 2 +- examples/two_service_example/variables.tf | 2 +- main.tf | 50 ++++++++++------------- outputs.tf | 4 +- variables.tf | 2 +- 5 files changed, 27 insertions(+), 33 deletions(-) diff --git a/examples/two_service_example/main.tf b/examples/two_service_example/main.tf index 167fbbea..44043fbe 100644 --- a/examples/two_service_example/main.tf +++ b/examples/two_service_example/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * 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. diff --git a/examples/two_service_example/variables.tf b/examples/two_service_example/variables.tf index 48980393..6ddbf9cc 100644 --- a/examples/two_service_example/variables.tf +++ b/examples/two_service_example/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * 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. diff --git a/main.tf b/main.tf index b0476e36..b14cee97 100644 --- a/main.tf +++ b/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * 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. @@ -20,41 +20,35 @@ resource "google_service_account" "bastion_host" { display_name = "Service Account for Bastion" } -# NOTE: Use the terraform-google-vm module once Shielded VMs are supported -# https://github.com/terraform-google-modules/terraform-google-vm/pull/38 -resource "google_compute_instance" "bastion_vm" { - project = var.project - zone = var.zone - name = var.name - machine_type = var.machine_type - labels = var.labels - - boot_disk { - initialize_params { - image = var.image - } - } - scratch_disk {} - - network_interface { - subnetwork = var.subnet - } +module "instance_template" { + source = "terraform-google-modules/vm/google//modules/instance_template" + version = "1.1.0" - service_account { + project_id = var.project + machine_type = var.machine_type + subnetwork = var.subnet + service_account = { email = google_service_account.bastion_host.email - scopes = var.scopes + scopes = ["cloud-platform"] } + enable_shielded_vm = true + startup_script = var.startup_script - metadata_startup_script = var.startup_script metadata = { enable-oslogin = "TRUE" } +} + +resource "google_compute_instance_from_template" "bastion_vm" { + name = var.name + project = var.project + zone = var.zone - shielded_instance_config { - enable_secure_boot = var.shielded_vm - enable_vtpm = var.shielded_vm - enable_integrity_monitoring = var.shielded_vm + network_interface { + subnetwork = var.subnet } + + source_instance_template = module.instance_template.self_link } resource "google_compute_firewall" "allow_from_iap_to_bastion" { @@ -77,7 +71,7 @@ resource "google_iap_tunnel_instance_iam_binding" "enable_iap" { provider = "google-beta" project = var.project zone = var.zone - instance = google_compute_instance.bastion_vm.name + instance = google_compute_instance_from_template.bastion_vm.name role = "roles/iap.tunnelResourceAccessor" members = var.members } diff --git a/outputs.tf b/outputs.tf index 904bd4f9..dc6c1f67 100644 --- a/outputs.tf +++ b/outputs.tf @@ -25,10 +25,10 @@ output "hostname" { output "ip_address" { description = "Internal IP address of the bastion host" - value = google_compute_instance.bastion_vm.network_interface.0.network_ip + value = google_compute_instance_from_template.bastion_vm.network_interface.0.network_ip } output "self_link" { description = "Self link of the bastion host" - value = google_compute_instance.bastion_vm.self_link + value = google_compute_instance_from_template.bastion_vm.self_link } diff --git a/variables.tf b/variables.tf index c606862e..b52ab2cd 100644 --- a/variables.tf +++ b/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2018 Google LLC + * 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.