Skip to content

Commit

Permalink
New tool: Firewall Enforcer (GoogleCloudPlatform#644)
Browse files Browse the repository at this point in the history
* add firewall enforcer

* Add license header

* tighten up .gitignore

* simplify logging

* ignore deletes

* add license & minor formatting

* fmt terraform

Co-authored-by: Thomas Ruble <[email protected]>
Co-authored-by: Abdel SGHIOUAR <[email protected]>
  • Loading branch information
3 people authored Jul 1, 2021
1 parent 70c4223 commit 9fed93f
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 1 deletion.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ The tools folder contains ready-made utilities which can simplify Google Cloud P
* [CUD Prioritized Attribution](tools/cuds-prioritized-attribution) - A tool that allows GCP customers who purchased Committed Use Discounts (CUDs) to prioritize a specific scope (e.g. project or folder) to attribute CUDs first before letting any unconsumed discount float to other parts of an organization.
* [Custom Role Manager](tools/custom-role-manager) - Manages organization- or project-level custom roles by combining predefined roles and including and removing permissions with wildcards. Can run as Cloud Function or output Terraform resources.
* [DNS Sync](tools/dns-sync) - Sync a Cloud DNS zone with GCE resources. Instances and load balancers are added to the cloud DNS zone as they start from compute_engine_activity log events sent from a pub/sub push subscription. Can sync multiple projects to a single Cloud DNS zone.
* [GCE Disk Encryption Converter](tools/gce-google-keys-to-cmek) - A tool that converts disks attached to a GCE VM instance from Google-managed keys to a customer-managed key stored in Cloud KMS.
* [Firewall Enforcer](tools/firewal-enforcer) - Automatically watch & remove illegal firewall rules across organization. Firewall rules are monitored by a Cloud Asset Inventory Feed, which trigger a Cloud Function that inspects the firewall rule and deletes it if it fails a test.
* [GCE Disk Encryption Converter](tools/gce-google-keys-to-cmek) - A tool that converts disks attached to a GCE VM instnace from Google-managed keys to a customer-managed key stored in Cloud KMS.
* [GCE Quota Sync](tools/gce-quota-sync) - A tool that fetches resource quota usage from the GCE API and synchronizes it to Stackdriver as a custom metric, where it can be used to define automated alerts.
* [GCE Usage Log](tools/gce-usage-log) - Collect GCE instance events into a BigQuery dataset, surfacing your vCPUs, RAM, and Persistent Disk, sliced by project, zone, and labels.
* [GCP Architecture Visualizer](https://github.com/forseti-security/forseti-visualizer) - A tool that takes CSV output from a Forseti Inventory scan and draws out a dynamic hierarchical tree diagram of org -> folders -> projects -> gcp_resources using the D3.js javascript library.
Expand Down
3 changes: 3 additions & 0 deletions tools/firewall-enforcer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.terraform*
*.tfstate*
index.zip
6 changes: 6 additions & 0 deletions tools/firewall-enforcer/.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
org_id = "99999999"
project_id = "PROJECT"
region = "us-central1"
zone = "us-central1-f"
feed_id = "org_ai_feed_firewall"
function_sa_name = "firewall-enforcer"
27 changes: 27 additions & 0 deletions tools/firewall-enforcer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Firewall Enforcer

Automatic firewall removal. This tool watches all new firewall rules, and if a rule has an illegal pattern, it gets deleted immediately.

# To deploy

Fill out the `.tfvars` file, and then apply the Terraform:

```bash
terraform init
terraform apply -var-file .tfvars
```

This will deploy:

- Asset Inventory Feed
- Cloud Function
- PubSub Topic
- Org IAM for the Cloud Function to perform firewall deletion (roles/compute.securityAdmin)

# Design

A Cloud Asset Inventory Feed watches for newly created firewall rules. Each creation will trigger a Cloud Function that inspects the firewall rule. If the rule has an illegal pattern, the Cloud Function will log it & delete it.

# Configuration

Implement the function in `main.py`: `should_delete(fw_rule: dict) -> bool`. The current reference implementation will return `True` for any firewall rule that allows `0.0.0.0/0` in its source IP range.
60 changes: 60 additions & 0 deletions tools/firewall-enforcer/function/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright 2021 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.
# Copyright 2021 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.
import base64
import json
import re
from googleapiclient.discovery import build


def main(event, ctx):
if 'data' not in event:
raise

payload = base64.b64decode(event['data']).decode('utf-8')
payload = json.loads(payload)

print(payload)
data = payload['asset']['resource']['data']

if should_delete(data):
delete_rule(data)


def should_delete(data) -> bool:
# Hook for deciding whether to delete a firewall rule

# Do the sourceRanges allow all IPs
return any('/0' in source_range for source_range in data['sourceRanges'])


def delete_rule(data):
self_link = data['selfLink']
project = re.search('projects/([\w-]+)/', self_link).group(1)
firewall = re.search('firewalls/([\w-]+)', self_link).group(1)
svc = build('compute', 'v1')
svc.firewalls().delete(project=project, firewall=firewall).execute()
print(f'Deleted firewall: {self_link}')
1 change: 1 addition & 0 deletions tools/firewall-enforcer/function/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
google-api-python-client==1.12.8
3 changes: 3 additions & 0 deletions tools/firewall-enforcer/function/samples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# These are sample events that come in from the asset inventory feed
create_ingress_allow_all = {'asset': {'ancestors': ['projects/163454223397', 'organizations/673763744309'], 'assetType': 'compute.googleapis.com/Firewall', 'name': '//compute.googleapis.com/projects/sc-vice-test/global/firewalls/foobar', 'resource': {'data': {'allowed': [{'IPProtocol': 'all'}], 'creationTimestamp': '2021-04-23T08:01:13.062-07:00', 'description': '', 'direction': 'INGRESS', 'disabled': False, 'id': '6001901033355173846', 'logConfig': {'enable': False}, 'name': 'foobar', 'network': 'https://www.googleapis.com/compute/v1/projects/sc-vice-test/global/networks/default', 'priority': 1000, 'selfLink': 'https://www.googleapis.com/compute/v1/projects/sc-vice-test/global/firewalls/foobar', 'sourceRanges': ['0.0.0.0/0']}, 'discoveryDocumentUri': 'https://www.googleapis.com/discovery/v1/apis/compute/v1/rest', 'discoveryName': 'Firewall', 'location': 'global', 'parent': '//cloudresourcemanager.googleapis.com/projects/163454223397', 'version': 'v1'}, 'updateTime': '2021-04-23T15:01:13.214753Z'}, 'priorAssetState': 'DOES_NOT_EXIST', 'window': {'startTime': '2021-04-23T15:01:13.214753Z'}}
create_ingress_allow_some = {'asset': {'ancestors': ['projects/163454223397', 'organizations/673763744309'], 'assetType': 'compute.googleapis.com/Firewall', 'name': '//compute.googleapis.com/projects/sc-vice-test/global/firewalls/allow-some', 'resource': {'data': {'allowed': [{'IPProtocol': 'tcp', 'ports': ['99']}, {'IPProtocol': 'udp', 'ports': ['88']}], 'creationTimestamp': '2021-04-23T08:04:23.615-07:00', 'description': '', 'direction': 'INGRESS', 'disabled': False, 'id': '6303844746161774360', 'logConfig': {'enable': False}, 'name': 'allow-some', 'network': 'https://www.googleapis.com/compute/v1/projects/sc-vice-test/global/networks/default', 'priority': 1000, 'selfLink': 'https://www.googleapis.com/compute/v1/projects/sc-vice-test/global/firewalls/allow-some', 'sourceRanges': ['0.0.0.0/0']}, 'discoveryDocumentUri': 'https://www.googleapis.com/discovery/v1/apis/compute/v1/rest', 'discoveryName': 'Firewall', 'location': 'global', 'parent': '//cloudresourcemanager.googleapis.com/projects/163454223397', 'version': 'v1'}, 'updateTime': '2021-04-23T15:04:23.874385Z'}, 'priorAssetState': 'DOES_NOT_EXIST', 'window': {'startTime': '2021-04-23T15:04:23.874385Z'}}
153 changes: 153 additions & 0 deletions tools/firewall-enforcer/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Copyright 2021 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.

# Create a feed that sends notifications about compute instance updates under a
# particular organization.
resource "google_cloud_asset_organization_feed" "main" {
provider = google-beta
billing_project = var.project_id
org_id = var.org_id
feed_id = var.feed_id
content_type = "RESOURCE"

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

condition {
expression = "!temporal_asset.deleted"
title = "Not deleted"
description = "Send notification only when created"
}

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

depends_on = [
google_project_service.cloudasset
]
}

# Allow the publishing role to the Cloud Asset service account of the project that
# was used for sending the notifications.
resource "google_pubsub_topic_iam_member" "cloud_asset_writer" {
project = var.project_id
topic = google_pubsub_topic.feed_output.id
role = "roles/pubsub.publisher"
member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-cloudasset.iam.gserviceaccount.com"

# Wait for the permission to be ready on the destination topic.
depends_on = [
google_cloud_asset_organization_feed.main
]
}

# The topic where the resource change notifications will be sent.
resource "google_pubsub_topic" "feed_output" {
project = var.project_id
name = var.feed_id
depends_on = [
google_project_service.pubsub,
google_project_service.rm
]
}

# Bucket to hold function code
resource "google_storage_bucket" "bucket" {
name = "${var.project_id}-fw"
uniform_bucket_level_access = true
}

# zip up cloud function
data "archive_file" "function_archive" {
type = "zip"
source_dir = "./function/"
output_path = "./function/index.zip"
}

# function source code
resource "google_storage_bucket_object" "archive" {
name = "${data.archive_file.function_archive.output_md5}-index"
bucket = google_storage_bucket.bucket.name
source = data.archive_file.function_archive.output_path
content_disposition = "attachment"
content_encoding = "gzip"
content_type = "application/zip"
}

# service account that runs cloud function
resource "google_service_account" "function" {
account_id = var.function_sa_name
display_name = var.function_sa_name
}

resource "google_cloudfunctions_function" "function" {
name = "firewall-enforcer"
description = "Deletes firewall rules"
runtime = "python39"
service_account_email = google_service_account.function.email
source_archive_bucket = google_storage_bucket.bucket.name
source_archive_object = google_storage_bucket_object.archive.name
entry_point = "main"
event_trigger {
event_type = "google.pubsub.topic.publish"
resource = google_pubsub_topic.feed_output.id
}
}

# Find the project number of the project whose identity will be used for sending
# the asset change notifications.
data "google_project" "project" {
project_id = var.project_id
}

resource "google_project_service" "cloudasset" {
project = var.project_id
service = "cloudasset.googleapis.com"
}

resource "google_project_service" "functions" {
project = var.project_id
service = "cloudfunctions.googleapis.com"
}

resource "google_project_service" "pubsub" {
project = var.project_id
service = "pubsub.googleapis.com"
}

resource "google_project_service" "build" {
project = var.project_id
service = "cloudbuild.googleapis.com"
}

resource "google_project_service" "rm" {
project = var.project_id
service = "cloudresourcemanager.googleapis.com"
}

resource "google_organization_iam_member" "function" {
org_id = var.org_id
member = "serviceAccount:${google_service_account.function.email}"
role = "roles/compute.securityAdmin"
}

resource "google_project_iam_member" "function_logger" {
project = var.project_id
member = "serviceAccount:${google_service_account.function.email}"
role = "roles/logging.logWriter"
}
33 changes: 33 additions & 0 deletions tools/firewall-enforcer/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright 2021 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.
*/

# must set billing_project & user_project_override
# for cloud asset api to work via terraform
provider "google" {
project = var.project_id
billing_project = var.project_id
region = var.region
# zone = var.zone
user_project_override = true
}

provider "google-beta" {
project = var.project_id
billing_project = var.project_id
region = var.region
# zone = var.zone
user_project_override = true
}
37 changes: 37 additions & 0 deletions tools/firewall-enforcer/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2021 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 "org_id" {
type = string
description = "Numeric organization ID"
}
variable "project_id" {
type = string
description = "Project ID to deploy Function into"
}
variable "region" {
type = string
description = "Region for deploying Function"
}
variable "zone" {
type = string
}
variable "feed_id" {
type = string
description = "Name of the Asset Inventory Feed"
default = "org_ai_feed_firewall"
}
variable "function_sa_name" {
type = string
description = "Name of the service account the Cloud Function runs as"
}

0 comments on commit 9fed93f

Please sign in to comment.