Skip to content

Commit 029cd98

Browse files
feat!: support for externally managed egress/ingress policies (#193)
1 parent 47f65af commit 029cd98

File tree

4 files changed

+192
-149
lines changed

4 files changed

+192
-149
lines changed

examples/automatic_folder/watcher.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
module "event_folder_log_entry" {
1818
source = "terraform-google-modules/event-function/google//modules/event-folder-log-entry"
19-
version = "~> 4.0"
19+
version = "~> 5.0"
2020

2121
filter = <<EOF
2222
resource.type="project" AND
@@ -37,7 +37,7 @@ resource "google_service_account" "watcher" {
3737

3838
module "localhost_function" {
3939
source = "terraform-google-modules/event-function/google"
40-
version = "~> 4.0"
40+
version = "~> 5.0"
4141

4242
description = "Adds projects to VPC service permiterer."
4343
entry_point = "handler"

modules/regular_service_perimeter/main.tf

Lines changed: 5 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -33,80 +33,7 @@ resource "google_access_context_manager_service_perimeter" "regular_service_peri
3333
var.access_levels
3434
)
3535

36-
dynamic "ingress_policies" {
37-
for_each = var.ingress_policies
38-
iterator = ingress_policies
39-
content {
40-
ingress_from {
41-
dynamic "sources" {
42-
for_each = merge(
43-
{ for k, v in lookup(lookup(ingress_policies.value["from"], "sources", {}), "resources", []) : v => "resource" },
44-
{ for k, v in lookup(lookup(ingress_policies.value["from"], "sources", {}), "access_levels", []) : v => "access_level" })
45-
content {
46-
resource = sources.value == "resource" ? sources.key : null
47-
access_level = sources.value == "access_level" ? sources.key != "*" ? "accessPolicies/${var.policy}/accessLevels/${sources.key}" : "*" : null
48-
}
49-
}
50-
identity_type = lookup(ingress_policies.value["from"], "identity_type", null)
51-
identities = lookup(ingress_policies.value["from"], "identities", null)
52-
}
5336

54-
ingress_to {
55-
resources = lookup(ingress_policies.value["to"], "resources", ["*"])
56-
dynamic "operations" {
57-
for_each = lookup(ingress_policies.value["to"], "operations", [])
58-
content {
59-
service_name = operations.key
60-
dynamic "method_selectors" {
61-
for_each = operations.key != "*" ? merge(
62-
{ for v in lookup(operations.value, "methods", []) : v => "method" },
63-
{ for v in lookup(operations.value, "permissions", []) : v => "permission" }) : {}
64-
content {
65-
method = method_selectors.value == "method" ? method_selectors.key : null
66-
permission = method_selectors.value == "permission" ? method_selectors.key : null
67-
}
68-
}
69-
}
70-
}
71-
}
72-
}
73-
}
74-
dynamic "egress_policies" {
75-
for_each = var.egress_policies
76-
iterator = egress_policies
77-
content {
78-
egress_from {
79-
identity_type = lookup(egress_policies.value["from"], "identity_type", null)
80-
identities = lookup(egress_policies.value["from"], "identities", null)
81-
dynamic "sources" {
82-
for_each = { for k, v in lookup(lookup(egress_policies.value["from"], "sources", {}), "access_levels", []) : v => "access_level" }
83-
content {
84-
access_level = sources.value == "access_level" ? sources.key != "*" ? "accessPolicies/${var.policy}/accessLevels/${sources.key}" : "*" : null
85-
}
86-
}
87-
source_restriction = lookup(egress_policies.value["from"], "sources", null) != null ? "SOURCE_RESTRICTION_ENABLED" : null
88-
}
89-
egress_to {
90-
resources = lookup(egress_policies.value["to"], "resources", ["*"])
91-
external_resources = lookup(egress_policies.value["to"], "external_resources", [])
92-
dynamic "operations" {
93-
for_each = lookup(egress_policies.value["to"], "operations", [])
94-
content {
95-
service_name = operations.key
96-
dynamic "method_selectors" {
97-
for_each = operations.key != "*" ? merge(
98-
{ for v in lookup(operations.value, "methods", []) : v => "method" },
99-
{ for v in lookup(operations.value, "permissions", []) : v => "permission" }) : {}
100-
content {
101-
method = method_selectors.value == "method" ? method_selectors.key : null
102-
permission = method_selectors.value == "permission" ? method_selectors.key : null
103-
}
104-
}
105-
}
106-
}
107-
}
108-
}
109-
}
11037

11138
dynamic "vpc_accessible_services" {
11239
for_each = contains(var.vpc_accessible_services, "*") ? [] : [var.vpc_accessible_services]
@@ -128,79 +55,7 @@ resource "google_access_context_manager_service_perimeter" "regular_service_peri
12855
var.access_levels_dry_run
12956
)
13057

131-
dynamic "ingress_policies" {
132-
for_each = var.ingress_policies_dry_run
133-
iterator = ingress_policies_dry_run
134-
content {
135-
ingress_from {
136-
dynamic "sources" {
137-
for_each = merge(
138-
{ for k, v in lookup(lookup(ingress_policies_dry_run.value["from"], "sources", {}), "resources", []) : v => "resource" },
139-
{ for k, v in lookup(lookup(ingress_policies_dry_run.value["from"], "sources", {}), "access_levels", []) : v => "access_level" })
140-
content {
141-
resource = sources.value == "resource" ? sources.key : null
142-
access_level = sources.value == "access_level" ? sources.key != "*" ? "accessPolicies/${var.policy}/accessLevels/${sources.key}" : "*" : null
143-
}
144-
}
145-
identity_type = lookup(ingress_policies_dry_run.value["from"], "identity_type", null)
146-
identities = lookup(ingress_policies_dry_run.value["from"], "identities", null)
147-
}
14858

149-
ingress_to {
150-
resources = lookup(ingress_policies_dry_run.value["to"], "resources", ["*"])
151-
dynamic "operations" {
152-
for_each = lookup(ingress_policies_dry_run.value["to"], "operations", [])
153-
content {
154-
service_name = operations.key
155-
dynamic "method_selectors" {
156-
for_each = operations.key != "*" ? merge(
157-
{ for v in lookup(operations.value, "methods", []) : v => "method" },
158-
{ for v in lookup(operations.value, "permissions", []) : v => "permission" }) : {}
159-
content {
160-
method = method_selectors.value == "method" ? method_selectors.key : null
161-
permission = method_selectors.value == "permission" ? method_selectors.key : null
162-
}
163-
}
164-
}
165-
}
166-
}
167-
}
168-
}
169-
dynamic "egress_policies" {
170-
for_each = var.egress_policies_dry_run
171-
iterator = egress_policies_dry_run
172-
content {
173-
egress_from {
174-
identity_type = lookup(egress_policies_dry_run.value["from"], "identity_type", null)
175-
identities = lookup(egress_policies_dry_run.value["from"], "identities", null)
176-
dynamic "sources" {
177-
for_each = { for k, v in lookup(lookup(egress_policies_dry_run.value["from"], "sources", {}), "access_levels", []) : v => "access_level" }
178-
content {
179-
access_level = sources.value == "access_level" ? sources.key != "*" ? "accessPolicies/${var.policy}/accessLevels/${sources.key}" : "*" : null
180-
}
181-
}
182-
source_restriction = lookup(egress_policies_dry_run.value["from"], "sources", null) != null ? "SOURCE_RESTRICTION_ENABLED" : null
183-
}
184-
egress_to {
185-
resources = lookup(egress_policies_dry_run.value["to"], "resources", ["*"])
186-
dynamic "operations" {
187-
for_each = lookup(egress_policies_dry_run.value["to"], "operations", [])
188-
content {
189-
service_name = operations.key
190-
dynamic "method_selectors" {
191-
for_each = operations.key != "*" ? merge(
192-
{ for v in lookup(operations.value, "methods", []) : v => "method" },
193-
{ for v in lookup(operations.value, "permissions", []) : v => "permission" }) : {}
194-
content {
195-
method = method_selectors.value == "method" ? method_selectors.key : null
196-
permission = method_selectors.value == "permission" ? method_selectors.key : null
197-
}
198-
}
199-
}
200-
}
201-
}
202-
}
203-
}
20459

20560
dynamic "vpc_accessible_services" {
20661
for_each = contains(var.vpc_accessible_services_dry_run, "*") ? [] : [var.vpc_accessible_services_dry_run]
@@ -214,7 +69,11 @@ resource "google_access_context_manager_service_perimeter" "regular_service_peri
21469
use_explicit_dry_run_spec = local.dry_run
21570

21671
lifecycle {
217-
ignore_changes = [status[0].resources]
72+
ignore_changes = [
73+
status[0].resources,
74+
status[0].ingress_policies, # Allows ingress policies to be managed by google_access_context_manager_service_perimeter_ingress_policy resources
75+
status[0].egress_policies # Allows egress policies to be managed by google_access_context_manager_service_perimeter_egress_policy resources
76+
]
21877
}
21978
}
22079

modules/regular_service_perimeter/versions.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ terraform {
2020

2121
google = {
2222
source = "hashicorp/google"
23-
version = ">= 5.4, < 7"
23+
version = ">= 6.21, < 7"
2424
}
2525
}
2626

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
resource "google_access_context_manager_service_perimeter_ingress_policy" "ingress_policies" {
18+
for_each = { for k, v in var.ingress_policies : k => v }
19+
20+
perimeter = google_access_context_manager_service_perimeter.regular_service_perimeter.name
21+
title = "Ingress Policy ${each.key}"
22+
ingress_from {
23+
dynamic "sources" {
24+
for_each = merge(
25+
{ for k, v in lookup(lookup(each.value["from"], "sources", {}), "resources", []) : v => "resource" },
26+
{ for k, v in lookup(lookup(each.value["from"], "sources", {}), "access_levels", []) : v => "access_level" })
27+
content {
28+
resource = sources.value == "resource" ? sources.key : null
29+
access_level = sources.value == "access_level" ? sources.key != "*" ? "accessPolicies/${var.policy}/accessLevels/${sources.key}" : "*" : null
30+
}
31+
}
32+
identity_type = lookup(each.value["from"], "identity_type", null)
33+
identities = lookup(each.value["from"], "identities", null)
34+
}
35+
36+
ingress_to {
37+
resources = lookup(each.value["to"], "resources", ["*"])
38+
dynamic "operations" {
39+
for_each = lookup(each.value["to"], "operations", [])
40+
content {
41+
service_name = operations.key
42+
dynamic "method_selectors" {
43+
for_each = operations.key != "*" ? merge(
44+
{ for v in lookup(operations.value, "methods", []) : v => "method" },
45+
{ for v in lookup(operations.value, "permissions", []) : v => "permission" }) : {}
46+
content {
47+
method = method_selectors.value == "method" ? method_selectors.key : null
48+
permission = method_selectors.value == "permission" ? method_selectors.key : null
49+
}
50+
}
51+
}
52+
}
53+
}
54+
lifecycle {
55+
create_before_destroy = true
56+
}
57+
}
58+
59+
resource "google_access_context_manager_service_perimeter_egress_policy" "egress_policies" {
60+
for_each = { for k, v in var.egress_policies : k => v }
61+
62+
perimeter = google_access_context_manager_service_perimeter.regular_service_perimeter.name
63+
title = "Egress Policy ${each.key}"
64+
65+
egress_from {
66+
identity_type = lookup(each.value["from"], "identity_type", null)
67+
identities = lookup(each.value["from"], "identities", null)
68+
dynamic "sources" {
69+
for_each = { for k, v in lookup(lookup(each.value["from"], "sources", {}), "access_levels", []) : v => "access_level" }
70+
content {
71+
access_level = sources.value == "access_level" ? sources.key != "*" ? "accessPolicies/${var.policy}/accessLevels/${sources.key}" : "*" : null
72+
}
73+
}
74+
source_restriction = lookup(each.value["from"], "sources", null) != null ? "SOURCE_RESTRICTION_ENABLED" : null
75+
}
76+
egress_to {
77+
resources = lookup(each.value["to"], "resources", ["*"])
78+
external_resources = lookup(each.value["to"], "external_resources", [])
79+
dynamic "operations" {
80+
for_each = lookup(each.value["to"], "operations", [])
81+
content {
82+
service_name = operations.key
83+
dynamic "method_selectors" {
84+
for_each = operations.key != "*" ? merge(
85+
{ for v in lookup(operations.value, "methods", []) : v => "method" },
86+
{ for v in lookup(operations.value, "permissions", []) : v => "permission" }) : {}
87+
content {
88+
method = method_selectors.value == "method" ? method_selectors.key : null
89+
permission = method_selectors.value == "permission" ? method_selectors.key : null
90+
}
91+
}
92+
}
93+
}
94+
}
95+
lifecycle {
96+
create_before_destroy = true
97+
}
98+
}
99+
100+
############################################
101+
# DRY-RUN POLICIES #
102+
############################################
103+
104+
resource "google_access_context_manager_service_perimeter_dry_run_ingress_policy" "ingress_policies" {
105+
for_each = { for k, v in var.ingress_policies_dry_run : k => v }
106+
107+
perimeter = google_access_context_manager_service_perimeter.regular_service_perimeter.name
108+
title = "Ingress Policy ${each.key}"
109+
ingress_from {
110+
dynamic "sources" {
111+
for_each = merge(
112+
{ for k, v in lookup(lookup(each.value["from"], "sources", {}), "resources", []) : v => "resource" },
113+
{ for k, v in lookup(lookup(each.value["from"], "sources", {}), "access_levels", []) : v => "access_level" })
114+
content {
115+
resource = sources.value == "resource" ? sources.key : null
116+
access_level = sources.value == "access_level" ? sources.key != "*" ? "accessPolicies/${var.policy}/accessLevels/${sources.key}" : "*" : null
117+
}
118+
}
119+
identity_type = lookup(each.value["from"], "identity_type", null)
120+
identities = lookup(each.value["from"], "identities", null)
121+
}
122+
123+
ingress_to {
124+
resources = lookup(each.value["to"], "resources", ["*"])
125+
dynamic "operations" {
126+
for_each = lookup(each.value["to"], "operations", [])
127+
content {
128+
service_name = operations.key
129+
dynamic "method_selectors" {
130+
for_each = operations.key != "*" ? merge(
131+
{ for v in lookup(operations.value, "methods", []) : v => "method" },
132+
{ for v in lookup(operations.value, "permissions", []) : v => "permission" }) : {}
133+
content {
134+
method = method_selectors.value == "method" ? method_selectors.key : null
135+
permission = method_selectors.value == "permission" ? method_selectors.key : null
136+
}
137+
}
138+
}
139+
}
140+
}
141+
lifecycle {
142+
create_before_destroy = true
143+
}
144+
}
145+
146+
resource "google_access_context_manager_service_perimeter_dry_run_egress_policy" "egress_policies" {
147+
for_each = { for k, v in var.egress_policies_dry_run : k => v }
148+
149+
perimeter = google_access_context_manager_service_perimeter.regular_service_perimeter.name
150+
title = "Egress Policy ${each.key}"
151+
152+
egress_from {
153+
identity_type = lookup(each.value["from"], "identity_type", null)
154+
identities = lookup(each.value["from"], "identities", null)
155+
dynamic "sources" {
156+
for_each = { for k, v in lookup(lookup(each.value["from"], "sources", {}), "access_levels", []) : v => "access_level" }
157+
content {
158+
access_level = sources.value == "access_level" ? sources.key != "*" ? "accessPolicies/${var.policy}/accessLevels/${sources.key}" : "*" : null
159+
}
160+
}
161+
source_restriction = lookup(each.value["from"], "sources", null) != null ? "SOURCE_RESTRICTION_ENABLED" : null
162+
}
163+
egress_to {
164+
resources = lookup(each.value["to"], "resources", ["*"])
165+
dynamic "operations" {
166+
for_each = lookup(each.value["to"], "operations", [])
167+
content {
168+
service_name = operations.key
169+
dynamic "method_selectors" {
170+
for_each = operations.key != "*" ? merge(
171+
{ for v in lookup(operations.value, "methods", []) : v => "method" },
172+
{ for v in lookup(operations.value, "permissions", []) : v => "permission" }) : {}
173+
content {
174+
method = method_selectors.value == "method" ? method_selectors.key : null
175+
permission = method_selectors.value == "permission" ? method_selectors.key : null
176+
}
177+
}
178+
}
179+
}
180+
}
181+
lifecycle {
182+
create_before_destroy = true
183+
}
184+
}

0 commit comments

Comments
 (0)