Skip to content
This repository was archived by the owner on Aug 20, 2025. It is now read-only.

Commit b2a5c1d

Browse files
committed
VPC SC #2: Access level whitelist
1 parent f0dc14c commit b2a5c1d

File tree

7 files changed

+196
-15
lines changed

7 files changed

+196
-15
lines changed

policies/templates/gcp_vpc_sc_ensure_access_levels_v1.yaml

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,18 @@ spec:
2626
validation:
2727
openAPIV3Schema:
2828
properties:
29-
required_access_levels:
30-
description: "Required access level names"
29+
mode:
30+
type: string
31+
enum: [whitelist, blacklist, require]
32+
description: "String identifying the operational mode,
33+
whitelist, blacklist or reqired . In the whitelist mode, only
34+
access levels from the access_levels list will be allowed (all
35+
other levels will raise a violation). In the blacklist mode, any
36+
access level not in the access_levels list will not raise a
37+
violation. In required mode, all access levels in the
38+
access_levels list will be required in the perimeter."
39+
access_levels:
40+
description: "Access level names"
3141
type: array
3242
items: string
3343
targets:
@@ -64,16 +74,38 @@ spec:
6474
lib.has_field(asset, "service_perimeter")
6575
6676
lib.get_constraint_params(constraint, params)
67-
required_access_levels_array := lib.get_default(params, "required_access_levels", [])
68-
required_access_levels := {p | p = required_access_levels_array[_]}
77+
mode := lib.get_default(params, "mode", "require")
6978
7079
perimeter_access_levels_raw := {split(r, "/") | r = asset.service_perimeter.status.access_levels[_]}
7180
perimeter_access_levels := {r[3] | r = perimeter_access_levels_raw[_]}
7281
73-
count(perimeter_access_levels - required_access_levels) != count(perimeter_access_levels) - count(required_access_levels)
82+
# For compatibility reasons, we support the old key name required_access_levels
83+
configured_access_levels := cast_set(lib.get_default(params, "access_levels", lib.get_default(params, "required_access_levels", [])))
7484
75-
message := sprintf("Required access levels missing from service perimeter %v.", [asset.service_perimeter.name])
85+
check_access_level(perimeter_access_levels, configured_access_levels, mode)
86+
87+
message := sprintf("Invalid access levels in service perimeter %v.", [asset.service_perimeter.name])
7688
7789
metadata := {"resource": asset.name, "service_perimeter_name": asset.service_perimeter.name}
7890
}
91+
92+
check_access_level(perimeter_access_levels, configured_access_levels, mode) {
93+
mode == "whitelist"
94+
perimeter := perimeter_access_levels[_]
95+
matches := {perimeter} & configured_access_levels
96+
count(matches) == 0
97+
}
98+
99+
check_access_level(perimeter_access_levels, configured_access_levels, mode) {
100+
mode == "blacklist"
101+
perimeter := perimeter_access_levels[_]
102+
matches := {perimeter} & configured_access_levels
103+
count(matches) > 0
104+
}
105+
106+
check_access_level(perimeter_access_levels, configured_access_levels, mode) {
107+
mode == "require"
108+
intersection := perimeter_access_levels & configured_access_levels
109+
count(intersection) != count(configured_access_levels)
110+
}
79111
#ENDINLINE
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2019 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
apiVersion: constraints.gatekeeper.sh/v1alpha1
16+
kind: GCPVPCSCEnsureAccessLevelsConstraintV1
17+
metadata:
18+
name: vpc_sc_ensure_access_levels_blacklist
19+
spec:
20+
severity: high
21+
match:
22+
gcp:
23+
target: ["organization/*"]
24+
parameters:
25+
mode: blacklist
26+
access_levels:
27+
- efgh

validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/data.yaml renamed to validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/old/data.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
apiVersion: constraints.gatekeeper.sh/v1alpha1
1616
kind: GCPVPCSCEnsureAccessLevelsConstraintV1
1717
metadata:
18-
name: vpc_sc_ensure_access_levels
18+
name: vpc_sc_ensure_access_levels_old
1919
spec:
2020
severity: high
2121
match:
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright 2019 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
apiVersion: constraints.gatekeeper.sh/v1alpha1
16+
kind: GCPVPCSCEnsureAccessLevelsConstraintV1
17+
metadata:
18+
name: vpc_sc_ensure_access_levels_require
19+
spec:
20+
severity: high
21+
match:
22+
gcp:
23+
target: ["organization/*"]
24+
parameters:
25+
mode: require
26+
access_levels:
27+
- abcd
28+
- efgh
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2019 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
apiVersion: constraints.gatekeeper.sh/v1alpha1
16+
kind: GCPVPCSCEnsureAccessLevelsConstraintV1
17+
metadata:
18+
name: vpc_sc_ensure_access_levels_whitelist
19+
spec:
20+
severity: high
21+
match:
22+
gcp:
23+
target: ["organization/*"]
24+
parameters:
25+
mode: whitelist
26+
access_levels:
27+
- abcd

validator/vpc_sc_ensure_access_levels.rego

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,37 @@ deny[{
2929
lib.has_field(asset, "service_perimeter")
3030

3131
lib.get_constraint_params(constraint, params)
32-
required_access_levels_array := lib.get_default(params, "required_access_levels", [])
33-
required_access_levels := {p | p = required_access_levels_array[_]}
32+
mode := lib.get_default(params, "mode", "require")
3433

3534
perimeter_access_levels_raw := {split(r, "/") | r = asset.service_perimeter.status.access_levels[_]}
3635
perimeter_access_levels := {r[3] | r = perimeter_access_levels_raw[_]}
3736

38-
count(perimeter_access_levels - required_access_levels) != count(perimeter_access_levels) - count(required_access_levels)
37+
# For compatibility reasons, we support the old key name required_access_levels
38+
configured_access_levels := cast_set(lib.get_default(params, "access_levels", lib.get_default(params, "required_access_levels", [])))
3939

40-
message := sprintf("Required access levels missing from service perimeter %v.", [asset.service_perimeter.name])
40+
check_access_level(perimeter_access_levels, configured_access_levels, mode)
41+
42+
message := sprintf("Invalid access levels in service perimeter %v.", [asset.service_perimeter.name])
4143

4244
metadata := {"resource": asset.name, "service_perimeter_name": asset.service_perimeter.name}
4345
}
46+
47+
check_access_level(perimeter_access_levels, configured_access_levels, mode) {
48+
mode == "whitelist"
49+
perimeter := perimeter_access_levels[_]
50+
matches := {perimeter} & configured_access_levels
51+
count(matches) == 0
52+
}
53+
54+
check_access_level(perimeter_access_levels, configured_access_levels, mode) {
55+
mode == "blacklist"
56+
perimeter := perimeter_access_levels[_]
57+
matches := {perimeter} & configured_access_levels
58+
count(matches) > 0
59+
}
60+
61+
check_access_level(perimeter_access_levels, configured_access_levels, mode) {
62+
mode == "require"
63+
intersection := perimeter_access_levels & configured_access_levels
64+
count(intersection) != count(configured_access_levels)
65+
}

validator/vpc_sc_ensure_access_levels_test.rego

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,62 @@ package templates.gcp.GCPVPCSCEnsureAccessLevelsConstraintV1
1818

1919
import data.validator.gcp.lib as lib
2020

21-
all_violations[violation] {
21+
old_style_violations[violation] {
2222
resource := data.test.fixtures.vpc_sc_ensure_access_levels.assets[_]
23-
constraint := data.test.fixtures.vpc_sc_ensure_access_levels.constraints
23+
constraint := data.test.fixtures.vpc_sc_ensure_access_levels.constraints.old
2424

2525
issues := deny with input.asset as resource
2626
with input.constraint as constraint
2727

2828
violation := issues[_]
2929
}
3030

31-
test_violations_basic {
32-
violation_resources := {r | r = all_violations[_].details.service_perimeter_name}
31+
require_violations[violation] {
32+
resource := data.test.fixtures.vpc_sc_ensure_access_levels.assets[_]
33+
constraint := data.test.fixtures.vpc_sc_ensure_access_levels.constraints.require
34+
35+
issues := deny with input.asset as resource
36+
with input.constraint as constraint
37+
38+
violation := issues[_]
39+
}
40+
41+
whitelist_violations[violation] {
42+
resource := data.test.fixtures.vpc_sc_ensure_access_levels.assets[_]
43+
constraint := data.test.fixtures.vpc_sc_ensure_access_levels.constraints.whitelist
44+
45+
issues := deny with input.asset as resource
46+
with input.constraint as constraint
47+
48+
violation := issues[_]
49+
}
50+
51+
blacklist_violations[violation] {
52+
resource := data.test.fixtures.vpc_sc_ensure_access_levels.assets[_]
53+
constraint := data.test.fixtures.vpc_sc_ensure_access_levels.constraints.blacklist
54+
55+
issues := deny with input.asset as resource
56+
with input.constraint as constraint
57+
58+
violation := issues[_]
59+
}
60+
61+
test_violations_old_style {
62+
violation_resources := {r | r = old_style_violations[_].details.service_perimeter_name}
3363
violation_resources == {"accessPolicies/1008882730434/servicePerimeters/Test_Service_Perimeter_Bad"}
3464
}
65+
66+
test_violations_require {
67+
violation_resources := {r | r = require_violations[_].details.service_perimeter_name}
68+
violation_resources == {"accessPolicies/1008882730434/servicePerimeters/Test_Service_Perimeter_Bad"}
69+
}
70+
71+
test_violations_whitelist {
72+
violation_resources := {r | r = whitelist_violations[_].details.service_perimeter_name}
73+
violation_resources == {"accessPolicies/1008882730433/servicePerimeters/Test_Service_Perimeter_Good"}
74+
}
75+
76+
test_violations_blacklist {
77+
violation_resources := {r | r = blacklist_violations[_].details.service_perimeter_name}
78+
violation_resources == {"accessPolicies/1008882730433/servicePerimeters/Test_Service_Perimeter_Good"}
79+
}

0 commit comments

Comments
 (0)