From 84ab0f4f9d291553a98335c1efef17ddc0f12178 Mon Sep 17 00:00:00 2001 From: Charlie Wolf Date: Mon, 23 Dec 2019 04:49:35 -0800 Subject: [PATCH] VPC SC #2: Access level whitelist --- .../gcp_vpc_sc_ensure_access_levels_v1.yaml | 44 ++++++++++++--- .../constraints/blacklist/data.yaml | 27 ++++++++++ .../constraints/{ => old}/data.yaml | 2 +- .../constraints/require/data.yaml | 28 ++++++++++ .../constraints/whitelist/data.yaml | 27 ++++++++++ validator/vpc_sc_ensure_access_levels.rego | 30 +++++++++-- .../vpc_sc_ensure_access_levels_test.rego | 53 +++++++++++++++++-- 7 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/blacklist/data.yaml rename validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/{ => old}/data.yaml (95%) create mode 100644 validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/require/data.yaml create mode 100644 validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/whitelist/data.yaml diff --git a/policies/templates/gcp_vpc_sc_ensure_access_levels_v1.yaml b/policies/templates/gcp_vpc_sc_ensure_access_levels_v1.yaml index 0233dba3b..88115bac5 100644 --- a/policies/templates/gcp_vpc_sc_ensure_access_levels_v1.yaml +++ b/policies/templates/gcp_vpc_sc_ensure_access_levels_v1.yaml @@ -26,8 +26,18 @@ spec: validation: openAPIV3Schema: properties: - required_access_levels: - description: "Required access level names" + mode: + type: string + enum: [whitelist, blacklist, reqired] + description: "String identifying the operational mode, + whitelist, blacklist or reqired . In the whitelist mode, only + access levels from the access_levels list will be allowed (all + other levels will raise a violation). In the blacklist mode, any + access level not in the access_levels list will not raise a + violation. In required mode, all access levels in the + access_levels list will be required in the perimeter." + access_levels: + description: "Access level names" type: array items: string targets: @@ -64,16 +74,38 @@ spec: lib.has_field(asset, "service_perimeter") lib.get_constraint_params(constraint, params) - required_access_levels_array := lib.get_default(params, "required_access_levels", []) - required_access_levels := {p | p = required_access_levels_array[_]} + mode := lib.get_default(params, "mode", "require") perimeter_access_levels_raw := {split(r, "/") | r = asset.service_perimeter.status.access_levels[_]} perimeter_access_levels := {r[3] | r = perimeter_access_levels_raw[_]} - count(perimeter_access_levels - required_access_levels) != count(perimeter_access_levels) - count(required_access_levels) + # For compatibility reasons, we support the old key name required_access_levels + configured_access_levels := cast_set(lib.get_default(params, "access_levels", lib.get_default(params, "required_access_levels", []))) - message := sprintf("Required access levels missing from service perimeter %v.", [asset.service_perimeter.name]) + check_access_level(perimeter_access_levels, configured_access_levels, mode) + + message := sprintf("Invalid access levels in service perimeter %v.", [asset.service_perimeter.name]) metadata := {"resource": asset.name, "service_perimeter_name": asset.service_perimeter.name} } + + check_access_level(perimeter_access_levels, configured_access_levels, mode) { + mode == "whitelist" + perimeter := perimeter_access_levels[_] + matches := {perimeter} & configured_access_levels + count(matches) == 0 + } + + check_access_level(perimeter_access_levels, configured_access_levels, mode) { + mode == "blacklist" + perimeter := perimeter_access_levels[_] + matches := {perimeter} & configured_access_levels + count(matches) > 0 + } + + check_access_level(perimeter_access_levels, configured_access_levels, mode) { + mode == "require" + intersection := perimeter_access_levels & configured_access_levels + count(intersection) != count(configured_access_levels) + } #ENDINLINE diff --git a/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/blacklist/data.yaml b/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/blacklist/data.yaml new file mode 100644 index 000000000..d96c39f7f --- /dev/null +++ b/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/blacklist/data.yaml @@ -0,0 +1,27 @@ +# Copyright 2019 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. +# +apiVersion: constraints.gatekeeper.sh/v1alpha1 +kind: GCPVPCSCEnsureAccessLevelsConstraintV1 +metadata: + name: vpc_sc_ensure_access_levels_blacklist +spec: + severity: high + match: + gcp: + target: ["organization/*"] + parameters: + mode: blacklist + access_levels: + - efgh diff --git a/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/data.yaml b/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/old/data.yaml similarity index 95% rename from validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/data.yaml rename to validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/old/data.yaml index 1264b2b4b..7c542396d 100644 --- a/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/data.yaml +++ b/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/old/data.yaml @@ -15,7 +15,7 @@ apiVersion: constraints.gatekeeper.sh/v1alpha1 kind: GCPVPCSCEnsureAccessLevelsConstraintV1 metadata: - name: vpc_sc_ensure_access_levels + name: vpc_sc_ensure_access_levels_old spec: severity: high match: diff --git a/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/require/data.yaml b/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/require/data.yaml new file mode 100644 index 000000000..366339028 --- /dev/null +++ b/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/require/data.yaml @@ -0,0 +1,28 @@ +# Copyright 2019 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. +# +apiVersion: constraints.gatekeeper.sh/v1alpha1 +kind: GCPVPCSCEnsureAccessLevelsConstraintV1 +metadata: + name: vpc_sc_ensure_access_levels_require +spec: + severity: high + match: + gcp: + target: ["organization/*"] + parameters: + mode: require + access_levels: + - abcd + - efgh diff --git a/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/whitelist/data.yaml b/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/whitelist/data.yaml new file mode 100644 index 000000000..3f2099af6 --- /dev/null +++ b/validator/test/fixtures/vpc_sc_ensure_access_levels/constraints/whitelist/data.yaml @@ -0,0 +1,27 @@ +# Copyright 2019 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. +# +apiVersion: constraints.gatekeeper.sh/v1alpha1 +kind: GCPVPCSCEnsureAccessLevelsConstraintV1 +metadata: + name: vpc_sc_ensure_access_levels_whitelist +spec: + severity: high + match: + gcp: + target: ["organization/*"] + parameters: + mode: whitelist + access_levels: + - abcd diff --git a/validator/vpc_sc_ensure_access_levels.rego b/validator/vpc_sc_ensure_access_levels.rego index 3aa95176f..09a12f9d2 100644 --- a/validator/vpc_sc_ensure_access_levels.rego +++ b/validator/vpc_sc_ensure_access_levels.rego @@ -29,15 +29,37 @@ deny[{ lib.has_field(asset, "service_perimeter") lib.get_constraint_params(constraint, params) - required_access_levels_array := lib.get_default(params, "required_access_levels", []) - required_access_levels := {p | p = required_access_levels_array[_]} + mode := lib.get_default(params, "mode", "require") perimeter_access_levels_raw := {split(r, "/") | r = asset.service_perimeter.status.access_levels[_]} perimeter_access_levels := {r[3] | r = perimeter_access_levels_raw[_]} - count(perimeter_access_levels - required_access_levels) != count(perimeter_access_levels) - count(required_access_levels) + # For compatibility reasons, we support the old key name required_access_levels + configured_access_levels := cast_set(lib.get_default(params, "access_levels", lib.get_default(params, "required_access_levels", []))) - message := sprintf("Required access levels missing from service perimeter %v.", [asset.service_perimeter.name]) + check_access_level(perimeter_access_levels, configured_access_levels, mode) + + message := sprintf("Invalid access levels in service perimeter %v.", [asset.service_perimeter.name]) metadata := {"resource": asset.name, "service_perimeter_name": asset.service_perimeter.name} } + +check_access_level(perimeter_access_levels, configured_access_levels, mode) { + mode == "whitelist" + perimeter := perimeter_access_levels[_] + matches := {perimeter} & configured_access_levels + count(matches) == 0 +} + +check_access_level(perimeter_access_levels, configured_access_levels, mode) { + mode == "blacklist" + perimeter := perimeter_access_levels[_] + matches := {perimeter} & configured_access_levels + count(matches) > 0 +} + +check_access_level(perimeter_access_levels, configured_access_levels, mode) { + mode == "require" + intersection := perimeter_access_levels & configured_access_levels + count(intersection) != count(configured_access_levels) +} diff --git a/validator/vpc_sc_ensure_access_levels_test.rego b/validator/vpc_sc_ensure_access_levels_test.rego index d02ccd5e5..dd8400b06 100644 --- a/validator/vpc_sc_ensure_access_levels_test.rego +++ b/validator/vpc_sc_ensure_access_levels_test.rego @@ -18,9 +18,9 @@ package templates.gcp.GCPVPCSCEnsureAccessLevelsConstraintV1 import data.validator.gcp.lib as lib -all_violations[violation] { +old_style_violations[violation] { resource := data.test.fixtures.vpc_sc_ensure_access_levels.assets[_] - constraint := data.test.fixtures.vpc_sc_ensure_access_levels.constraints + constraint := data.test.fixtures.vpc_sc_ensure_access_levels.constraints.old issues := deny with input.asset as resource with input.constraint as constraint @@ -28,7 +28,52 @@ all_violations[violation] { violation := issues[_] } -test_violations_basic { - violation_resources := {r | r = all_violations[_].details.service_perimeter_name} +require_violations[violation] { + resource := data.test.fixtures.vpc_sc_ensure_access_levels.assets[_] + constraint := data.test.fixtures.vpc_sc_ensure_access_levels.constraints.require + + issues := deny with input.asset as resource + with input.constraint as constraint + + violation := issues[_] +} + +whitelist_violations[violation] { + resource := data.test.fixtures.vpc_sc_ensure_access_levels.assets[_] + constraint := data.test.fixtures.vpc_sc_ensure_access_levels.constraints.whitelist + + issues := deny with input.asset as resource + with input.constraint as constraint + + violation := issues[_] +} + +blacklist_violations[violation] { + resource := data.test.fixtures.vpc_sc_ensure_access_levels.assets[_] + constraint := data.test.fixtures.vpc_sc_ensure_access_levels.constraints.blacklist + + issues := deny with input.asset as resource + with input.constraint as constraint + + violation := issues[_] +} + +test_violations_old_style { + violation_resources := {r | r = old_style_violations[_].details.service_perimeter_name} violation_resources == {"accessPolicies/1008882730434/servicePerimeters/Test_Service_Perimeter_Bad"} } + +test_violations_require { + violation_resources := {r | r = require_violations[_].details.service_perimeter_name} + violation_resources == {"accessPolicies/1008882730434/servicePerimeters/Test_Service_Perimeter_Bad"} +} + +test_violations_whitelist { + violation_resources := {r | r = whitelist_violations[_].details.service_perimeter_name} + violation_resources == {"accessPolicies/1008882730433/servicePerimeters/Test_Service_Perimeter_Good"} +} + +test_violations_blacklist { + violation_resources := {r | r = blacklist_violations[_].details.service_perimeter_name} + violation_resources == {"accessPolicies/1008882730433/servicePerimeters/Test_Service_Perimeter_Good"} +}