Skip to content

Commit

Permalink
VPC Flow Logs enforcer (GoogleCloudPlatform#619)
Browse files Browse the repository at this point in the history
* Added VPC Flow Logs enforcer Cloud Function

* Fixed pylint errors

* Fixed last pylint errros

* Fix last pylint error

* Fix for empty CAI messages + update from legacy to new audit logs

* Applying yapf format

* Restructuring cloud function code
  • Loading branch information
apsureda authored Mar 10, 2021
1 parent bfc956a commit 8808720
Show file tree
Hide file tree
Showing 19 changed files with 827 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ creates relationships between assets and outputs a format compatible with [graph
* [Snowflake_to_BQ](tools/snowflake2bq/) - A shell script to transfer tables (schema & data) from Snowflake to BigQuery.
* [STS Job Manager](tools/sts-job-manager/) - A petabyte-scale bucket migration tool utilizing [Storage Transfer Service](https://cloud.google.com/storage-transfer-service)
* [VPC Flow Logs Analysis](tools/vpc-flowlogs-analysis) - A configurable Sink + BigQuery report that shows traffic grouped by target IP address ranges.
* [VPC Flow Logs Enforcer](tools/vpc-flowlogs-enforcer) - A Cloud Function that will automatically enable VPC Flow Logs when a subnet is created or modified in any project under a particular folder or folders.
* [VM Migrator](tools/vm-migrator) - This utility automates migrating Virtual Machine instances within GCP. You can migrate VM's from one zone to another zone/region within the same project or different projects while retaining all the original VM properties like disks, network interfaces, ip, metadata, network tags and much more.
* [Webhook Ingestion Data Pipeline](tools/webhook-ingestion-pipeline) - A deployable app to accept and ingest unauthenticated webhook data to BigQuery.
* [gmon](tools/gmon/) - A command-line interface (CLI) for Cloud Monitoring written in Python.
Expand Down
77 changes: 77 additions & 0 deletions tools/vpc-flowlogs-enforcer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# VPC Flow logs enforcement via Cloud Function

## Description

This sample code shows how a Cloud Function can be used to enforce VPC Flow logs in all the networks under a particular folder. The Cloud Function will listen on a Pub/Sub topic for notifications about chenges in subnets. The notifications can be configured in two ways:

1. By creating a log sink that will filter all the network change events under the chosen folders and send them to a Pub Sub topic. This can be enabled using the `configure_log_sinks` variable.
2. By creating Cloud Asset Inventry feeds that will send ntifications to a Pub Sub topic when a subnet is modified under a particular folder. This can be enabled using the `configure_asset_feeds` variable. Currently, the Cloud Asset Inventory feeds cannot be configured via Terraform. The terraform configuration will create shell scripts to create and delete the feeds.

You should only enable one of the two options to avoid duplicate operations. If you enable both, you will see hat the asset inventory notifications are received a few seconds before the stackdriver based ones. You will probably want to use these in production, but I have included both options for demonstration purposes.

The Terraform code included in this example creates the following resources:

* A GCP project where all the resources will be located.
* A pub Sub topic where the subnet change events will be published by the log sink or the inventory feed.
* A cloud function that listens to subnet change notifications and makes sure VPC flow logs are activated.
* If enabled, it will create the log sinks that will send notifications each time a subnet is modified.
* If enabled, it will create two shell scripts for creating and deleting the asset inventory feeds (not yet supported by terraform).
* Creates all the necessary permissions:
* For the Cloud Function to be able to modify the subnets under the folders being monitored.
* For the log sin service accounts to publish notifications in the Pub Sub topic.
* For the Cloud Asset Inventory service account to publish notifications in the Pub Sub topic.

## Setup instructions

This terraform code uses service account impersionation to authenticate in the GCP APIs. The reasons for this are:

1. It is recommended to only grant high level permissions to one service account, and allow impersonating this SA to specific users who will need to run the terraform code. This way individual users will not need to have excessive permissions.
2. By always using a service account it is simpler to configure fine-grained permissions to run terraform code.
3. By using impersonation instead of just a service account key we eliminate the private key exfiltration risk.
4. By using impersonation instead of just a service account key we can see in the logs who executed the terraform code, even if the authentication was made via a SA key (the `serviceAccountDelegationInfo` attribute in the logs contain the email of the user who impersonated the SA).

In order to run this terraform code, you will need to:

1. Create a service account in the main project and grant it the necessary permissions. You will need:
* If using CAI feeds, you will need the Cloud Asset Viewer role at the organization level, or above the folders you want to monitor.
* If using log sinks, you will need the Logs Configuration Writer role at the organization level, or above the folders you want to monitor.
* Project Creator and Billing account User roles, if you want to creat ethe project using terraform. If you are using an existing project, you will need to grant the service account the project Editor or Owner role.
2. Identify the team members who will need to run teh terraform code, and grant them the `roles/iam.serviceAccountTokenCreator` role.
3. Edit the `terraform.tfvars` files and replace the value of the `terraform_service_account` variable with the email of your service account.

The setup is quite straightforward:

1. Decide on which which folders in your organization you want to enfoce VPC flow logs and take note of those folder IDs.
2. Choose a name for your demo project and a folder where you want to place it.
3. Decide the VPC flow logs configuration you want to apply to your networks. See [here](https://cloud.google.com/compute/docs/reference/rest/v1/subnetworks) for the options.
4. Decide if you want to use asset inventory feeds or log sinks for the subnet change notifications.
5. Edit the `terraform.tf` file with your configuration options.
6. Apply the terraform configuration.
7. If you chose to use asset inventory feeds, make sure you run terraform using a service account. The Cloud Asset Inventory API requires being invoked using a service account.

## Testing locally

You can test the cloud function locally using the sample logs provided. To do this, you will first need to configure you rpython environment. While in this filder (vpc_log_enforcer), use [virtualenv](https://virtualenv.pypa.io/en/latest/) to set up a python3 environment:

```
virtualenv --python python3 env
source env/bin/activate
pip3 install -r terraform/templates/cloud_function/requirements.txt
```

Run the cloud funtion using any of the sample logs provided (or create your own):

```
python3 terraform/templates/cloud_function/main.py sample_logs/insert_subnet_call_last.json
```

sample output:

```
reading sample message from: sample_logs/insert_subnet_call_last.json
/Users/alpalacios/Workspaces/RNLT/PSO_Repo/vpc_log_enforcer/env/lib/python3.6/site-packages/google/auth/_default.py:69: UserWarning: Your application has authenticated using end user credentials from Google Cloud SDK. We recommend that most server applications use service accounts instead. If your application continues to use end user credentials from Cloud SDK, you might receive a "quota exceeded" or "API not enabled" error. For more information about service accounts, see https://cloud.google.com/docs/authentication/
warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)
got subnet change notification from stackdriver
enabling flow logs in subnetwork /projects/apszaz-crf-minutis-prod/regions/europe-west4/subnetworks/dummy.
flow logs successfully enabled in subnetwork /projects/apszaz-crf-minutis-prod/regions/europe-west4/subnetworks/dummy.
```
45 changes: 45 additions & 0 deletions tools/vpc-flowlogs-enforcer/sample_logs/asset_inventory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"asset": {
"ancestors": [
"projects/717196263256",
"folders/1094373516899",
"folders/273647969471",
"organizations/116143322321"
],
"assetType": "compute.googleapis.com/Subnetwork",
"name": "//compute.googleapis.com/projects/apszaz-shsvc-services-5af0da/regions/asia-east1/subnetworks/dummy",
"resource": {
"data": {
"creationTimestamp": "2020-05-11T01:02:09.269-07:00",
"description": "",
"enableFlowLogs": false,
"fingerprint": "0/V6UwWr3n4=",
"gatewayAddress": "10.0.7.1",
"id": "8070357952081737838",
"ipCidrRange": "10.0.7.0/24",
"logConfig": {
"aggregationInterval": "INTERVAL_5_SEC",
"enable": true,
"flowSampling": 0.75,
"metadata": "INCLUDE_ALL_METADATA"
},
"name": "dummy",
"network": "https://www.googleapis.com/compute/v1/projects/apszaz-shsvc-services-5af0da/global/networks/dummy",
"privateIpGoogleAccess": true,
"privateIpv6GoogleAccess": "DISABLE_GOOGLE_ACCESS",
"purpose": "PRIVATE",
"region": "https://www.googleapis.com/compute/v1/projects/apszaz-shsvc-services-5af0da/regions/asia-east1",
"selfLink": "https://www.googleapis.com/compute/v1/projects/apszaz-shsvc-services-5af0da/regions/asia-east1/subnetworks/dummy"
},
"discoveryDocumentUri": "https://www.googleapis.com/discovery/v1/apis/compute/v1/rest",
"discoveryName": "Subnetwork",
"location": "asia-east1",
"parent": "//cloudresourcemanager.googleapis.com/projects/717196263256",
"version": "v1"
},
"updateTime": "2020-05-11T08:44:29.328882Z"
},
"window": {
"startTime": "2020-05-11T08:44:29.328882Z"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"protoPayload": {
"@type": "type.googleapis.com/google.cloud.audit.AuditLog",
"authenticationInfo": {
"principalEmail": "alfonso@apszaz.com"
},
"requestMetadata": {
"callerIp": "82.64.56.220",
"callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.58 Safari/537.36,gzip(gfe),gzip(gfe)",
"requestAttributes": {
"time": "2021-02-26T06:06:38.713021Z",
"reason": "8uSywAYQGg5Db2xpc2V1bSBGbG93cw",
"auth": {}
},
"destinationAttributes": {}
},
"serviceName": "compute.googleapis.com",
"methodName": "v1.compute.subnetworks.insert",
"authorizationInfo": [
{
"permission": "compute.subnetworks.create",
"granted": true,
"resourceAttributes": {
"service": "compute",
"name": "projects/apszaz-shsvc-services-5af0da/regions/asia-east1/subnetworks/dummy",
"type": "compute.subnetworks"
}
},
{
"permission": "compute.networks.updatePolicy",
"granted": true,
"resourceAttributes": {
"service": "compute",
"name": "projects/apszaz-shsvc-services-5af0da/global/networks/dummy",
"type": "compute.networks"
}
}
],
"resourceName": "projects/apszaz-shsvc-services-5af0da/regions/asia-east1/subnetworks/dummy",
"request": {
"ipCidrRange": "10.0.1.0/24",
"privateIpGoogleAccess": false,
"@type": "type.googleapis.com/compute.subnetworks.insert",
"name": "dummy",
"enableFlowLogs": false,
"description": "",
"network": "projects/apszaz-shsvc-services-5af0da/global/networks/dummy",
"region": "projects/apszaz-shsvc-services-5af0da/regions/asia-east1"
},
"response": {
"user": "alfonso@apszaz.com",
"operationType": "insert",
"name": "operation-1614319596411-5bc3712191b9f-b52b323b-a8235fcc",
"@type": "type.googleapis.com/operation",
"insertTime": "2021-02-25T22:06:38.487-08:00",
"selfLink": "https://www.googleapis.com/compute/v1/projects/apszaz-shsvc-services-5af0da/regions/asia-east1/operations/operation-1614319596411-5bc3712191b9f-b52b323b-a8235fcc",
"selfLinkWithId": "https://www.googleapis.com/compute/v1/projects/apszaz-shsvc-services-5af0da/regions/asia-east1/operations/8394696394217435393",
"status": "RUNNING",
"region": "https://www.googleapis.com/compute/v1/projects/apszaz-shsvc-services-5af0da/regions/asia-east1",
"targetLink": "https://www.googleapis.com/compute/v1/projects/apszaz-shsvc-services-5af0da/regions/asia-east1/subnetworks/dummy",
"targetId": "4811090641529680129",
"id": "8394696394217435393",
"startTime": "2021-02-25T22:06:38.495-08:00",
"progress": "0"
},
"resourceLocation": {
"currentLocations": [
"asia-east1"
]
}
},
"insertId": "-4fkj6ed1h10",
"resource": {
"type": "gce_subnetwork",
"labels": {
"subnetwork_name": "dummy",
"location": "asia-east1",
"project_id": "apszaz-shsvc-services-5af0da",
"subnetwork_id": "4811090641529680129"
}
},
"timestamp": "2021-02-26T06:06:36.446815Z",
"severity": "NOTICE",
"logName": "projects/apszaz-shsvc-services-5af0da/logs/cloudaudit.googleapis.com%2Factivity",
"operation": {
"id": "operation-1614319596411-5bc3712191b9f-b52b323b-a8235fcc",
"producer": "compute.googleapis.com",
"first": true
},
"receiveTimestamp": "2021-02-26T06:06:39.239420333Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"protoPayload": {
"@type": "type.googleapis.com/google.cloud.audit.AuditLog",
"authenticationInfo": {
"principalEmail": "alfonso@apszaz.com"
},
"requestMetadata": {
"callerIp": "82.64.56.220",
"callerSuppliedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.58 Safari/537.36,gzip(gfe),gzip(gfe)"
},
"serviceName": "compute.googleapis.com",
"methodName": "v1.compute.subnetworks.insert",
"resourceName": "projects/apszaz-shsvc-services-5af0da/regions/asia-east1/subnetworks/dummy",
"request": {
"@type": "type.googleapis.com/compute.subnetworks.insert"
}
},
"insertId": "-num1c4cfzy",
"resource": {
"type": "gce_subnetwork",
"labels": {
"subnetwork_id": "4811090641529680129",
"subnetwork_name": "dummy",
"location": "asia-east1",
"project_id": "apszaz-shsvc-services-5af0da"
}
},
"timestamp": "2021-02-26T06:06:52.654074Z",
"severity": "NOTICE",
"logName": "projects/apszaz-shsvc-services-5af0da/logs/cloudaudit.googleapis.com%2Factivity",
"operation": {
"id": "operation-1614319596411-5bc3712191b9f-b52b323b-a8235fcc",
"producer": "compute.googleapis.com",
"last": true
},
"receiveTimestamp": "2021-02-26T06:06:53.190459199Z"
}
29 changes: 29 additions & 0 deletions tools/vpc-flowlogs-enforcer/terraform/cai_feeds.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2021 Google LLC. This software is provided as-is, without warranty
* or representation for any use or purpose. Your use of it is subject to your
* agreement with Google.
*/

# Create a feed that sends notifications about subnetwork resource updates under the
# chosen folders.
resource "google_cloud_asset_folder_feed" "subnet_change" {
count = length(var.enforcement_folders) * (var.configure_asset_feeds ? 1 : 0)
billing_project = google_project.demo_project.project_id
folder = var.enforcement_folders[count.index]
feed_id = "subnet_change"
content_type = "RESOURCE"

asset_types = [
"compute.googleapis.com/Subnetwork",
]

feed_output_config {
pubsub_destination {
topic = google_pubsub_topic.subnet_change.id
}
}

depends_on = [
google_project_service.demo_project["cloudasset.googleapis.com"],
]
}
58 changes: 58 additions & 0 deletions tools/vpc-flowlogs-enforcer/terraform/cloud_function.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2021 Google LLC. This software is provided as-is, without warranty
* or representation for any use or purpose. Your use of it is subject to your
* agreement with Google.
*/
resource "google_cloudfunctions_function" "net_logs" {
project = google_project.demo_project.project_id
name = "net_logs"
entry_point = "main"
description = "Enables network logs whenever a subnet is created or modified."
runtime = "python37"
region = "europe-west1"

service_account_email = google_service_account.net_logs_cf.email
source_archive_bucket = google_storage_bucket.cloud_function.name
source_archive_object = google_storage_bucket_object.cloud_function.name
event_trigger {
event_type = "providers/cloud.pubsub/eventTypes/topic.publish"
resource = google_pubsub_topic.subnet_change.id
failure_policy {
# enable retry, since the function may be called before the subnet is ready
# to be updated.
retry = true
}
}

environment_variables = {
LOG_CONFIG = jsonencode(var.log_config)
}

depends_on = [google_project_service.demo_project]
}

# Push the zip file containing the cloud function to the bucket
resource "google_storage_bucket_object" "cloud_function" {
name = "net_logs.zip"
source = data.archive_file.cloud_function.output_path
bucket = google_storage_bucket.cloud_function.name
}

# GCS bucket to store the clouf function code
resource "google_storage_bucket" "cloud_function" {
name = "${google_project.demo_project.project_id}-cf${local.suffix_dash}"
project = google_project.demo_project.project_id
location = var.region
uniform_bucket_level_access = true
force_destroy = true
storage_class = "REGIONAL"
depends_on = [google_project_service.demo_project]
}

# Create a ZIP file with the cloud function. This file will be uploaded to a
# GCS bucket, so it can be published as a Cloud function.
data "archive_file" "cloud_function" {
type = "zip"
output_path = "templates/cloud_function.zip"
source_dir = "templates/cloud_function"
}
18 changes: 18 additions & 0 deletions tools/vpc-flowlogs-enforcer/terraform/common.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2021 Google LLC. This software is provided as-is, without warranty
* or representation for any use or purpose. Your use of it is subject to your
* agreement with Google.
*/

locals {
suffix = var.random_suffix == "false" ? "" : "_${random_id.this.hex}"
suffix_dash = var.random_suffix == "false" ? "" : "-${random_id.this.hex}"
}

# If enabled in the config parameters, this random suffix will be added to
# resources that cannot be deleted and recreated with the same name right away.
# Some resources, like projects, GCS buckets and KMS keys are deleted only
# after a grece period. For test purposes, this random suffix can be useful.
resource "random_id" "this" {
byte_length = 2
}
Loading

0 comments on commit 8808720

Please sign in to comment.