Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make lasers optional, off by default #528

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/documentation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ jobs:
fail-fast: false
matrix:
module:
- audit-serviceaccount
- authorize-private-service
- bucket-events
- cloudevent-broker
Expand Down
73 changes: 73 additions & 0 deletions modules/audit-serviceaccount/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# `audit-serviceaccount`

This module creates an alert policy to monitor the principals that are
generating tokens for a particular service account.

The set of authorized principals can be enumerated explicitly:
```hcl
module "audit-foo-usage" {
source = "chainguard-dev/common/infra//modules/audit-serviceaccount"

project_id = var.project_id
service-account = google_service_account.foo.email

allowed_principals = [
# Only GKE should generate tokens for this service account.
"serviceAccount:${var.project_id}.svc.id.goog[foo-system/foo]",
]

notification_channels = var.notification_channels
}
```

Or a regular expression can be provided for the allowed principals:
```hcl
module "audit-foo-usage" {
source = "chainguard-dev/common/infra//modules/audit-serviceaccount"

project_id = var.project_id
service-account = google_service_account.foo.email

# Match v1.2.3 style tags on this repository.
allowed_principal_regex = "principal://iam[.]googleapis[.]com/${google_iam_workload_identity_pool.pool.name}/subject/repo:chainguard-dev/terraform-infra-common:ref:refs/tags/v[0-9]+[.][0-9]+[.][0-9]+"

notification_channels = var.notification_channels
}
```


<!-- BEGIN_TF_DOCS -->
## Requirements

No requirements.

## Providers

| Name | Version |
|------|---------|
| <a name="provider_google"></a> [google](#provider\_google) | n/a |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [google_monitoring_alert_policy.generate-access-token](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/monitoring_alert_policy) | resource |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_allowed_principal_regex"></a> [allowed\_principal\_regex](#input\_allowed\_principal\_regex) | A regular expression to match allowed principals. | `string` | `""` | no |
| <a name="input_allowed_principals"></a> [allowed\_principals](#input\_allowed\_principals) | The list of principals authorized to assume this identity. | `list(string)` | `[]` | no |
| <a name="input_notification_channels"></a> [notification\_channels](#input\_notification\_channels) | The list of notification channels to alert when this policy fires. | `list(string)` | n/a | yes |
| <a name="input_project_id"></a> [project\_id](#input\_project\_id) | n/a | `string` | n/a | yes |
| <a name="input_service-account"></a> [service-account](#input\_service-account) | The email of the service account being audited. | `string` | n/a | yes |

## Outputs

No outputs.
<!-- END_TF_DOCS -->
43 changes: 43 additions & 0 deletions modules/audit-serviceaccount/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
resource "google_monitoring_alert_policy" "generate-access-token" {
count = length(var.notification_channels) > 0 ? 1 : 0

# In the absence of data, incident will auto-close after an hour
alert_strategy {
auto_close = "3600s"

notification_rate_limit {
period = "3600s" // re-alert hourly if condition still valid.
}
}

display_name = "Abnormal Access Token Generation: ${var.service-account}"
combiner = "OR"

conditions {
display_name = "Access Token Generation"

condition_matched_log {
filter = <<EOT
logName="projects/${var.project_id}/logs/cloudaudit.googleapis.com%2Fdata_access"
protoPayload.request.name="projects/-/serviceAccounts/${var.service-account}"
protoPayload.serviceName="iamcredentials.googleapis.com"
protoPayload.methodName="GenerateAccessToken"

-- Allow these principals to generate tokens.
${join("\n", [for principal in var.allowed_principals : "-protoPayload.authenticationInfo.principalSubject=\"${principal}\""])}
${var.allowed_principal_regex != "" ? "-protoPayload.authenticationInfo.principalSubject=~\"${var.allowed_principal_regex}\"" : ""}
EOT

label_extractors = {
"email" = "EXTRACT(protoPayload.authenticationInfo.principalEmail)"
"method_name" = "EXTRACT(protoPayload.methodName)"
"user_agent" = "REGEXP_EXTRACT(protoPayload.requestMetadata.callerSuppliedUserAgent, \"(\\\\S+)\")"
}
}
}

notification_channels = var.notification_channels
k4leung4 marked this conversation as resolved.
Show resolved Hide resolved

enabled = "true"
project = var.project_id
}
25 changes: 25 additions & 0 deletions modules/audit-serviceaccount/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
variable "project_id" {
type = string
}

variable "service-account" {
description = "The email of the service account being audited."
type = string
}

variable "allowed_principals" {
description = "The list of principals authorized to assume this identity."
type = list(string)
default = []
}

variable "allowed_principal_regex" {
description = "A regular expression to match allowed principals."
type = string
default = ""
}

variable "notification_channels" {
description = "The list of notification channels to alert when this policy fires."
type = list(string)
}
1 change: 1 addition & 0 deletions modules/bucket-events/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ No requirements.

| Name | Source | Version |
|------|--------|---------|
| <a name="module_audit-delivery-serviceaccount"></a> [audit-delivery-serviceaccount](#module\_audit-delivery-serviceaccount) | ../audit-serviceaccount | n/a |
| <a name="module_authorize-delivery"></a> [authorize-delivery](#module\_authorize-delivery) | ../authorize-private-service | n/a |
| <a name="module_http"></a> [http](#module\_http) | ../dashboard/sections/http | n/a |
| <a name="module_layout"></a> [layout](#module\_layout) | ../dashboard/sections/layout | n/a |
Expand Down
16 changes: 16 additions & 0 deletions modules/bucket-events/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ resource "google_service_account_iam_binding" "allow-pubsub-to-mint-tokens" {
members = ["serviceAccount:${google_project_service_identity.pubsub.email}"]
}

module "audit-delivery-serviceaccount" {
count = length(var.notification_channels) > 0 ? 1 : 0

source = "../audit-serviceaccount"

project_id = var.project_id
service-account = google_service_account.delivery.email

# The absence of authorized identities here means that
# nothing is authorized to act as this service account.
# Note: Cloud Pub/Sub's usage doesn't show up in the
# audit logs.

notification_channels = var.notification_channels
}

module "this" {
source = "../regional-go-service"
project_id = var.project_id
Expand Down
2 changes: 2 additions & 0 deletions modules/cloudevent-recorder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ No requirements.

| Name | Source | Version |
|------|--------|---------|
| <a name="module_audit-import-serviceaccount"></a> [audit-import-serviceaccount](#module\_audit-import-serviceaccount) | ../audit-serviceaccount | n/a |
| <a name="module_recorder-dashboard"></a> [recorder-dashboard](#module\_recorder-dashboard) | ../dashboard/cloudevent-receiver | n/a |
| <a name="module_this"></a> [this](#module\_this) | ../regional-go-service | n/a |
| <a name="module_triggers"></a> [triggers](#module\_triggers) | ../cloudevent-trigger | n/a |
Expand All @@ -107,6 +108,7 @@ No requirements.
| [google_bigquery_table.types](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_table) | resource |
| [google_bigquery_table_iam_binding.import-writes-to-tables](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_table_iam_binding) | resource |
| [google_monitoring_alert_policy.bq_dts](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/monitoring_alert_policy) | resource |
| [google_monitoring_alert_policy.bucket-access](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/monitoring_alert_policy) | resource |
| [google_pubsub_subscription.dead-letter-pull-sub](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_subscription) | resource |
| [google_pubsub_subscription.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_subscription) | resource |
| [google_pubsub_subscription_iam_binding.allow-pubsub-to-ack](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_subscription_iam_binding) | resource |
Expand Down
16 changes: 16 additions & 0 deletions modules/cloudevent-recorder/bigquery.tf
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ resource "google_service_account_iam_binding" "provisioner-acts-as-import-identi
members = [var.provisioner]
}

module "audit-import-serviceaccount" {
count = length(var.notification_channels) > 0 ? 1 : 0

source = "../audit-serviceaccount"

project_id = var.project_id
service-account = google_service_account.import-identity.email

# The absence of authorized identities here means that
# nothing is authorized to act as this service account.
# Note: BigQuery DTS's usage doesn't show up in the
# audit logs.

notification_channels = var.notification_channels
}

// Create a BQ DTS job for each of the regions x types pulling from the appropriate buckets and paths.
resource "google_bigquery_data_transfer_config" "import-job" {
for_each = local.regional-types
Expand Down
67 changes: 67 additions & 0 deletions modules/cloudevent-recorder/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,70 @@ resource "google_storage_bucket" "recorder" {
// What identity is deploying this?
data "google_client_openid_userinfo" "me" {}

resource "google_monitoring_alert_policy" "bucket-access" {
count = length(var.notification_channels) > 0 ? 1 : 0

# In the absence of data, incident will auto-close after an hour
alert_strategy {
auto_close = "3600s"

notification_rate_limit {
period = "3600s" // re-alert hourly if condition still valid.
}
}

display_name = "Abnormal Event Bucket Access: ${var.name}"
combiner = "OR"

conditions {
display_name = "Bucket Access"

condition_matched_log {
filter = <<EOT
logName="projects/${var.project_id}/logs/cloudaudit.googleapis.com%2Fdata_access"
protoPayload.serviceName="storage.googleapis.com"
protoPayload.resourceName=~"projects/_/buckets/${var.name}-(${join("|", keys(var.regions))})-${random_id.suffix.hex}"

-- Exclude things that happen during terraform plan.
-protoPayload.methodName=("storage.buckets.get")

-- Don't alert if someone just opens the bucket list in the UI
-protoPayload.methodName=("storage.managedFolders.list")

-- The recorder service write objects into the bucket.
-(
protoPayload.authenticationInfo.principalEmail="${google_service_account.recorder.email}"
protoPayload.methodName="storage.objects.create"
)

-- The importer identity (used by DTS) enumerates and reads objects.
-(
protoPayload.authenticationInfo.principalEmail="${google_service_account.import-identity.email}"
protoPayload.methodName=("storage.objects.get" OR "storage.objects.list")
)

-- Our CI identity reconciles the bucket.
-(
protoPayload.authenticationInfo.principalEmail="${data.google_client_openid_userinfo.me.email}"
protoPayload.methodName=("storage.getIamPermissions")
)

-- Security scanners frequently probe for public buckets via listing buckets
-- and then getting permissions, so we ignore these even though they pierce
-- the abstraction.
-protoPayload.methodName="storage.getIamPermissions"
EOT

label_extractors = {
"email" = "EXTRACT(protoPayload.authenticationInfo.principalEmail)"
"method_name" = "EXTRACT(protoPayload.methodName)"
"user_agent" = "REGEXP_EXTRACT(protoPayload.requestMetadata.callerSuppliedUserAgent, \"(\\\\S+)\")"
}
}
}

notification_channels = var.notification_channels

enabled = "true"
project = var.project_id
}
1 change: 1 addition & 0 deletions modules/cloudevent-trigger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ No requirements.

| Name | Source | Version |
|------|--------|---------|
| <a name="module_audit-trigger-serviceaccount"></a> [audit-trigger-serviceaccount](#module\_audit-trigger-serviceaccount) | ../audit-serviceaccount | n/a |
| <a name="module_authorize-delivery"></a> [authorize-delivery](#module\_authorize-delivery) | ../authorize-private-service | n/a |

## Resources
Expand Down
16 changes: 16 additions & 0 deletions modules/cloudevent-trigger/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ resource "google_service_account_iam_binding" "allow-pubsub-to-mint-tokens" {
members = ["serviceAccount:${google_project_service_identity.pubsub.email}"]
}

module "audit-trigger-serviceaccount" {
count = length(var.notification_channels) > 0 ? 1 : 0

source = "../audit-serviceaccount"

project_id = var.project_id
service-account = google_service_account.this.email

# The absence of authorized identities here means that
# nothing is authorized to act as this service account.
# Note: Cloud Pub/Sub's usage doesn't show up in the
# audit logs.

notification_channels = var.notification_channels
}

// Authorize this service account to invoke the private service receiving
// events from this trigger.
module "authorize-delivery" {
Expand Down
5 changes: 3 additions & 2 deletions modules/configmap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module "my-configmap" {
EOT

# Optionally: channels to notify if this configuration is manipulated.
notification-channels = [ ... ]
notification_channels = [ ... ]
}

module "foo-service" {
Expand Down Expand Up @@ -77,6 +77,7 @@ No modules.

| Name | Type |
|------|------|
| [google_monitoring_alert_policy.anomalous-secret-access](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/monitoring_alert_policy) | resource |
| [google_secret_manager_secret.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/secret_manager_secret) | resource |
| [google_secret_manager_secret_iam_binding.authorize-access](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/secret_manager_secret_iam_binding) | resource |
| [google_secret_manager_secret_version.data](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/secret_manager_secret_version) | resource |
Expand All @@ -89,7 +90,7 @@ No modules.
|------|-------------|------|---------|:--------:|
| <a name="input_data"></a> [data](#input\_data) | The data to place in the secret. | `string` | n/a | yes |
| <a name="input_name"></a> [name](#input\_name) | The name to give the secret. | `string` | n/a | yes |
| <a name="input_notification-channels"></a> [notification-channels](#input\_notification-channels) | The channels to notify if the configuration data is improperly accessed. | `list(string)` | n/a | yes |
| <a name="input_notification_channels"></a> [notification\_channels](#input\_notification\_channels) | The channels to notify if the configuration data is improperly accessed. | `list(string)` | n/a | yes |
| <a name="input_project_id"></a> [project\_id](#input\_project\_id) | n/a | `string` | n/a | yes |
| <a name="input_service-account"></a> [service-account](#input\_service-account) | The email of the service account that will access the secret. | `string` | n/a | yes |

Expand Down
Loading
Loading