Merge pull request #180 from terraform-google-modules/new-sinks-2
Improving log sinks
This commit is contained in:
commit
b41e2b4b63
|
@ -3,7 +3,8 @@
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
- add support for creating logging sinks and logging exclusions in the `project`, `folder` and `organization` modules
|
- **incompatible change** removed the `logging-sinks` module. Logging sinks can now be created the `logging_sinks` variable in the in the `project`, `folder` and `organization` modules
|
||||||
|
- add support for creating logging exclusions in the `project`, `folder` and `organization` modules
|
||||||
- add support for Confidential Compute to `compute-vm` module
|
- add support for Confidential Compute to `compute-vm` module
|
||||||
|
|
||||||
## [4.2.0] - 2020-11-25
|
## [4.2.0] - 2020-11-25
|
||||||
|
|
|
@ -33,7 +33,7 @@ The current list of modules supports most of the core foundational and networkin
|
||||||
|
|
||||||
Currently available modules:
|
Currently available modules:
|
||||||
|
|
||||||
- **foundational** - [folder](./modules/folder), [log sinks](./modules/logging-sinks), [organization](./modules/organization), [project](./modules/project), [service accounts](./modules/iam-service-account)
|
- **foundational** - [folder](./modules/folder), [organization](./modules/organization), [project](./modules/project), [service accounts](./modules/iam-service-account)
|
||||||
- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [VPN HA](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/cloudenpoints)
|
- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [VPN HA](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/cloudenpoints)
|
||||||
- **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [GKE cluster](./modules/gke-cluster), [GKE nodepool](./modules/gke-nodepool), [COS container](./modules/cos-container) (coredns, mysql, onprem, squid)
|
- **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [GKE cluster](./modules/gke-cluster), [GKE nodepool](./modules/gke-nodepool), [COS container](./modules/cos-container) (coredns, mysql, onprem, squid)
|
||||||
- **data** - [GCS](./modules/gcs), [BigQuery dataset](./modules/bigquery-dataset), [Pub/Sub](./modules/pubsub), [Datafusion](./modules/datafusion), [Bigtable instance](./modules/bigtable-instance)
|
- **data** - [GCS](./modules/gcs), [BigQuery dataset](./modules/bigquery-dataset), [Pub/Sub](./modules/pubsub), [Datafusion](./modules/datafusion), [Bigtable instance](./modules/bigtable-instance)
|
||||||
|
|
|
@ -14,6 +14,19 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
locals {
|
||||||
|
logging_sinks = {
|
||||||
|
audit-logs = {
|
||||||
|
type = "bigquery"
|
||||||
|
destination = module.audit-dataset.id
|
||||||
|
filter = var.audit_filter
|
||||||
|
iam = true
|
||||||
|
include_children = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root_node_type = split("/", var.root_node)[0]
|
||||||
|
}
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Terraform top-level resources #
|
# Terraform top-level resources #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -99,8 +112,7 @@ module "audit-project" {
|
||||||
prefix = var.prefix
|
prefix = var.prefix
|
||||||
billing_account = var.billing_account_id
|
billing_account = var.billing_account_id
|
||||||
iam = {
|
iam = {
|
||||||
"roles/bigquery.dataEditor" = [module.audit-log-sinks.writer_identities[0]]
|
"roles/viewer" = var.iam_audit_viewers
|
||||||
"roles/viewer" = var.iam_audit_viewers
|
|
||||||
}
|
}
|
||||||
services = concat(var.project_services, [
|
services = concat(var.project_services, [
|
||||||
"bigquery.googleapis.com",
|
"bigquery.googleapis.com",
|
||||||
|
@ -122,16 +134,22 @@ module "audit-dataset" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module "audit-log-sinks" {
|
# uncomment the next two modules to create the logging sinks
|
||||||
source = "../../modules/logging-sinks"
|
|
||||||
parent = var.root_node
|
# module "root_org" {
|
||||||
destinations = {
|
# count = local.root_node_type == "organizations" ? 1 : 0
|
||||||
audit-logs = "bigquery.googleapis.com/${module.audit-dataset.id}"
|
# source = "../../modules/organization"
|
||||||
}
|
# organization_id = var.root_node
|
||||||
sinks = {
|
# logging_sinks = local.logging_sinks
|
||||||
audit-logs = var.audit_filter
|
# }
|
||||||
}
|
|
||||||
}
|
# module "root_folder" {
|
||||||
|
# count = local.root_node_type == "folders" ? 1 : 0
|
||||||
|
# source = "../../modules/folder"
|
||||||
|
# id = var.root_node
|
||||||
|
# folder_create = false
|
||||||
|
# logging_sinks = local.logging_sinks
|
||||||
|
# }
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Shared resources (GCR, GCS, KMS, etc.) #
|
# Shared resources (GCR, GCS, KMS, etc.) #
|
||||||
|
|
|
@ -36,4 +36,14 @@ locals {
|
||||||
||
|
||
|
||||||
substr(var.root_node, 0, 13) == "organizations"
|
substr(var.root_node, 0, 13) == "organizations"
|
||||||
)
|
)
|
||||||
|
logging_sinks = {
|
||||||
|
audit-logs = {
|
||||||
|
type = "bigquery"
|
||||||
|
destination = module.audit-dataset.id
|
||||||
|
filter = var.audit_filter
|
||||||
|
iam = true
|
||||||
|
include_children = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root_node_type = split("/", var.root_node)[0]
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,8 +105,7 @@ module "audit-project" {
|
||||||
prefix = var.prefix
|
prefix = var.prefix
|
||||||
billing_account = var.billing_account_id
|
billing_account = var.billing_account_id
|
||||||
iam = {
|
iam = {
|
||||||
"roles/bigquery.dataEditor" = [module.audit-log-sinks.writer_identities[0]]
|
"roles/viewer" = var.iam_audit_viewers
|
||||||
"roles/viewer" = var.iam_audit_viewers
|
|
||||||
}
|
}
|
||||||
services = concat(var.project_services, [
|
services = concat(var.project_services, [
|
||||||
"bigquery.googleapis.com",
|
"bigquery.googleapis.com",
|
||||||
|
@ -128,16 +127,23 @@ module "audit-dataset" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module "audit-log-sinks" {
|
# uncomment the next two modules to create the logging sinks
|
||||||
source = "../../modules/logging-sinks"
|
|
||||||
parent = var.root_node
|
# module "root_org" {
|
||||||
destinations = {
|
# count = local.root_node_type == "organizations" ? 1 : 0
|
||||||
audit-logs = "bigquery.googleapis.com/${module.audit-dataset.id}"
|
# source = "../../modules/organization"
|
||||||
}
|
# organization_id = var.root_node
|
||||||
sinks = {
|
# logging_sinks = local.logging_sinks
|
||||||
audit-logs = var.audit_filter
|
# }
|
||||||
}
|
|
||||||
}
|
# module "root_folder" {
|
||||||
|
# count = local.root_node_type == "folders" ? 1 : 0
|
||||||
|
# source = "../../modules/folder"
|
||||||
|
# id = var.root_node
|
||||||
|
# folder_create = false
|
||||||
|
# logging_sinks = local.logging_sinks
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Shared resources (GCR, GCS, KMS, etc.) #
|
# Shared resources (GCR, GCS, KMS, etc.) #
|
||||||
|
|
|
@ -11,7 +11,6 @@ Specific modules also offer support for non-authoritative bindings (e.g. `google
|
||||||
## Foundational modules
|
## Foundational modules
|
||||||
|
|
||||||
- [folder](./folder)
|
- [folder](./folder)
|
||||||
- [log sinks](./logging-sinks)
|
|
||||||
- [organization](./organization)
|
- [organization](./organization)
|
||||||
- [project](./project)
|
- [project](./project)
|
||||||
- [service account](./iam-service-account)
|
- [service account](./iam-service-account)
|
||||||
|
|
|
@ -69,22 +69,25 @@ module "folder-sink" {
|
||||||
name = "my-folder"
|
name = "my-folder"
|
||||||
logging_sinks = {
|
logging_sinks = {
|
||||||
warnings = {
|
warnings = {
|
||||||
type = "gcs"
|
type = "gcs"
|
||||||
destination = module.gcs.name
|
destination = module.gcs.name
|
||||||
filter = "severity=WARNING"
|
filter = "severity=WARNING"
|
||||||
grant = false
|
iam = false
|
||||||
|
include_children = true
|
||||||
}
|
}
|
||||||
info = {
|
info = {
|
||||||
type = "bigquery"
|
type = "bigquery"
|
||||||
destination = module.dataset.id
|
destination = module.dataset.id
|
||||||
filter = "severity=INFO"
|
filter = "severity=INFO"
|
||||||
grant = false
|
iam = false
|
||||||
|
include_children = true
|
||||||
}
|
}
|
||||||
notice = {
|
notice = {
|
||||||
type = "pubsub"
|
type = "pubsub"
|
||||||
destination = module.pubsub.id
|
destination = module.pubsub.id
|
||||||
filter = "severity=NOTICE"
|
filter = "severity=NOTICE"
|
||||||
grant = true
|
iam = true
|
||||||
|
include_children = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logging_exclusions = {
|
logging_exclusions = {
|
||||||
|
@ -147,7 +150,7 @@ module "folder2" {
|
||||||
| *iam* | IAM bindings in {ROLE => [MEMBERS]} format. | <code title="map(set(string))">map(set(string))</code> | | <code title="">{}</code> |
|
| *iam* | IAM bindings in {ROLE => [MEMBERS]} format. | <code title="map(set(string))">map(set(string))</code> | | <code title="">{}</code> |
|
||||||
| *id* | Folder ID in case you use folder_create=false | <code title="">string</code> | | <code title="">null</code> |
|
| *id* | Folder ID in case you use folder_create=false | <code title="">string</code> | | <code title="">null</code> |
|
||||||
| *logging_exclusions* | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code title="map(string)">map(string)</code> | | <code title="">{}</code> |
|
| *logging_exclusions* | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code title="map(string)">map(string)</code> | | <code title="">{}</code> |
|
||||||
| *logging_sinks* | Logging sinks to create for this folder. | <code title="map(object({ destination = string type = string filter = string grant = bool }))">map(object({...}))</code> | | <code title="">{}</code> |
|
| *logging_sinks* | Logging sinks to create for this folder. | <code title="map(object({ destination = string type = string filter = string iam = bool include_children = bool }))">map(object({...}))</code> | | <code title="">{}</code> |
|
||||||
| *name* | Folder name. | <code title="">string</code> | | <code title="">null</code> |
|
| *name* | Folder name. | <code title="">string</code> | | <code title="">null</code> |
|
||||||
| *parent* | Parent in folders/folder_id or organizations/org_id format. | <code title="">string</code> | | <code title="null validation { condition = var.parent == null || can(regex("(organizations|folders)/[0-9]+", var.parent)) error_message = "Parent must be of the form folders/folder_id or organizations/organization_id." }">...</code> |
|
| *parent* | Parent in folders/folder_id or organizations/org_id format. | <code title="">string</code> | | <code title="null validation { condition = var.parent == null || can(regex("(organizations|folders)/[0-9]+", var.parent)) error_message = "Parent must be of the form folders/folder_id or organizations/organization_id." }">...</code> |
|
||||||
| *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | <code title="map(bool)">map(bool)</code> | | <code title="">{}</code> |
|
| *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | <code title="map(bool)">map(bool)</code> | | <code title="">{}</code> |
|
||||||
|
|
|
@ -38,7 +38,7 @@ locals {
|
||||||
type => {
|
type => {
|
||||||
for name, sink in local.logging_sinks :
|
for name, sink in local.logging_sinks :
|
||||||
name => sink
|
name => sink
|
||||||
if sink.grant && sink.type == type
|
if sink.iam && sink.type == type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
folder = (
|
folder = (
|
||||||
|
@ -188,9 +188,10 @@ resource "google_logging_folder_sink" "sink" {
|
||||||
for_each = local.logging_sinks
|
for_each = local.logging_sinks
|
||||||
name = each.key
|
name = each.key
|
||||||
#description = "${each.key} (Terraform-managed)"
|
#description = "${each.key} (Terraform-managed)"
|
||||||
folder = local.folder.name
|
folder = local.folder.name
|
||||||
destination = "${local.sink_type_destination[each.value.type]}/${each.value.destination}"
|
destination = "${local.sink_type_destination[each.value.type]}/${each.value.destination}"
|
||||||
filter = each.value.filter
|
filter = each.value.filter
|
||||||
|
include_children = each.value.include_children
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_storage_bucket_iam_binding" "gcs-sinks-binding" {
|
resource "google_storage_bucket_iam_binding" "gcs-sinks-binding" {
|
||||||
|
@ -216,13 +217,6 @@ resource "google_pubsub_topic_iam_binding" "pubsub-sinks-binding" {
|
||||||
members = [google_logging_folder_sink.sink[each.key].writer_identity]
|
members = [google_logging_folder_sink.sink[each.key].writer_identity]
|
||||||
}
|
}
|
||||||
|
|
||||||
# resource "google_storage_bucket_iam_binding" "gcs-sinks-bindings" {
|
|
||||||
# for_each = local.sink_grants["gcs"]
|
|
||||||
# bucket = each.value.destination
|
|
||||||
# role = "roles/storage.objectCreator"
|
|
||||||
# members = [google_logging_folder_sink.sink[each.key].writer_identity]
|
|
||||||
# }
|
|
||||||
|
|
||||||
resource "google_logging_folder_exclusion" "logging-exclusion" {
|
resource "google_logging_folder_exclusion" "logging-exclusion" {
|
||||||
for_each = coalesce(var.logging_exclusions, {})
|
for_each = coalesce(var.logging_exclusions, {})
|
||||||
name = each.key
|
name = each.key
|
||||||
|
|
|
@ -78,10 +78,11 @@ variable "firewall_policy_attachments" {
|
||||||
variable "logging_sinks" {
|
variable "logging_sinks" {
|
||||||
description = "Logging sinks to create for this folder."
|
description = "Logging sinks to create for this folder."
|
||||||
type = map(object({
|
type = map(object({
|
||||||
destination = string
|
destination = string
|
||||||
type = string
|
type = string
|
||||||
filter = string
|
filter = string
|
||||||
grant = bool
|
iam = bool
|
||||||
|
include_children = bool
|
||||||
}))
|
}))
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
# Terraform Logging Sinks Module
|
|
||||||
|
|
||||||
This module allows easy creation of one or more logging sinks.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
<!-- BEGIN TFDOC -->
|
|
||||||
## Variables
|
|
||||||
|
|
||||||
| name | description | type | required | default |
|
|
||||||
|---|---|:---: |:---:|:---:|
|
|
||||||
| destinations | Map of destinations by sink name. | <code title="map(string)">map(string)</code> | ✓ | |
|
|
||||||
| parent | Resource where the sink will be created, eg 'organizations/nnnnnnnn'. | <code title="">string</code> | ✓ | |
|
|
||||||
| sinks | Map of sink name / sink filter. | <code title="map(string)">map(string)</code> | ✓ | |
|
|
||||||
| *default_options* | Default options used for sinks where no specific options are set. | <code title="object({ bigquery_partitioned_tables = bool include_children = bool unique_writer_identity = bool })">object({...})</code> | | <code title="{ bigquery_partitioned_tables = true include_children = true unique_writer_identity = false }">...</code> |
|
|
||||||
| *sink_options* | Optional map of sink name / sink options. If no options are specified for a sink defaults will be used. | <code title="map(object({ bigquery_partitioned_tables = bool include_children = bool unique_writer_identity = bool }))">map(object({...}))</code> | | <code title="">{}</code> |
|
|
||||||
|
|
||||||
## Outputs
|
|
||||||
|
|
||||||
| name | description | sensitive |
|
|
||||||
|---|---|:---:|
|
|
||||||
| names | Log sink names. | |
|
|
||||||
| sinks | Log sink resources. | |
|
|
||||||
| writer_identities | Log sink writer identities. | |
|
|
||||||
<!-- END TFDOC -->
|
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2020 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
locals {
|
|
||||||
bigquery_destinations = {
|
|
||||||
for name, destination in var.destinations :
|
|
||||||
name => substr(destination, 0, 8) == "bigquery"
|
|
||||||
}
|
|
||||||
resource_type = element(split("/", var.parent), 0)
|
|
||||||
resource_id = element(split("/", var.parent), 1)
|
|
||||||
sink_options = {
|
|
||||||
for name, _ in var.sinks :
|
|
||||||
name => lookup(var.sink_options, name, var.default_options)
|
|
||||||
}
|
|
||||||
sink_resources = concat(
|
|
||||||
[for _, sink in google_logging_organization_sink.sinks : sink],
|
|
||||||
[for _, sink in google_logging_billing_account_sink.sinks : sink],
|
|
||||||
[for _, sink in google_logging_folder_sink.sinks : sink],
|
|
||||||
[for _, sink in google_logging_project_sink.sinks : sink],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_logging_organization_sink" "sinks" {
|
|
||||||
for_each = local.resource_type == "organizations" ? var.sinks : {}
|
|
||||||
name = each.key
|
|
||||||
org_id = local.resource_id
|
|
||||||
filter = each.value
|
|
||||||
destination = var.destinations[each.key]
|
|
||||||
include_children = local.sink_options[each.key].include_children
|
|
||||||
dynamic bigquery_options {
|
|
||||||
for_each = local.bigquery_destinations[each.key] ? ["1"] : []
|
|
||||||
iterator = config
|
|
||||||
content {
|
|
||||||
use_partitioned_tables = local.sink_options[each.key].bigquery_partitioned_tables
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_logging_billing_account_sink" "sinks" {
|
|
||||||
for_each = local.resource_type == "billing_accounts" ? var.sinks : {}
|
|
||||||
name = each.key
|
|
||||||
billing_account = local.resource_id
|
|
||||||
filter = each.value
|
|
||||||
destination = var.destinations[each.key]
|
|
||||||
dynamic bigquery_options {
|
|
||||||
for_each = local.bigquery_destinations[each.key] ? ["1"] : []
|
|
||||||
iterator = config
|
|
||||||
content {
|
|
||||||
use_partitioned_tables = local.sink_options[each.key].bigquery_partitioned_tables
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_logging_folder_sink" "sinks" {
|
|
||||||
for_each = local.resource_type == "folders" ? var.sinks : {}
|
|
||||||
name = each.key
|
|
||||||
folder = var.parent
|
|
||||||
filter = each.value
|
|
||||||
destination = var.destinations[each.key]
|
|
||||||
include_children = local.sink_options[each.key].include_children
|
|
||||||
dynamic bigquery_options {
|
|
||||||
for_each = local.bigquery_destinations[each.key] ? ["1"] : []
|
|
||||||
iterator = config
|
|
||||||
content {
|
|
||||||
use_partitioned_tables = local.sink_options[each.key].bigquery_partitioned_tables
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_logging_project_sink" "sinks" {
|
|
||||||
for_each = local.resource_type == "projects" ? var.sinks : {}
|
|
||||||
name = each.key
|
|
||||||
project = local.resource_id
|
|
||||||
filter = each.value
|
|
||||||
destination = var.destinations[each.key]
|
|
||||||
unique_writer_identity = local.sink_options[each.key].unique_writer_identity
|
|
||||||
dynamic bigquery_options {
|
|
||||||
for_each = local.bigquery_destinations[each.key] ? ["1"] : []
|
|
||||||
iterator = config
|
|
||||||
content {
|
|
||||||
use_partitioned_tables = local.sink_options[each.key].bigquery_partitioned_tables
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2020 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
output "sinks" {
|
|
||||||
description = "Log sink resources."
|
|
||||||
value = local.sink_resources
|
|
||||||
}
|
|
||||||
|
|
||||||
output "names" {
|
|
||||||
description = "Log sink names."
|
|
||||||
value = [for sink in local.sink_resources : sink.name]
|
|
||||||
}
|
|
||||||
|
|
||||||
output "writer_identities" {
|
|
||||||
description = "Log sink writer identities."
|
|
||||||
value = [for sink in local.sink_resources : sink.writer_identity]
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2020 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 "default_options" {
|
|
||||||
description = "Default options used for sinks where no specific options are set."
|
|
||||||
type = object({
|
|
||||||
bigquery_partitioned_tables = bool
|
|
||||||
include_children = bool
|
|
||||||
unique_writer_identity = bool
|
|
||||||
})
|
|
||||||
default = {
|
|
||||||
bigquery_partitioned_tables = true
|
|
||||||
include_children = true
|
|
||||||
unique_writer_identity = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "destinations" {
|
|
||||||
description = "Map of destinations by sink name."
|
|
||||||
type = map(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "parent" {
|
|
||||||
description = "Resource where the sink will be created, eg 'organizations/nnnnnnnn'."
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "sink_options" {
|
|
||||||
description = "Optional map of sink name / sink options. If no options are specified for a sink defaults will be used."
|
|
||||||
type = map(object({
|
|
||||||
bigquery_partitioned_tables = bool
|
|
||||||
include_children = bool
|
|
||||||
unique_writer_identity = bool
|
|
||||||
}))
|
|
||||||
default = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "sinks" {
|
|
||||||
description = "Map of sink name / sink filter."
|
|
||||||
type = map(string)
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2020 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
terraform {
|
|
||||||
required_version = ">= 0.12.6"
|
|
||||||
}
|
|
|
@ -86,22 +86,25 @@ module "org" {
|
||||||
|
|
||||||
logging_sinks = {
|
logging_sinks = {
|
||||||
warnings = {
|
warnings = {
|
||||||
type = "gcs"
|
type = "gcs"
|
||||||
destination = module.gcs.name
|
destination = module.gcs.name
|
||||||
filter = "severity=WARNING"
|
filter = "severity=WARNING"
|
||||||
grant = false
|
iam = false
|
||||||
|
include_children = true
|
||||||
}
|
}
|
||||||
info = {
|
info = {
|
||||||
type = "bigquery"
|
type = "bigquery"
|
||||||
destination = module.dataset.id
|
destination = module.dataset.id
|
||||||
filter = "severity=INFO"
|
filter = "severity=INFO"
|
||||||
grant = false
|
iam = false
|
||||||
|
include_children = true
|
||||||
}
|
}
|
||||||
notice = {
|
notice = {
|
||||||
type = "pubsub"
|
type = "pubsub"
|
||||||
destination = module.pubsub.id
|
destination = module.pubsub.id
|
||||||
filter = "severity=NOTICE"
|
filter = "severity=NOTICE"
|
||||||
grant = true
|
iam = true
|
||||||
|
include_children = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logging_exclusions = {
|
logging_exclusions = {
|
||||||
|
@ -126,7 +129,7 @@ module "org" {
|
||||||
| *iam_additive_members* | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code title="map(list(string))">map(list(string))</code> | | <code title="">{}</code> |
|
| *iam_additive_members* | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code title="map(list(string))">map(list(string))</code> | | <code title="">{}</code> |
|
||||||
| *iam_audit_config* | Service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service. | <code title="map(map(list(string)))">map(map(list(string)))</code> | | <code title="">{}</code> |
|
| *iam_audit_config* | Service audit logging configuration. Service as key, map of log permission (eg DATA_READ) and excluded members as value for each service. | <code title="map(map(list(string)))">map(map(list(string)))</code> | | <code title="">{}</code> |
|
||||||
| *logging_exclusions* | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code title="map(string)">map(string)</code> | | <code title="">{}</code> |
|
| *logging_exclusions* | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code title="map(string)">map(string)</code> | | <code title="">{}</code> |
|
||||||
| *logging_sinks* | Logging sinks to create for this organization. | <code title="map(object({ destination = string type = string filter = string grant = bool }))">map(object({...}))</code> | | <code title="">{}</code> |
|
| *logging_sinks* | Logging sinks to create for this organization. | <code title="map(object({ destination = string type = string filter = string iam = bool include_children = bool }))">map(object({...}))</code> | | <code title="">{}</code> |
|
||||||
| *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | <code title="map(bool)">map(bool)</code> | | <code title="">{}</code> |
|
| *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | <code title="map(bool)">map(bool)</code> | | <code title="">{}</code> |
|
||||||
| *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | <code title="map(object({ inherit_from_parent = bool suggested_value = string status = bool values = list(string) }))">map(object({...}))</code> | | <code title="">{}</code> |
|
| *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | <code title="map(object({ inherit_from_parent = bool suggested_value = string status = bool values = list(string) }))">map(object({...}))</code> | | <code title="">{}</code> |
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ locals {
|
||||||
type => {
|
type => {
|
||||||
for name, sink in local.logging_sinks :
|
for name, sink in local.logging_sinks :
|
||||||
name => sink
|
name => sink
|
||||||
if sink.grant && sink.type == type
|
if sink.iam && sink.type == type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,9 +221,10 @@ resource "google_logging_organization_sink" "sink" {
|
||||||
for_each = local.logging_sinks
|
for_each = local.logging_sinks
|
||||||
name = each.key
|
name = each.key
|
||||||
#description = "${each.key} (Terraform-managed)"
|
#description = "${each.key} (Terraform-managed)"
|
||||||
org_id = local.organization_id_numeric
|
org_id = local.organization_id_numeric
|
||||||
destination = "${local.sink_type_destination[each.value.type]}/${each.value.destination}"
|
destination = "${local.sink_type_destination[each.value.type]}/${each.value.destination}"
|
||||||
filter = each.value.filter
|
filter = each.value.filter
|
||||||
|
include_children = each.value.include_children
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_storage_bucket_iam_binding" "gcs-sinks-binding" {
|
resource "google_storage_bucket_iam_binding" "gcs-sinks-binding" {
|
||||||
|
@ -249,13 +250,6 @@ resource "google_pubsub_topic_iam_binding" "pubsub-sinks-binding" {
|
||||||
members = [google_logging_organization_sink.sink[each.key].writer_identity]
|
members = [google_logging_organization_sink.sink[each.key].writer_identity]
|
||||||
}
|
}
|
||||||
|
|
||||||
# resource "google_storage_bucket_iam_binding" "gcs-sinks-bindings" {
|
|
||||||
# for_each = local.sink_grants["gcs"]
|
|
||||||
# bucket = each.value.destination
|
|
||||||
# role = "roles/storage.objectCreator"
|
|
||||||
# members = [google_logging_organization_sink.sink[each.key].writer_identity]
|
|
||||||
# }
|
|
||||||
|
|
||||||
resource "google_logging_organization_exclusion" "logging-exclusion" {
|
resource "google_logging_organization_exclusion" "logging-exclusion" {
|
||||||
for_each = coalesce(var.logging_exclusions, {})
|
for_each = coalesce(var.logging_exclusions, {})
|
||||||
name = each.key
|
name = each.key
|
||||||
|
|
|
@ -102,10 +102,11 @@ variable "firewall_policy_attachments" {
|
||||||
variable "logging_sinks" {
|
variable "logging_sinks" {
|
||||||
description = "Logging sinks to create for this organization."
|
description = "Logging sinks to create for this organization."
|
||||||
type = map(object({
|
type = map(object({
|
||||||
destination = string
|
destination = string
|
||||||
type = string
|
type = string
|
||||||
filter = string
|
filter = string
|
||||||
grant = bool
|
iam = bool
|
||||||
|
include_children = bool
|
||||||
}))
|
}))
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,19 +115,19 @@ module "project-host" {
|
||||||
type = "gcs"
|
type = "gcs"
|
||||||
destination = module.gcs.name
|
destination = module.gcs.name
|
||||||
filter = "severity=WARNING"
|
filter = "severity=WARNING"
|
||||||
grant = false
|
iam = false
|
||||||
}
|
}
|
||||||
info = {
|
info = {
|
||||||
type = "bigquery"
|
type = "bigquery"
|
||||||
destination = module.dataset.id
|
destination = module.dataset.id
|
||||||
filter = "severity=INFO"
|
filter = "severity=INFO"
|
||||||
grant = false
|
iam = false
|
||||||
}
|
}
|
||||||
notice = {
|
notice = {
|
||||||
type = "pubsub"
|
type = "pubsub"
|
||||||
destination = module.pubsub.id
|
destination = module.pubsub.id
|
||||||
filter = "severity=NOTICE"
|
filter = "severity=NOTICE"
|
||||||
grant = true
|
iam = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logging_exclusions = {
|
logging_exclusions = {
|
||||||
|
@ -153,7 +153,7 @@ module "project-host" {
|
||||||
| *labels* | Resource labels. | <code title="map(string)">map(string)</code> | | <code title="">{}</code> |
|
| *labels* | Resource labels. | <code title="map(string)">map(string)</code> | | <code title="">{}</code> |
|
||||||
| *lien_reason* | If non-empty, creates a project lien with this description. | <code title="">string</code> | | <code title=""></code> |
|
| *lien_reason* | If non-empty, creates a project lien with this description. | <code title="">string</code> | | <code title=""></code> |
|
||||||
| *logging_exclusions* | Logging exclusions for this project in the form {NAME -> FILTER}. | <code title="map(string)">map(string)</code> | | <code title="">{}</code> |
|
| *logging_exclusions* | Logging exclusions for this project in the form {NAME -> FILTER}. | <code title="map(string)">map(string)</code> | | <code title="">{}</code> |
|
||||||
| *logging_sinks* | Logging sinks to create for this project. | <code title="map(object({ destination = string type = string filter = string grant = bool }))">map(object({...}))</code> | | <code title="">{}</code> |
|
| *logging_sinks* | Logging sinks to create for this project. | <code title="map(object({ destination = string type = string filter = string iam = bool }))">map(object({...}))</code> | | <code title="">{}</code> |
|
||||||
| *oslogin* | Enable OS Login. | <code title="">bool</code> | | <code title="">false</code> |
|
| *oslogin* | Enable OS Login. | <code title="">bool</code> | | <code title="">false</code> |
|
||||||
| *oslogin_admins* | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
| *oslogin_admins* | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
||||||
| *oslogin_users* | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
| *oslogin_users* | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code title="list(string)">list(string)</code> | | <code title="">[]</code> |
|
||||||
|
|
|
@ -50,7 +50,7 @@ locals {
|
||||||
type => {
|
type => {
|
||||||
for name, sink in local.logging_sinks :
|
for name, sink in local.logging_sinks :
|
||||||
name => sink
|
name => sink
|
||||||
if sink.grant && sink.type == type
|
if sink.iam && sink.type == type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,13 +291,6 @@ resource "google_pubsub_topic_iam_binding" "pubsub-sinks-binding" {
|
||||||
members = [google_logging_project_sink.sink[each.key].writer_identity]
|
members = [google_logging_project_sink.sink[each.key].writer_identity]
|
||||||
}
|
}
|
||||||
|
|
||||||
# resource "google_storage_bucket_iam_binding" "gcs-sinks-bindings" {
|
|
||||||
# for_each = local.sink_grants["gcs"]
|
|
||||||
# bucket = each.value.destination
|
|
||||||
# role = "roles/storage.objectCreator"
|
|
||||||
# members = [google_logging_project_sink.sink[each.key].writer_identity]
|
|
||||||
# }
|
|
||||||
|
|
||||||
resource "google_logging_project_exclusion" "logging-exclusion" {
|
resource "google_logging_project_exclusion" "logging-exclusion" {
|
||||||
for_each = coalesce(var.logging_exclusions, {})
|
for_each = coalesce(var.logging_exclusions, {})
|
||||||
name = each.key
|
name = each.key
|
||||||
|
|
|
@ -172,7 +172,7 @@ variable "logging_sinks" {
|
||||||
destination = string
|
destination = string
|
||||||
type = string
|
type = string
|
||||||
filter = string
|
filter = string
|
||||||
grant = bool
|
iam = bool
|
||||||
}))
|
}))
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,12 +26,12 @@ BASEDIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
def _plan_runner():
|
def _plan_runner():
|
||||||
"Returns a function to run Terraform plan on a fixture."
|
"Returns a function to run Terraform plan on a fixture."
|
||||||
|
|
||||||
def run_plan(fixture_path, targets=None, **tf_vars):
|
def run_plan(fixture_path, targets=None, refresh=True, **tf_vars):
|
||||||
"Runs Terraform plan and returns parsed output."
|
"Runs Terraform plan and returns parsed output."
|
||||||
tf = tftest.TerraformTest(fixture_path, BASEDIR,
|
tf = tftest.TerraformTest(fixture_path, BASEDIR,
|
||||||
os.environ.get('TERRAFORM', 'terraform'))
|
os.environ.get('TERRAFORM', 'terraform'))
|
||||||
tf.setup()
|
tf.setup()
|
||||||
return tf.plan(output=True, tf_vars=tf_vars, targets=targets)
|
return tf.plan(output=True, refresh=refresh, tf_vars=tf_vars, targets=targets)
|
||||||
|
|
||||||
return run_plan
|
return run_plan
|
||||||
|
|
||||||
|
@ -54,9 +54,9 @@ def plan_runner(_plan_runner):
|
||||||
def e2e_plan_runner(_plan_runner):
|
def e2e_plan_runner(_plan_runner):
|
||||||
"Returns a function to run Terraform plan on an end-to-end fixture."
|
"Returns a function to run Terraform plan on an end-to-end fixture."
|
||||||
|
|
||||||
def run_plan(fixture_path, targets=None, **tf_vars):
|
def run_plan(fixture_path, targets=None, refresh=True, **tf_vars):
|
||||||
"Runs Terraform plan on an end-to-end module using defaults, returns data."
|
"Runs Terraform plan on an end-to-end module using defaults, returns data."
|
||||||
plan = _plan_runner(fixture_path, targets=targets, **tf_vars)
|
plan = _plan_runner(fixture_path, targets=targets, refresh=refresh, **tf_vars)
|
||||||
# skip the fixture
|
# skip the fixture
|
||||||
root_module = plan.root_module['child_modules'][0]
|
root_module = plan.root_module['child_modules'][0]
|
||||||
modules = dict((mod['address'], mod['resources'])
|
modules = dict((mod['address'], mod['resources'])
|
||||||
|
|
|
@ -23,5 +23,5 @@ FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
|
||||||
def test_resources(e2e_plan_runner):
|
def test_resources(e2e_plan_runner):
|
||||||
"Test that plan works and the numbers of resources is as expected."
|
"Test that plan works and the numbers of resources is as expected."
|
||||||
modules, resources = e2e_plan_runner(FIXTURES_DIR)
|
modules, resources = e2e_plan_runner(FIXTURES_DIR)
|
||||||
assert len(modules) == 9
|
assert len(modules) == 8
|
||||||
assert len(resources) == 84
|
assert len(resources) == 82
|
||||||
|
|
|
@ -22,7 +22,7 @@ FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
|
||||||
|
|
||||||
def test_folder_roles(e2e_plan_runner):
|
def test_folder_roles(e2e_plan_runner):
|
||||||
"Test folder roles."
|
"Test folder roles."
|
||||||
modules, _ = e2e_plan_runner(FIXTURES_DIR)
|
modules, _ = e2e_plan_runner(FIXTURES_DIR, refresh=False)
|
||||||
for env in ['test', 'prod']:
|
for env in ['test', 'prod']:
|
||||||
resources = modules[f'module.test.module.environment-folders["{env}"]']
|
resources = modules[f'module.test.module.environment-folders["{env}"]']
|
||||||
folders = [r for r in resources if r['type'] == 'google_folder']
|
folders = [r for r in resources if r['type'] == 'google_folder']
|
||||||
|
@ -41,7 +41,7 @@ def test_org_roles(e2e_plan_runner):
|
||||||
'organization_id': 'organizations/123',
|
'organization_id': 'organizations/123',
|
||||||
'iam_xpn_config': '{grant = true, target_org = true}'
|
'iam_xpn_config': '{grant = true, target_org = true}'
|
||||||
}
|
}
|
||||||
modules, _ = e2e_plan_runner(FIXTURES_DIR, **tf_vars)
|
modules, _ = e2e_plan_runner(FIXTURES_DIR, refresh=False, **tf_vars)
|
||||||
for env in ['test', 'prod']:
|
for env in ['test', 'prod']:
|
||||||
resources = modules[f'module.test.module.environment-folders["{env}"]']
|
resources = modules[f'module.test.module.environment-folders["{env}"]']
|
||||||
folder_bindings = [r['index']
|
folder_bindings = [r['index']
|
||||||
|
|
|
@ -23,4 +23,6 @@ module "test" {
|
||||||
policy_list = var.policy_list
|
policy_list = var.policy_list
|
||||||
firewall_policies = var.firewall_policies
|
firewall_policies = var.firewall_policies
|
||||||
firewall_policy_attachments = var.firewall_policy_attachments
|
firewall_policy_attachments = var.firewall_policy_attachments
|
||||||
|
logging_sinks = var.logging_sinks
|
||||||
|
logging_exclusions = var.logging_exclusions
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,3 +53,19 @@ variable "firewall_policy_attachments" {
|
||||||
type = map(string)
|
type = map(string)
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "logging_sinks" {
|
||||||
|
type = map(object({
|
||||||
|
destination = string
|
||||||
|
type = string
|
||||||
|
filter = string
|
||||||
|
iam = bool
|
||||||
|
include_children = bool
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "logging_exclusions" {
|
||||||
|
type = map(string)
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
# Copyright 2020 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 os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
|
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
|
||||||
|
|
||||||
|
|
||||||
|
def test_sinks(plan_runner):
|
||||||
|
"Test folder-level sinks."
|
||||||
|
logging_sinks = """ {
|
||||||
|
warning = {
|
||||||
|
type = "gcs"
|
||||||
|
destination = "mybucket"
|
||||||
|
filter = "severity=WARNING"
|
||||||
|
iam = true
|
||||||
|
include_children = true
|
||||||
|
}
|
||||||
|
info = {
|
||||||
|
type = "bigquery"
|
||||||
|
destination = "projects/myproject/datasets/mydataset"
|
||||||
|
filter = "severity=INFO"
|
||||||
|
iam = true
|
||||||
|
include_children = true
|
||||||
|
}
|
||||||
|
notice = {
|
||||||
|
type = "pubsub"
|
||||||
|
destination = "projects/myproject/topics/mytopic"
|
||||||
|
filter = "severity=NOTICE"
|
||||||
|
iam = true
|
||||||
|
include_children = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
_, resources = plan_runner(FIXTURES_DIR, logging_sinks=logging_sinks)
|
||||||
|
assert len(resources) == 7
|
||||||
|
|
||||||
|
resource_types = Counter([r['type'] for r in resources])
|
||||||
|
assert resource_types == {
|
||||||
|
'google_bigquery_dataset_iam_binding': 1,
|
||||||
|
'google_folder': 1,
|
||||||
|
'google_logging_folder_sink': 3,
|
||||||
|
'google_pubsub_topic_iam_binding': 1,
|
||||||
|
'google_storage_bucket_iam_binding': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sinks = [r for r in resources
|
||||||
|
if r['type'] == 'google_logging_folder_sink']
|
||||||
|
assert sorted([r['index'] for r in sinks]) == [
|
||||||
|
'info',
|
||||||
|
'notice',
|
||||||
|
'warning',
|
||||||
|
]
|
||||||
|
values = [(r['index'], r['values']['filter'], r['values']['destination'],
|
||||||
|
r['values']['include_children'])
|
||||||
|
for r in sinks]
|
||||||
|
assert sorted(values) == [
|
||||||
|
('info',
|
||||||
|
'severity=INFO',
|
||||||
|
'bigquery.googleapis.com/projects/myproject/datasets/mydataset',
|
||||||
|
True),
|
||||||
|
('notice',
|
||||||
|
'severity=NOTICE',
|
||||||
|
'pubsub.googleapis.com/projects/myproject/topics/mytopic',
|
||||||
|
False),
|
||||||
|
('warning', 'severity=WARNING', 'storage.googleapis.com/mybucket', True)]
|
||||||
|
|
||||||
|
bindings = [r for r in resources
|
||||||
|
if 'binding' in r['type']]
|
||||||
|
values = [(r['index'], r['type'], r['values']['role'])
|
||||||
|
for r in bindings]
|
||||||
|
assert sorted(values) == [
|
||||||
|
('info', 'google_bigquery_dataset_iam_binding', 'roles/bigquery.dataEditor'),
|
||||||
|
('notice', 'google_pubsub_topic_iam_binding', 'roles/pubsub.publisher'),
|
||||||
|
('warning', 'google_storage_bucket_iam_binding', 'roles/storage.objectCreator')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_exclusions(plan_runner):
|
||||||
|
"Test folder-level logging exclusions."
|
||||||
|
logging_exclusions = (
|
||||||
|
'{'
|
||||||
|
'exclusion1 = "resource.type=gce_instance", '
|
||||||
|
'exclusion2 = "severity=NOTICE", '
|
||||||
|
'}'
|
||||||
|
)
|
||||||
|
_, resources = plan_runner(FIXTURES_DIR,
|
||||||
|
logging_exclusions=logging_exclusions)
|
||||||
|
assert len(resources) == 3
|
||||||
|
exclusions = [r for r in resources
|
||||||
|
if r['type'] == 'google_logging_folder_exclusion']
|
||||||
|
assert sorted([r['index'] for r in exclusions]) == [
|
||||||
|
'exclusion1',
|
||||||
|
'exclusion2',
|
||||||
|
]
|
||||||
|
values = [(r['index'], r['values']['filter']) for r in exclusions]
|
||||||
|
assert sorted(values) == [
|
||||||
|
('exclusion1', 'resource.type=gce_instance'),
|
||||||
|
('exclusion2', 'severity=NOTICE')
|
||||||
|
]
|
|
@ -20,8 +20,8 @@ import pytest
|
||||||
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
|
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
|
||||||
|
|
||||||
|
|
||||||
def test_policy_boolean(plan_runner):
|
def test_sink(plan_runner):
|
||||||
"Test boolean folder policy."
|
"Test folder-level sink."
|
||||||
policy_boolean = '{policy-a = true, policy-b = false, policy-c = null}'
|
policy_boolean = '{policy-a = true, policy-b = false, policy-c = null}'
|
||||||
_, resources = plan_runner(FIXTURES_DIR, policy_boolean=policy_boolean)
|
_, resources = plan_runner(FIXTURES_DIR, policy_boolean=policy_boolean)
|
||||||
|
|
||||||
|
@ -46,8 +46,8 @@ def test_policy_boolean(plan_runner):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_policy_list(plan_runner):
|
def test_exclussions(plan_runner):
|
||||||
"Test list org policy."
|
"Test folder-level logging exclusions."
|
||||||
policy_list = (
|
policy_list = (
|
||||||
'{'
|
'{'
|
||||||
'policy-a = {inherit_from_parent = true, suggested_value = null, status = true, values = []}, '
|
'policy-a = {inherit_from_parent = true, suggested_value = null, status = true, values = []}, '
|
||||||
|
|
|
@ -26,4 +26,6 @@ module "test" {
|
||||||
policy_list = var.policy_list
|
policy_list = var.policy_list
|
||||||
firewall_policies = var.firewall_policies
|
firewall_policies = var.firewall_policies
|
||||||
firewall_policy_attachments = var.firewall_policy_attachments
|
firewall_policy_attachments = var.firewall_policy_attachments
|
||||||
|
logging_sinks = var.logging_sinks
|
||||||
|
logging_exclusions = var.logging_exclusions
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,3 +73,19 @@ variable "firewall_policy_attachments" {
|
||||||
type = map(string)
|
type = map(string)
|
||||||
default = {}
|
default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "logging_sinks" {
|
||||||
|
type = map(object({
|
||||||
|
destination = string
|
||||||
|
type = string
|
||||||
|
filter = string
|
||||||
|
iam = bool
|
||||||
|
include_children = bool
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "logging_exclusions" {
|
||||||
|
type = map(string)
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
# Copyright 2020 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 os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
|
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
|
||||||
|
|
||||||
|
|
||||||
|
def test_sinks(plan_runner):
|
||||||
|
"Test folder-level sinks."
|
||||||
|
logging_sinks = """ {
|
||||||
|
warning = {
|
||||||
|
type = "gcs"
|
||||||
|
destination = "mybucket"
|
||||||
|
filter = "severity=WARNING"
|
||||||
|
iam = true
|
||||||
|
include_children = true
|
||||||
|
}
|
||||||
|
info = {
|
||||||
|
type = "bigquery"
|
||||||
|
destination = "projects/myproject/datasets/mydataset"
|
||||||
|
filter = "severity=INFO"
|
||||||
|
iam = true
|
||||||
|
include_children = true
|
||||||
|
}
|
||||||
|
notice = {
|
||||||
|
type = "pubsub"
|
||||||
|
destination = "projects/myproject/topics/mytopic"
|
||||||
|
filter = "severity=NOTICE"
|
||||||
|
iam = true
|
||||||
|
include_children = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
_, resources = plan_runner(FIXTURES_DIR, logging_sinks=logging_sinks)
|
||||||
|
assert len(resources) == 6
|
||||||
|
|
||||||
|
resource_types = Counter([r['type'] for r in resources])
|
||||||
|
assert resource_types == {
|
||||||
|
'google_bigquery_dataset_iam_binding': 1,
|
||||||
|
'google_logging_organization_sink': 3,
|
||||||
|
'google_pubsub_topic_iam_binding': 1,
|
||||||
|
'google_storage_bucket_iam_binding': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sinks = [r for r in resources
|
||||||
|
if r['type'] == 'google_logging_organization_sink']
|
||||||
|
assert sorted([r['index'] for r in sinks]) == [
|
||||||
|
'info',
|
||||||
|
'notice',
|
||||||
|
'warning',
|
||||||
|
]
|
||||||
|
values = [(r['index'], r['values']['filter'], r['values']['destination'],
|
||||||
|
r['values']['include_children'])
|
||||||
|
for r in sinks]
|
||||||
|
assert sorted(values) == [
|
||||||
|
('info',
|
||||||
|
'severity=INFO',
|
||||||
|
'bigquery.googleapis.com/projects/myproject/datasets/mydataset',
|
||||||
|
True),
|
||||||
|
('notice',
|
||||||
|
'severity=NOTICE',
|
||||||
|
'pubsub.googleapis.com/projects/myproject/topics/mytopic',
|
||||||
|
False),
|
||||||
|
('warning', 'severity=WARNING', 'storage.googleapis.com/mybucket', True)]
|
||||||
|
|
||||||
|
bindings = [r for r in resources
|
||||||
|
if 'binding' in r['type']]
|
||||||
|
values = [(r['index'], r['type'], r['values']['role'])
|
||||||
|
for r in bindings]
|
||||||
|
assert sorted(values) == [
|
||||||
|
('info', 'google_bigquery_dataset_iam_binding', 'roles/bigquery.dataEditor'),
|
||||||
|
('notice', 'google_pubsub_topic_iam_binding', 'roles/pubsub.publisher'),
|
||||||
|
('warning', 'google_storage_bucket_iam_binding', 'roles/storage.objectCreator')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_exclusions(plan_runner):
|
||||||
|
"Test folder-level logging exclusions."
|
||||||
|
logging_exclusions = (
|
||||||
|
'{'
|
||||||
|
'exclusion1 = "resource.type=gce_instance", '
|
||||||
|
'exclusion2 = "severity=NOTICE", '
|
||||||
|
'}'
|
||||||
|
)
|
||||||
|
_, resources = plan_runner(FIXTURES_DIR,
|
||||||
|
logging_exclusions=logging_exclusions)
|
||||||
|
assert len(resources) == 2
|
||||||
|
exclusions = [r for r in resources
|
||||||
|
if r['type'] == 'google_logging_organization_exclusion']
|
||||||
|
assert sorted([r['index'] for r in exclusions]) == [
|
||||||
|
'exclusion1',
|
||||||
|
'exclusion2',
|
||||||
|
]
|
||||||
|
values = [(r['index'], r['values']['filter']) for r in exclusions]
|
||||||
|
assert sorted(values) == [
|
||||||
|
('exclusion1', 'resource.type=gce_instance'),
|
||||||
|
('exclusion2', 'severity=NOTICE')
|
||||||
|
]
|
|
@ -33,4 +33,6 @@ module "test" {
|
||||||
policy_list = var.policy_list
|
policy_list = var.policy_list
|
||||||
prefix = var.prefix
|
prefix = var.prefix
|
||||||
services = var.services
|
services = var.services
|
||||||
|
logging_sinks = var.logging_sinks
|
||||||
|
logging_exclusions = var.logging_exclusions
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,3 +93,18 @@ variable "services" {
|
||||||
type = list(string)
|
type = list(string)
|
||||||
default = []
|
default = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "logging_sinks" {
|
||||||
|
type = map(object({
|
||||||
|
destination = string
|
||||||
|
type = string
|
||||||
|
filter = string
|
||||||
|
iam = bool
|
||||||
|
}))
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "logging_exclusions" {
|
||||||
|
type = map(string)
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
# Copyright 2020 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 os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
|
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
|
||||||
|
|
||||||
|
|
||||||
|
def test_sinks(plan_runner):
|
||||||
|
"Test folder-level sinks."
|
||||||
|
logging_sinks = """ {
|
||||||
|
warning = {
|
||||||
|
type = "gcs"
|
||||||
|
destination = "mybucket"
|
||||||
|
filter = "severity=WARNING"
|
||||||
|
iam = true
|
||||||
|
}
|
||||||
|
info = {
|
||||||
|
type = "bigquery"
|
||||||
|
destination = "projects/myproject/datasets/mydataset"
|
||||||
|
filter = "severity=INFO"
|
||||||
|
iam = true
|
||||||
|
}
|
||||||
|
notice = {
|
||||||
|
type = "pubsub"
|
||||||
|
destination = "projects/myproject/topics/mytopic"
|
||||||
|
filter = "severity=NOTICE"
|
||||||
|
iam = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
_, resources = plan_runner(FIXTURES_DIR, logging_sinks=logging_sinks)
|
||||||
|
assert len(resources) == 7
|
||||||
|
|
||||||
|
resource_types = Counter([r['type'] for r in resources])
|
||||||
|
assert resource_types == {
|
||||||
|
'google_bigquery_dataset_iam_binding': 1,
|
||||||
|
'google_logging_project_sink': 3,
|
||||||
|
'google_project': 1,
|
||||||
|
'google_pubsub_topic_iam_binding': 1,
|
||||||
|
'google_storage_bucket_iam_binding': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sinks = [r for r in resources
|
||||||
|
if r['type'] == 'google_logging_project_sink']
|
||||||
|
assert sorted([r['index'] for r in sinks]) == [
|
||||||
|
'info',
|
||||||
|
'notice',
|
||||||
|
'warning',
|
||||||
|
]
|
||||||
|
values = [(r['index'], r['values']['filter'], r['values']['destination'])
|
||||||
|
for r in sinks]
|
||||||
|
assert sorted(values) == [
|
||||||
|
('info',
|
||||||
|
'severity=INFO',
|
||||||
|
'bigquery.googleapis.com/projects/myproject/datasets/mydataset'),
|
||||||
|
('notice',
|
||||||
|
'severity=NOTICE',
|
||||||
|
'pubsub.googleapis.com/projects/myproject/topics/mytopic'),
|
||||||
|
('warning', 'severity=WARNING', 'storage.googleapis.com/mybucket')]
|
||||||
|
|
||||||
|
bindings = [r for r in resources
|
||||||
|
if 'binding' in r['type']]
|
||||||
|
values = [(r['index'], r['type'], r['values']['role'])
|
||||||
|
for r in bindings]
|
||||||
|
assert sorted(values) == [
|
||||||
|
('info', 'google_bigquery_dataset_iam_binding', 'roles/bigquery.dataEditor'),
|
||||||
|
('notice', 'google_pubsub_topic_iam_binding', 'roles/pubsub.publisher'),
|
||||||
|
('warning', 'google_storage_bucket_iam_binding', 'roles/storage.objectCreator')
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_exclusions(plan_runner):
|
||||||
|
"Test folder-level logging exclusions."
|
||||||
|
logging_exclusions = (
|
||||||
|
'{'
|
||||||
|
'exclusion1 = "resource.type=gce_instance", '
|
||||||
|
'exclusion2 = "severity=NOTICE", '
|
||||||
|
'}'
|
||||||
|
)
|
||||||
|
_, resources = plan_runner(FIXTURES_DIR,
|
||||||
|
logging_exclusions=logging_exclusions)
|
||||||
|
assert len(resources) == 3
|
||||||
|
exclusions = [r for r in resources
|
||||||
|
if r['type'] == 'google_logging_project_exclusion']
|
||||||
|
assert sorted([r['index'] for r in exclusions]) == [
|
||||||
|
'exclusion1',
|
||||||
|
'exclusion2',
|
||||||
|
]
|
||||||
|
values = [(r['index'], r['values']['filter']) for r in exclusions]
|
||||||
|
assert sorted(values) == [
|
||||||
|
('exclusion1', 'resource.type=gce_instance'),
|
||||||
|
('exclusion2', 'severity=NOTICE')
|
||||||
|
]
|
Loading…
Reference in New Issue