Skip to content

Commit

Permalink
feat: Add Billing Account Log sinks (#1164)
Browse files Browse the repository at this point in the history
  • Loading branch information
renato-rudnicki authored Mar 25, 2024
1 parent be6b808 commit a72d12f
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 0 deletions.
7 changes: 7 additions & 0 deletions 0-bootstrap/sa.tf
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,10 @@ resource "google_billing_account_iam_member" "billing_admin_user" {
google_billing_account_iam_member.tf_billing_user
]
}

resource "google_billing_account_iam_member" "billing_account_sink" {
billing_account_id = var.billing_account
role = "roles/logging.configWriter"
member = "serviceAccount:${google_service_account.terraform-env-sa["org"].email}"
}

1 change: 1 addition & 0 deletions 1-org/envs/shared/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
| Name | Description |
|------|-------------|
| base\_net\_hub\_project\_id | The Base Network hub project ID |
| billing\_sink\_names | The name of the sinks under billing account level. |
| cai\_monitoring\_artifact\_registry | CAI Monitoring Cloud Function Artifact Registry name. |
| cai\_monitoring\_asset\_feed | CAI Monitoring Cloud Function Organization Asset Feed name. |
| cai\_monitoring\_bucket | CAI Monitoring Cloud Function Source Bucket name. |
Expand Down
3 changes: 3 additions & 0 deletions 1-org/envs/shared/log_sinks.tf
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ module "logs_export" {
resources = local.parent_resources
resource_type = local.parent_resource_type
logging_destination_project_id = module.org_audit_logs.project_id
billing_account = local.billing_account
enable_billing_account_sink = true


/******************************************
Send logs to Storage
Expand Down
11 changes: 11 additions & 0 deletions 1-org/envs/shared/org_policy.tf
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ module "restrict_protocol_fowarding" {
IAM
*******************************************/

resource "time_sleep" "wait_logs_export" {
create_duration = "30s"
depends_on = [
module.logs_export
]
}

module "org_domain_restricted_sharing" {
source = "terraform-google-modules/org-policy/google//modules/domain_restricted_sharing"
version = "~> 5.1"
Expand All @@ -98,6 +105,10 @@ module "org_domain_restricted_sharing" {
folder_id = local.folder_id
policy_for = local.policy_for
domains_to_allow = var.domains_to_allow

depends_on = [
time_sleep.wait_logs_export
]
}

/******************************************
Expand Down
5 changes: 5 additions & 0 deletions 1-org/envs/shared/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ output "logs_export_logbucket_name" {
description = "The log bucket for destination of log exports. See https://cloud.google.com/logging/docs/routing/overview#buckets ."
}

output "billing_sink_names" {
value = module.logs_export.billing_sink_names
description = "The name of the sinks under billing account level."
}

output "logs_export_logbucket_linked_dataset_name" {
value = module.logs_export.logbucket_linked_dataset_name
description = "The resource name of the Log Bucket linked BigQuery dataset created for Log Analytics. See https://cloud.google.com/logging/docs/log-analytics ."
Expand Down
3 changes: 3 additions & 0 deletions 1-org/modules/centralized-logging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ module "logging_logbucket" {

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| billing\_account | Billing Account ID used in case sinks are under billing account level. Format 000000-000000-000000. | `string` | `null` | no |
| enable\_billing\_account\_sink | If true, a log router sink will be created for the billing account. The billing\_account variable cannot be null. | `bool` | `false` | no |
| logbucket\_options | Destination LogBucket options:<br>- name: The name of the log bucket to be created and used for log entries matching the filter.<br>- logging\_sink\_name: The name of the log sink to be created.<br>- logging\_sink\_filter: The filter to apply when exporting logs. Only log entries that match the filter are exported. Default is "" which exports all logs.<br>- location: The location of the log bucket. Default: global.<br>- enable\_analytics: Whether or not Log Analytics is enabled. A Log bucket with Log Analytics enabled can be queried in the Log Analytics page using SQL queries. Cannot be disabled once enabled.<br>- linked\_dataset\_id: The ID of the linked BigQuery dataset. A valid link dataset ID must only have alphanumeric characters and underscores within it and have up to 100 characters.<br>- linked\_dataset\_description: A use-friendly description of the linked BigQuery dataset. The maximum length of the description is 8000 characters.<br>- retention\_days: The number of days data should be retained for the log bucket. Default 30. | <pre>object({<br> name = optional(string, null)<br> logging_sink_name = optional(string, null)<br> logging_sink_filter = optional(string, "")<br> location = optional(string, "global")<br> enable_analytics = optional(bool, true)<br> linked_dataset_id = optional(string, null)<br> linked_dataset_description = optional(string, null)<br> retention_days = optional(number, 30)<br> })</pre> | `null` | no |
| logging\_destination\_project\_id | The ID of the project that will have the resources where the logs will be created. | `string` | n/a | yes |
| logging\_project\_key | (Optional) The key of logging destination project if it is inside resources map. It is mandatory when resource\_type = project and logging\_target\_type = logbucket. | `string` | `""` | no |
Expand All @@ -71,6 +73,7 @@ module "logging_logbucket" {

| Name | Description |
|------|-------------|
| billing\_sink\_names | Map of log sink names with billing suffix |
| logbucket\_destination\_name | The resource name for the destination Log Bucket. |
| logbucket\_linked\_dataset\_name | The resource name of the Log Bucket linked BigQuery dataset. |
| pubsub\_destination\_name | The resource name for the destination Pub/Sub. |
Expand Down
80 changes: 80 additions & 0 deletions 1-org/modules/centralized-logging/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ locals {
lbk = try(module.destination_logbucket[0].destination_uri, "")
}

destination_resource_name = merge(
var.pubsub_options != null ? { pub = module.destination_pubsub[0].resource_name } : {},
var.storage_options != null ? { sto = module.destination_storage[0].resource_name } : {},
var.logbucket_options != null ? { lbk = module.destination_logbucket[0].resource_name } : {}
)

logging_tgt_prefix = {
pub = "tp-logs-"
sto = try("bkt-logs-${var.logging_destination_project_id}-", "bkt-logs-")
Expand Down Expand Up @@ -90,6 +96,28 @@ module "log_export" {
include_children = local.include_children
}


module "log_export_billing" {
source = "terraform-google-modules/log-export/google"
version = "~> 7.4"

for_each = var.enable_billing_account_sink ? local.destination_resource_name : {}

destination_uri = local.destination_uri_map[each.key]
filter = ""
log_sink_name = "${coalesce(local.destinations_options[each.key].logging_sink_name, local.logging_sink_name_map[each.key])}-billing-${random_string.suffix.result}"
parent_resource_id = var.billing_account
parent_resource_type = "billing_account"
unique_writer_identity = true
}

resource "time_sleep" "wait_sa_iam_membership" {
create_duration = "30s"
depends_on = [
module.log_export_billing
]
}

#-------------------------#
# Send logs to Log Bucket #
#-------------------------#
Expand Down Expand Up @@ -124,6 +152,25 @@ resource "google_project_iam_member" "logbucket_sink_member" {
member = module.log_export["${each.value}_lbk"].writer_identity
}

#------------------------------------------------------------------#
# Log Bucket Service account IAM membership for log_export_billing #
#------------------------------------------------------------------#
resource "google_project_iam_member" "logbucket_sink_member_billing" {
count = var.enable_billing_account_sink == true && var.logbucket_options != null ? 1 : 0

project = var.logging_destination_project_id
role = "roles/logging.bucketWriter"

# Set permission only on sinks for this destination using
# module.log_export_billing key "<resource>_<dest>"
member = module.log_export_billing["lbk"].writer_identity


depends_on = [
time_sleep.wait_sa_iam_membership
]
}

#----------------------#
# Send logs to Storage #
#----------------------#
Expand Down Expand Up @@ -158,6 +205,22 @@ resource "google_storage_bucket_iam_member" "storage_sink_member" {
member = module.log_export["${each.value}_sto"].writer_identity
}

#---------------------------------------------------------------#
# Storage Service account IAM membership for log_export_billing #
#---------------------------------------------------------------#
resource "google_storage_bucket_iam_member" "storage_sink_member_billing" {
count = var.enable_billing_account_sink == true && var.storage_options != null ? length(var.resources) : 0

bucket = module.destination_storage[0].resource_name
role = "roles/storage.objectCreator"
member = module.log_export_billing["sto"].writer_identity


depends_on = [
google_project_iam_member.logbucket_sink_member_billing
]
}


#----------------------#
# Send logs to Pub\Sub #
Expand Down Expand Up @@ -185,3 +248,20 @@ resource "google_pubsub_topic_iam_member" "pubsub_sink_member" {
role = "roles/pubsub.publisher"
member = module.log_export["${each.value}_pub"].writer_identity
}

#--------------------------------------------------------------#
# Pubsub Service account IAM membership for log_export_billing #
#--------------------------------------------------------------#
resource "google_pubsub_topic_iam_member" "pubsub_sink_member_billing" {
count = var.enable_billing_account_sink && var.pubsub_options != null ? length(var.resources) : 0


project = var.logging_destination_project_id
topic = module.destination_pubsub[0].resource_name
role = "roles/pubsub.publisher"
member = module.log_export_billing["pub"].writer_identity

depends_on = [
google_storage_bucket_iam_member.storage_sink_member_billing
]
}
8 changes: 8 additions & 0 deletions 1-org/modules/centralized-logging/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ output "logbucket_destination_name" {
value = try(module.destination_logbucket[0].resource_name, "")
}

output "billing_sink_names" {
description = "Map of log sink names with billing suffix"
value = {
for key, options in local.destinations_options :
key => "${coalesce(options.logging_sink_name, local.logging_sink_name_map[key])}-billing-${random_string.suffix.result}"
}
}

output "logbucket_linked_dataset_name" {
description = "The resource name of the Log Bucket linked BigQuery dataset."
value = try(module.destination_logbucket[0].linked_dataset_name, "")
Expand Down
12 changes: 12 additions & 0 deletions 1-org/modules/centralized-logging/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ variable "resource_type" {
}
}

variable "billing_account" {
description = "Billing Account ID used in case sinks are under billing account level. Format 000000-000000-000000."
type = string
default = null
}

variable "enable_billing_account_sink" {
description = "If true, a log router sink will be created for the billing account. The billing_account variable cannot be null."
type = bool
default = false
}

variable "logging_project_key" {
description = "(Optional) The key of logging destination project if it is inside resources map. It is mandatory when resource_type = project and logging_target_type = logbucket."
type = string
Expand Down
28 changes: 28 additions & 0 deletions test/integration/org/org_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,34 @@ func TestOrg(t *testing.T) {

}

// Log Sink billing
billingAccount := org.GetTFSetupStringOutput("billing_account")
billingSinkNames := terraform.OutputMap(t, org.GetTFOptions(), "billing_sink_names")
billingLBKSinkName := billingSinkNames["lbk"]
billingPUBSinkName := billingSinkNames["pub"]
billingSTOSinkName := billingSinkNames["sto"]

for _, sinkBilling := range []struct {
name string
destination string
}{
{
name: billingSTOSinkName,
destination: fmt.Sprintf("storage.googleapis.com/%s", logsExportStorageBucketName),
},
{
name: billingLBKSinkName,
destination: fmt.Sprintf("logging.googleapis.com/%s", logBktFullName),
},
{
name: billingPUBSinkName,
destination: fmt.Sprintf("pubsub.googleapis.com/projects/%s/topics/%s", auditLogsProjectID, logsExportTopicName),
},
} {
logSinkBilling := gcloud.Runf(t, "logging sinks describe %s --billing-account %s", sinkBilling.name, billingAccount)
assert.Equal(sinkBilling.destination, logSinkBilling.Get("destination").String(), fmt.Sprintf("sink %s should have destination %s", sinkBilling.name, sinkBilling.destination))
}

// hub and spoke infrastructure
enable_hub_and_spoke, err := strconv.ParseBool(bootstrap.GetTFSetupStringOutput("enable_hub_and_spoke"))
require.NoError(t, err)
Expand Down
6 changes: 6 additions & 0 deletions test/setup/iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ resource "google_billing_account_iam_member" "tf_billing_user" {
member = "serviceAccount:${google_service_account.int_test.email}"
}

resource "google_billing_account_iam_member" "billing_account_log_config" {
billing_account_id = var.billing_account
role = "roles/logging.configWriter"
member = "serviceAccount:${google_service_account.int_test.email}"
}

resource "google_service_account" "int_test" {
project = module.project.project_id
account_id = "ci-account"
Expand Down

0 comments on commit a72d12f

Please sign in to comment.