diff --git a/policies/templates/gcp_iam_role_separation_v1.yaml b/policies/templates/gcp_iam_role_separation_v1.yaml new file mode 100644 index 00000000..38bc1842 --- /dev/null +++ b/policies/templates/gcp_iam_role_separation_v1.yaml @@ -0,0 +1,91 @@ +# Copyright 2019 Google Inc. +# +# 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: templates.gatekeeper.sh/v1alpha1 +kind: ConstraintTemplate +metadata: + name: gcp-iam-allowed-bindings-v1 +spec: + crd: + spec: + names: + kind: GCPIAMRoleSeparationConstraintV1 + plural: gcpiamroleseparationconstraintsv1 + validation: + openAPIV3Schema: + properties: + roles: + description: "A list of combinations of roles that the same user cannot have simultaneously" + type: array + items: + type: array + items: + type: string + targets: + validation.gcp.forsetisecurity.org: + rego: | #INLINE("validator/iam_role_separation.rego") + # + # 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. + # + + package templates.gcp.GCPIAMRoleSeparationConstraintV1 + + import data.validator.gcp.lib as lib + + deny[{ + "msg": message, + "details": metadata, + }] { + constraint := input.constraint + lib.get_constraint_params(constraint, params) + asset := input.asset + + conflicting_roles := cast_set(params.roles[_]) + + all_members = {member | member := asset.iam_policy.bindings[_].members[_]} + + members_to_roles := {member: roles | + member := all_members[_] + roles := {binding.role | + count({member} & cast_set(asset.iam_policy.bindings[b].members)) == 1 + binding := asset.iam_policy.bindings[b] + } + } + + have_roles := members_to_roles[member] + + potentially_conflicting_roles := have_roles & conflicting_roles + count(potentially_conflicting_roles) > 1 + + message := sprintf("IAM policy for %v grants %v to %v", [asset.name, concat(",", potentially_conflicting_roles), member]) + + metadata := { + "resource": asset.name, + "member": member, + "roles": potentially_conflicting_roles, + } + } + #ENDINLINE diff --git a/samples/kms_role_separation.yaml b/samples/kms_role_separation.yaml new file mode 100644 index 00000000..9fff1f08 --- /dev/null +++ b/samples/kms_role_separation.yaml @@ -0,0 +1,18 @@ +apiVersion: constraints.gatekeeper.sh/v1alpha1 +kind: GCPIAMRoleSeparationConstraintV1 +metadata: + name: ensure_service_account_role_separation + annotations: + description: Prevent public users from having access to both administer and user service accounts at the same time + # This constraint is not certified by CIS. + bundles.validator.forsetisecurity.org/cis-v1.1: 1.09 +spec: + severity: high + parameters: + roles: + - - roles/cloudkms.admin + - roles/cloudkms.cryptoKeyDecrypter + - - roles/cloudkms.admin + - roles/cloudkms.cryptoKeyEncrypter + - - roles/cloudkms.admin + - roles/cloudkms.cryptoKeyEncrypterDecrypter diff --git a/samples/service_account_role_separation.yaml b/samples/service_account_role_separation.yaml new file mode 100644 index 00000000..3c0cecd8 --- /dev/null +++ b/samples/service_account_role_separation.yaml @@ -0,0 +1,14 @@ +apiVersion: constraints.gatekeeper.sh/v1alpha1 +kind: GCPIAMRoleSeparationConstraintV1 +metadata: + name: ensure_service_account_role_separation + annotations: + description: Prevent public users from having access to both administer and user service accounts at the same time + # This constraint is not certified by CIS. + bundles.validator.forsetisecurity.org/cis-v1.1: 1.07 +spec: + severity: high + parameters: + roles: + - - roles/iam.serviceAccountUser + - roles/iam.serviceAccountAdmin diff --git a/validator/iam_role_separation.rego b/validator/iam_role_separation.rego new file mode 100644 index 00000000..54ad1060 --- /dev/null +++ b/validator/iam_role_separation.rego @@ -0,0 +1,53 @@ +# +# 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. +# + +package templates.gcp.GCPIAMRoleSeparationConstraintV1 + +import data.validator.gcp.lib as lib + +deny[{ + "msg": message, + "details": metadata, +}] { + constraint := input.constraint + lib.get_constraint_params(constraint, params) + asset := input.asset + + conflicting_roles := cast_set(params.roles[_]) + + all_members = {member | member := asset.iam_policy.bindings[_].members[_]} + + members_to_roles := {member: roles | + member := all_members[_] + roles := {binding.role | + count({member} & cast_set(asset.iam_policy.bindings[b].members)) == 1 + binding := asset.iam_policy.bindings[b] + } + } + + have_roles := members_to_roles[member] + + potentially_conflicting_roles := have_roles & conflicting_roles + count(potentially_conflicting_roles) > 1 + + message := sprintf("IAM policy for %v grants %v to %v", [asset.name, concat(",", potentially_conflicting_roles), member]) + + metadata := { + "resource": asset.name, + "member": member, + "roles": potentially_conflicting_roles, + } +} diff --git a/validator/iam_role_separation_test.rego b/validator/iam_role_separation_test.rego new file mode 100644 index 00000000..f35c8236 --- /dev/null +++ b/validator/iam_role_separation_test.rego @@ -0,0 +1,34 @@ +# +# 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. +# + +package templates.gcp.GCPIAMRoleSeparationConstraintV1 + +import data.validator.gcp.lib as lib + +all_violations[violation] { + resource := data.test.fixtures.iam_role_separation.assets[_] + constraint := data.test.fixtures.iam_role_separation.constraints + + issues := deny with input.asset as resource + with input.constraint as constraint + + violation := issues[_] +} + +test_violations_basic { + violation_resources := {r | r = all_violations[_].details.resource} + violation_resources == {"//cloudresourcemanager.googleapis.com/projects/12345"} +} diff --git a/validator/test/fixtures/iam_role_separation/assets/data.json b/validator/test/fixtures/iam_role_separation/assets/data.json new file mode 100644 index 00000000..f59ddba0 --- /dev/null +++ b/validator/test/fixtures/iam_role_separation/assets/data.json @@ -0,0 +1,46 @@ +[ + { + "name": "//cloudresourcemanager.googleapis.com/projects/12345", + "asset_type": "cloudresourcemanager.googleapis.com/Project", + "iam_policy": { + "version": 1, + "etag": "CdWC1qPLfdw=", + "bindings": [ + { + "role": "roles/iam.serviceAccountUser", + "members": [ + "user:bad@company.com" + ] + }, + { + "role": "roles/iam.serviceAccountAdmin", + "members": [ + "user:bad@company.com" + ] + } + ] + } + }, + { + "name": "//cloudresourcemanager.googleapis.com/projects/12346", + "asset_type": "cloudresourcemanager.googleapis.com/Project", + "iam_policy": { + "version": 1, + "etag": "CdWC1qPLfdw=", + "bindings": [ + { + "role": "roles/iam.serviceAccountUser", + "members": [ + "user:bad@company.com" + ] + }, + { + "role": "roles/iam.serviceAccountAdmin", + "members": [ + "user:good@company.com" + ] + } + ] + } + } +] diff --git a/validator/test/fixtures/iam_role_separation/constraints/data.yaml b/validator/test/fixtures/iam_role_separation/constraints/data.yaml new file mode 100644 index 00000000..3273902d --- /dev/null +++ b/validator/test/fixtures/iam_role_separation/constraints/data.yaml @@ -0,0 +1,12 @@ +apiVersion: constraints.gatekeeper.sh/v1alpha1 +kind: GCPIAMRoleSeparationConstraintV1 +metadata: + name: ensure_service_account_role_separation + annotations: + description: Prevent public users from having access to both administer and user service accounts at the same time +spec: + severity: high + parameters: + roles: + - - roles/iam.serviceAccountUser + - roles/iam.serviceAccountAdmin