Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VPC-SC #2: Whitelist support for access levels #238

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions policies/templates/gcp_vpc_sc_ensure_access_levels_v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,18 @@ spec:
validation:
openAPIV3Schema:
properties:
required_access_levels:
description: "Required access level names"
mode:
type: string
enum: [whitelist, blacklist, require]
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:
Expand Down Expand Up @@ -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")
joecheuk marked this conversation as resolved.
Show resolved Hide resolved

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", [])))
joecheuk marked this conversation as resolved.
Show resolved Hide resolved

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"
joecheuk marked this conversation as resolved.
Show resolved Hide resolved
intersection := perimeter_access_levels & configured_access_levels
count(intersection) != count(configured_access_levels)
}
#ENDINLINE
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
30 changes: 26 additions & 4 deletions validator/vpc_sc_ensure_access_levels.rego
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
53 changes: 49 additions & 4 deletions validator/vpc_sc_ensure_access_levels_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,62 @@ 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

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"}
}