-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from terraform-google-modules/feature/folder-f…
…unction Implement function for managing VPC Service Controls
- Loading branch information
Showing
18 changed files
with
470 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/provider.tf | ||
/.terraform | ||
tfplan | ||
local.tfvars |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# Automatically Secured Folder | ||
|
||
This example illustrates how to use Terraform and Cloud Functions to secure all projects within a folder via VPC service Controls. | ||
|
||
Terraform is used to set up a new service perimeter and to deploy a Cloud Function which monitors that folder via Stackdriver and Cloud Pub/Sub. When the function notices a new project is added to the folder, it executes Terraform to add the new project to the associated perimeter. Similarly, the function automatically removes projects from the perimeter if they are moved out of the folder. | ||
|
||
![Diagram](./diagram.png) | ||
|
||
## Set up | ||
|
||
**Please note, the whole example folder is uploaded as a Cloud Function. Do not store credentials in it.** | ||
|
||
1. [Authenticate](https://www.terraform.io/docs/providers/google/provider_reference.html#credentials-1) to Terraform using either your user account or an exported Service Account key. | ||
|
||
```bash | ||
export GOOGLE_APPLICATION_CREDENTIALS=~/mykey.json | ||
``` | ||
|
||
You will need these roles: | ||
|
||
- Access Context Manager Admin (`roles/accesscontextmanager.policyAdmin`) | ||
- Editor on the watched project (`roles/editor`) | ||
- Logs Configuration Writer on the watched folder (`roles/logging.configWriter`) | ||
|
||
2. Choose or create a project for hosting the VPC Service Controls manager. | ||
|
||
```bash | ||
gcloud config set project YOUR_PROJECT | ||
``` | ||
|
||
3. Activate the required APIs on your management project: | ||
- cloudfunctions.googleapis.com | ||
- accesscontextmanager.googleapis.com | ||
- cloudresourcemanager.googleapis.com | ||
|
||
```bash | ||
gcloud config set project YOUR_PROJECT | ||
gcloud services enable cloudresourcemanager.googleapis.com | ||
gcloud services enable cloudfunctions.googleapis.com | ||
gcloud services enable accesscontextmanager.googleapis.com | ||
``` | ||
|
||
3. Create a Google Cloud Storage bucket to hold Terraform state. | ||
|
||
```sh | ||
gsutil mb -p YOUR_PROJECT gs://YOUR_BUCKET_NAME | ||
``` | ||
|
||
4. Copy `backend.tf.sample` to `backend.tf` and change the bucket to match your own on line 5. | ||
|
||
```sh | ||
cp backend.tf.sample backend.tf | ||
``` | ||
|
||
3. Create a local `terraform.tfvars` file with required inputs, like this: | ||
|
||
```tf | ||
project_id = "YOUR_PROJECT" | ||
org_id = "ORG_ID" | ||
folder_id = "FOLDER_ID" | ||
policy_name = "automatic_folder" | ||
members = ["user:[email protected]"] | ||
region = "us-east1" | ||
restricted_services = ["storage.googleapis.com"] | ||
``` | ||
|
||
4. Run `terraform apply` to create the perimeter and watching function. | ||
|
||
5. Grant the Cloud Function's SA access to your organization and management project. It needs these roles: | ||
- Access Context Manager Admin (`roles/accesscontextmanager.policyAdmin`) | ||
- Editor on the watched project (`roles/editor`) | ||
- Logs Configuration Writer on the watched folder (`roles/logging.configWriter`) | ||
```bash | ||
SA_ID=$(terraform output function_service_account) | ||
ORG_ID=$(terraform output organization_id) | ||
PROJECT_ID=$(terraform output project_id) | ||
FOLDER_ID=$(terraform output folder_id) | ||
gcloud organizations add-iam-policy-binding \ | ||
"${ORG_ID}" \ | ||
--member="serviceAccount:${SA_ID}" \ | ||
--role="roles/accesscontextmanager.policyAdmin" | ||
gcloud projects add-iam-policy-binding \ | ||
"${PROJECT_ID}" \ | ||
--member="serviceAccount:${SA_ID}" \ | ||
--role="roles/editor" | ||
gcloud resource-manager folders add-iam-policy-binding \ | ||
"${FOLDER_ID}" \ | ||
--member="serviceAccount:${SA_ID}" \ | ||
--role="roles/logging.configWriter" | ||
``` | ||
6. Test the function by creating a project in the protected folder (or moving an existing project in). | ||
## Limitations | ||
1. The Cloud Function used to manage the perimeter must be hosted in a project **outside** the perimeter. This is because Cloud Functions do not yet support VPC Service Controls. | ||
2. Nested folders are not supported. Only projects directly contained within your perimeter folder will be added. | ||
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> | ||
## Inputs | ||
| Name | Description | Type | Default | Required | | ||
|------|-------------|:----:|:-----:|:-----:| | ||
| folder\_id | Folder ID to watch for projects. | string | n/a | yes | | ||
| members | An allowed list of members (users, service accounts). The signed-in identity originating the request must be a part of one of the provided members. If not specified, a request may come from any user (logged in/not logged in, etc.). Formats: user:{emailid}, serviceAccount:{emailid} | list(string) | n/a | yes | | ||
| org\_id | The parent organization ID of this AccessPolicy in the Cloud Resource Hierarchy. | string | n/a | yes | | ||
| perimeter\_name | Name of perimeter. | string | `"regular_perimeter"` | no | | ||
| policy\_name | The policy's name. | string | n/a | yes | | ||
| project\_id | The ID of the project to host the watcher function. | string | n/a | yes | | ||
| region | The region in which resources will be applied. | string | n/a | yes | | ||
| restricted\_services | List of services to restrict. | list(string) | n/a | yes | | ||
|
||
## Outputs | ||
|
||
| Name | Description | | ||
|------|-------------| | ||
| folder\_id | The ID of the watched folder. | | ||
| function\_service\_account | Email of the watcher function's Service Account | | ||
| organization\_id | Organization ID hosting the perimeter | | ||
| policy\_name | Name of the parent policy | | ||
| project\_id | The ID of the project hosting the watcher function. | | ||
| protected\_project\_ids | Project ids of the projects INSIDE the regular service perimeter | | ||
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
terraform { | ||
required_version = "~> 0.12.0" | ||
|
||
backend "gcs" { | ||
bucket = "<YOUR_BUCKET_NAME>" | ||
prefix = "terraform/vpc-service-controls" | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import os | ||
import subprocess | ||
import urllib.request | ||
import os | ||
from shutil import copytree, copyfile, ignore_patterns, rmtree | ||
|
||
# Version of Terraform that we're using | ||
TERRAFORM_VERSION = '0.12.8' | ||
|
||
# Download URL for Terraform | ||
PLATFORM = 'linux' | ||
TERRAFORM_DOWNLOAD_URL = ( | ||
'https://releases.hashicorp.com/terraform/%s/terraform_%s_%s_amd64.zip' | ||
% (TERRAFORM_VERSION, TERRAFORM_VERSION, PLATFORM)) | ||
|
||
# Paths where Terraform should be installed | ||
TERRAFORM_DIR = os.path.join('/tmp', 'terraform_%s' % TERRAFORM_VERSION) | ||
TERRAFORM_PATH = os.path.join(TERRAFORM_DIR, 'terraform') | ||
|
||
PROJECT_DIR = os.path.join('/tmp', 'project') | ||
|
||
def check_call(args, cwd=None, printOut=False): | ||
"""Wrapper for subprocess that checks if a process runs correctly, | ||
and if not, prints stdout and stderr. | ||
""" | ||
proc = subprocess.Popen(args, | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
cwd=cwd) | ||
stdout, stderr = proc.communicate() | ||
if proc.returncode != 0: | ||
print(stdout.decode()) | ||
print(stderr.decode()) | ||
raise subprocess.CalledProcessError( | ||
returncode=proc.returncode, | ||
cmd=args) | ||
if printOut: | ||
print(stdout.decode()) | ||
print(stderr.decode()) | ||
|
||
|
||
def install_terraform(): | ||
"""Install Terraform.""" | ||
if os.path.exists(TERRAFORM_PATH): | ||
return | ||
|
||
print(TERRAFORM_PATH) | ||
|
||
urllib.request.urlretrieve(TERRAFORM_DOWNLOAD_URL, '/tmp/terraform.zip') | ||
|
||
check_call(['unzip', '-o', '/tmp/terraform.zip', '-d', TERRAFORM_DIR], '/tmp') | ||
|
||
check_call([TERRAFORM_PATH, '--version']) | ||
|
||
|
||
def handler(event, context): | ||
print(event) | ||
|
||
if os.path.exists(PROJECT_DIR): | ||
rmtree(PROJECT_DIR) | ||
|
||
copytree('.', PROJECT_DIR, ignore=ignore_patterns('.terraform', 'credentials.json')) | ||
|
||
install_terraform() | ||
|
||
check_call([TERRAFORM_PATH, 'init'], | ||
cwd=PROJECT_DIR, | ||
printOut=True) | ||
check_call([TERRAFORM_PATH, 'apply', | ||
'-target=module.service_perimeter', '-no-color', | ||
'-auto-approve', '-lock=false', | ||
'-lock-timeout=300s'], | ||
cwd=PROJECT_DIR, | ||
printOut=True) | ||
check_call([TERRAFORM_PATH, 'output', '-json'], | ||
cwd=PROJECT_DIR, | ||
printOut=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/** | ||
* 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 | ||
* | ||
* 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 "archive" { | ||
version = "~> 1.0" | ||
} | ||
|
||
provider "random" { | ||
version = "~> 2.0" | ||
} | ||
|
||
provider "null" { | ||
version = "~> 2.1" | ||
} | ||
|
||
data "google_projects" "in_perimeter_folder" { | ||
filter = "parent.id:${var.folder_id}" | ||
} | ||
|
||
data "google_project" "in_perimeter_folder" { | ||
count = length(data.google_projects.in_perimeter_folder.projects) | ||
|
||
project_id = data.google_projects.in_perimeter_folder.projects[count.index].project_id | ||
} | ||
|
||
locals { | ||
projects = compact(data.google_project.in_perimeter_folder.*.number) | ||
parent_id = var.org_id | ||
watcher_name = replace("${var.policy_name}-manager", "_", "-") | ||
} | ||
|
||
module "access_context_manager_policy" { | ||
source = "terraform-google-modules/vpc-service-controls/google" | ||
version = "1.0.1" | ||
|
||
parent_id = local.parent_id | ||
policy_name = var.policy_name | ||
} | ||
|
||
module "access_level_members" { | ||
source = "terraform-google-modules/vpc-service-controls/google//modules/access_level" | ||
version = "1.0.1" | ||
|
||
description = "${var.perimeter_name} Access Level" | ||
policy = module.access_context_manager_policy.policy_id | ||
name = "${var.perimeter_name}_members" | ||
members = var.members | ||
} | ||
|
||
module "service_perimeter" { | ||
source = "terraform-google-modules/vpc-service-controls/google//modules/regular_service_perimeter" | ||
version = "1.0.1" | ||
|
||
policy = module.access_context_manager_policy.policy_id | ||
perimeter_name = var.perimeter_name | ||
|
||
description = "Perimeter ${var.perimeter_name}" | ||
resources = local.projects | ||
|
||
access_levels = [module.access_level_members.name] | ||
restricted_services = var.restricted_services | ||
|
||
shared_resources = { | ||
all = local.projects | ||
} | ||
} |
Oops, something went wrong.