From 0d4c4b51c16d92a01a8a0622432a973a780d1fa1 Mon Sep 17 00:00:00 2001 From: mjshastha <61929310+mjshastha@users.noreply.github.com> Date: Fri, 28 Jul 2023 23:27:10 +0530 Subject: [PATCH] Update rego title in OSS defsec Part2 (#1386) * we introduced three new rego checks to the oss. These checks are currently part of the commercial but not included in the oss. Additionally, there are certain checks in the oss where two checks are combined into a single check. However, in the commercial, these checks are treated as two separate checks. Therefore, we will be splitting the combined checks from the oss into two distinct checks to align with the commercial implementation. * fix test * fix tests * fix tests --------- Co-authored-by: mjshastha Co-authored-by: Simar --- .../kubernetes/general/AVD-KSV-0041/docs.md | 2 +- .../kubernetes/general/AVD-KSV-0046/docs.md | 2 +- .../kubernetes/general/AVD-KSV-0112/docs.md | 13 ++ .../kubernetes/general/AVD-KSV-0113/docs.md | 13 ++ .../kubernetes/general/AVD-KSV-0114/docs.md | 13 ++ .../kubernetes/general/AVD-KSV-0115/docs.md | 13 ++ .../kubernetes/general/AVD-KSV-0116/docs.md | 13 ++ pkg/scanners/helm/test/scanner_test.go | 16 +- ...esource.rego => manage_all_resources.rego} | 12 +- .../manage_all_resources_at_namespace.rego | 38 ++++ ..._all_resources_at_the_namespace_test.rego} | 2 +- .../general/manage_all_resources_test.rego | 145 ++++++++++++++++ .../manage_eks_iam_auth_configmap.rego | 43 +++++ .../manage_eks_iam_auth_configmap_test.rego | 153 ++++++++++++++++ .../general/manage_namespace_secrets.rego | 38 ++++ .../manage_namespace_secrets_test.rego | 163 ++++++++++++++++++ .../policies/general/manage_secrets.rego | 10 +- .../policies/general/manage_secrets_test.rego | 18 +- .../manage_webhook_configurations.rego | 40 +++++ .../manage_webhook_configurations_test.rego | 163 ++++++++++++++++++ ...h_a_root_primary_or_supplementary_GID.rego | 56 ++++++ ...oot_primary_or_supplementary_GID_test.rego | 31 ++++ 22 files changed, 966 insertions(+), 31 deletions(-) create mode 100644 avd_docs/kubernetes/general/AVD-KSV-0112/docs.md create mode 100644 avd_docs/kubernetes/general/AVD-KSV-0113/docs.md create mode 100644 avd_docs/kubernetes/general/AVD-KSV-0114/docs.md create mode 100644 avd_docs/kubernetes/general/AVD-KSV-0115/docs.md create mode 100644 avd_docs/kubernetes/general/AVD-KSV-0116/docs.md rename rules/kubernetes/policies/general/{any_resource.rego => manage_all_resources.rego} (59%) create mode 100644 rules/kubernetes/policies/general/manage_all_resources_at_namespace.rego rename rules/kubernetes/policies/general/{any_resource_test.rego => manage_all_resources_at_the_namespace_test.rego} (98%) create mode 100644 rules/kubernetes/policies/general/manage_all_resources_test.rego create mode 100644 rules/kubernetes/policies/general/manage_eks_iam_auth_configmap.rego create mode 100644 rules/kubernetes/policies/general/manage_eks_iam_auth_configmap_test.rego create mode 100644 rules/kubernetes/policies/general/manage_namespace_secrets.rego create mode 100644 rules/kubernetes/policies/general/manage_namespace_secrets_test.rego create mode 100644 rules/kubernetes/policies/general/manage_webhook_configurations.rego create mode 100644 rules/kubernetes/policies/general/manage_webhook_configurations_test.rego create mode 100644 rules/kubernetes/policies/general/runs_with_a_root_primary_or_supplementary_GID.rego create mode 100644 rules/kubernetes/policies/general/runs_with_a_root_primary_or_supplementary_GID_test.rego diff --git a/avd_docs/kubernetes/general/AVD-KSV-0041/docs.md b/avd_docs/kubernetes/general/AVD-KSV-0041/docs.md index 2377040b0..7acf6da1b 100644 --- a/avd_docs/kubernetes/general/AVD-KSV-0041/docs.md +++ b/avd_docs/kubernetes/general/AVD-KSV-0041/docs.md @@ -1,5 +1,5 @@ -Check whether role permits managing secrets +Viewing secrets at the cluster-scope is akin to cluster-admin in most clusters as there are typically at least one service accounts (their token stored in a secret) bound to cluster-admin directly or a role/clusterrole that gives similar permissions. ### Impact diff --git a/avd_docs/kubernetes/general/AVD-KSV-0046/docs.md b/avd_docs/kubernetes/general/AVD-KSV-0046/docs.md index 8bc923402..4257a2aec 100644 --- a/avd_docs/kubernetes/general/AVD-KSV-0046/docs.md +++ b/avd_docs/kubernetes/general/AVD-KSV-0046/docs.md @@ -1,5 +1,5 @@ -Check whether role permits specific verb on wildcard resources +Full control of the cluster resources, and therefore also root on all nodes where workloads can run and has access to all pods, secrets, and data. ### Impact diff --git a/avd_docs/kubernetes/general/AVD-KSV-0112/docs.md b/avd_docs/kubernetes/general/AVD-KSV-0112/docs.md new file mode 100644 index 000000000..dac8568b4 --- /dev/null +++ b/avd_docs/kubernetes/general/AVD-KSV-0112/docs.md @@ -0,0 +1,13 @@ + +Full control of the resources within a namespace. In some cluster configurations, this is excessive. In others, this is normal (a gitops deployment operator like flux) + +### Impact + + + +{{ remediationActions }} + +### Links +- https://kubernetes.io/docs/concepts/security/rbac-good-practices/ + + diff --git a/avd_docs/kubernetes/general/AVD-KSV-0113/docs.md b/avd_docs/kubernetes/general/AVD-KSV-0113/docs.md new file mode 100644 index 000000000..4e6afa661 --- /dev/null +++ b/avd_docs/kubernetes/general/AVD-KSV-0113/docs.md @@ -0,0 +1,13 @@ + +Viewing secrets at the namespace scope can lead to escalation if another service account in that namespace has a higher privileged rolebinding or clusterrolebinding bound. + +### Impact + + + +{{ remediationActions }} + +### Links +- https://kubernetes.io/docs/concepts/security/rbac-good-practices/ + + diff --git a/avd_docs/kubernetes/general/AVD-KSV-0114/docs.md b/avd_docs/kubernetes/general/AVD-KSV-0114/docs.md new file mode 100644 index 000000000..e5f0d5a0e --- /dev/null +++ b/avd_docs/kubernetes/general/AVD-KSV-0114/docs.md @@ -0,0 +1,13 @@ + +Webhooks can silently intercept or actively mutate/block resources as they are being created or updated. This includes secrets and pod specs. + +### Impact + + + +{{ remediationActions }} + +### Links +- https://kubernetes.io/docs/concepts/security/rbac-good-practices/ + + diff --git a/avd_docs/kubernetes/general/AVD-KSV-0115/docs.md b/avd_docs/kubernetes/general/AVD-KSV-0115/docs.md new file mode 100644 index 000000000..aa50f17c0 --- /dev/null +++ b/avd_docs/kubernetes/general/AVD-KSV-0115/docs.md @@ -0,0 +1,13 @@ + +Ability to add AWS IAM to RBAC bindings via special EKS configmap. + +### Impact + + + +{{ remediationActions }} + +### Links +- https://kubernetes.io/docs/concepts/security/rbac-good-practices/ + + diff --git a/avd_docs/kubernetes/general/AVD-KSV-0116/docs.md b/avd_docs/kubernetes/general/AVD-KSV-0116/docs.md new file mode 100644 index 000000000..ef60ecb6a --- /dev/null +++ b/avd_docs/kubernetes/general/AVD-KSV-0116/docs.md @@ -0,0 +1,13 @@ + +According to pod security standard 'Non-root groups', containers should be forbidden from running with a root primary or supplementary GID. + +### Impact + + + +{{ remediationActions }} + +### Links +- https://kubesec.io/basics/containers-securitycontext-runasuser/ + + diff --git a/pkg/scanners/helm/test/scanner_test.go b/pkg/scanners/helm/test/scanner_test.go index 7998c4a79..dd4ed2b92 100644 --- a/pkg/scanners/helm/test/scanner_test.go +++ b/pkg/scanners/helm/test/scanner_test.go @@ -47,7 +47,7 @@ func Test_helm_scanner_with_archive(t *testing.T) { require.NotNil(t, results) failed := results.GetFailed() - assert.Equal(t, 13, len(failed)) + assert.Equal(t, 19, len(failed)) visited := make(map[string]bool) var errorCodes []string @@ -58,7 +58,7 @@ func Test_helm_scanner_with_archive(t *testing.T) { errorCodes = append(errorCodes, id) } } - assert.Len(t, errorCodes, 13) + assert.Len(t, errorCodes, 14) sort.Strings(errorCodes) @@ -67,7 +67,7 @@ func Test_helm_scanner_with_archive(t *testing.T) { "AVD-KSV-0011", "AVD-KSV-0012", "AVD-KSV-0014", "AVD-KSV-0015", "AVD-KSV-0016", "AVD-KSV-0018", "AVD-KSV-0020", "AVD-KSV-0021", "AVD-KSV-0030", - "AVD-KSV-0104", "AVD-KSV-0106", + "AVD-KSV-0104", "AVD-KSV-0106", "AVD-KSV-0116", }, errorCodes) } } @@ -127,7 +127,7 @@ func Test_helm_scanner_with_dir(t *testing.T) { require.NotNil(t, results) failed := results.GetFailed() - assert.Equal(t, 13, len(failed)) + assert.Equal(t, 16, len(failed)) visited := make(map[string]bool) var errorCodes []string @@ -146,7 +146,7 @@ func Test_helm_scanner_with_dir(t *testing.T) { "AVD-KSV-0011", "AVD-KSV-0012", "AVD-KSV-0014", "AVD-KSV-0015", "AVD-KSV-0016", "AVD-KSV-0018", "AVD-KSV-0020", "AVD-KSV-0021", "AVD-KSV-0030", - "AVD-KSV-0104", "AVD-KSV-0106", + "AVD-KSV-0104", "AVD-KSV-0106", "AVD-KSV-0116", }, errorCodes) } } @@ -213,7 +213,7 @@ deny[res] { require.NotNil(t, results) failed := results.GetFailed() - assert.Equal(t, 15, len(failed)) + assert.Equal(t, 21, len(failed)) visited := make(map[string]bool) var errorCodes []string @@ -224,7 +224,7 @@ deny[res] { errorCodes = append(errorCodes, id) } } - assert.Len(t, errorCodes, 14) + assert.Len(t, errorCodes, 15) sort.Strings(errorCodes) @@ -233,7 +233,7 @@ deny[res] { "AVD-KSV-0011", "AVD-KSV-0012", "AVD-KSV-0014", "AVD-KSV-0015", "AVD-KSV-0016", "AVD-KSV-0018", "AVD-KSV-0020", "AVD-KSV-0021", "AVD-KSV-0030", - "AVD-KSV-0104", "AVD-KSV-0106", "AVD-USR-ID001", + "AVD-KSV-0104", "AVD-KSV-0106", "AVD-KSV-0116", "AVD-USR-ID001", }, errorCodes) }) } diff --git a/rules/kubernetes/policies/general/any_resource.rego b/rules/kubernetes/policies/general/manage_all_resources.rego similarity index 59% rename from rules/kubernetes/policies/general/any_resource.rego rename to rules/kubernetes/policies/general/manage_all_resources.rego index d256aa664..e0cfca624 100644 --- a/rules/kubernetes/policies/general/any_resource.rego +++ b/rules/kubernetes/policies/general/manage_all_resources.rego @@ -1,6 +1,6 @@ # METADATA -# title: "No wildcard resource roles" -# description: "Check whether role permits specific verb on wildcard resources" +# title: "Manage all resources" +# description: "Full control of the cluster resources, and therefore also root on all nodes where workloads can run and has access to all pods, secrets, and data." # scope: package # schemas: # - input: schema["kubernetes"] @@ -10,8 +10,8 @@ # id: KSV046 # avd_id: AVD-KSV-0046 # severity: CRITICAL -# short_code: no-wildcard-resource-role -# recommended_action: "Create a role which does not permit specific verb on wildcard resources" +# short_code: no-wildcard-resource-clusterrole +# recommended_actions: "Remove '*' from 'rules.resources'. Provide specific list of resources to be managed by cluster role" # input: # selector: # - type: kubernetes @@ -22,7 +22,7 @@ import data.lib.utils readVerbs := ["create", "update", "delete", "deletecollection", "impersonate", "*", "list", "get"] -readKinds := ["Role", "ClusterRole"] +readKinds := ["ClusterRole"] resourceAllowSpecificVerbOnAnyResource[input.rules[ru]] { some ru, r, v @@ -33,6 +33,6 @@ resourceAllowSpecificVerbOnAnyResource[input.rules[ru]] { deny[res] { badRule := resourceAllowSpecificVerbOnAnyResource[_] - msg := "Role permits specific verb on wildcard resource" + msg := kubernetes.format(sprintf("%s '%s' shouldn't manage all resources", [kubernetes.kind, kubernetes.name])) res := result.new(msg, badRule) } diff --git a/rules/kubernetes/policies/general/manage_all_resources_at_namespace.rego b/rules/kubernetes/policies/general/manage_all_resources_at_namespace.rego new file mode 100644 index 000000000..a2c5d0a9a --- /dev/null +++ b/rules/kubernetes/policies/general/manage_all_resources_at_namespace.rego @@ -0,0 +1,38 @@ +# METADATA +# title: "Manage all resources at the namespace" +# description: "Full control of the resources within a namespace. In some cluster configurations, this is excessive. In others, this is normal (a gitops deployment operator like flux)" +# scope: package +# schemas: +# - input: schema["kubernetes"] +# related_resources: +# - https://kubernetes.io/docs/concepts/security/rbac-good-practices/ +# custom: +# id: KSV112 +# avd_id: AVD-KSV-0112 +# severity: CRITICAL +# short_code: no-wildcard-resource-role +# recommended_actions: "Remove '*' from 'rules.resources'. Provide specific list of resources to be managed by role in namespace" +# input: +# selector: +# - type: kubernetes +package builtin.kubernetes.KSV112 + +import data.lib.kubernetes +import data.lib.utils + +readVerbs := ["create", "update", "delete", "deletecollection", "impersonate", "*", "list", "get"] + +readKinds := ["Role"] + +managingAllResourcesAtNamespace[input.rules[ru]] { + some ru, r, v + input.kind == readKinds[_] + input.rules[ru].resources[r] == "*" + input.rules[ru].verbs[v] == readVerbs[_] +} + +deny[res] { + badRule := managingAllResourcesAtNamespace[_] + msg := kubernetes.format(sprintf("%s '%s' shouldn't manage all resources at the namespace '%s'", [kubernetes.kind, kubernetes.name, kubernetes.namespace])) + res := result.new(msg, badRule) +} diff --git a/rules/kubernetes/policies/general/any_resource_test.rego b/rules/kubernetes/policies/general/manage_all_resources_at_the_namespace_test.rego similarity index 98% rename from rules/kubernetes/policies/general/any_resource_test.rego rename to rules/kubernetes/policies/general/manage_all_resources_at_the_namespace_test.rego index 60e9d0dd4..a0dbf43b6 100644 --- a/rules/kubernetes/policies/general/any_resource_test.rego +++ b/rules/kubernetes/policies/general/manage_all_resources_at_the_namespace_test.rego @@ -1,4 +1,4 @@ -package builtin.kubernetes.KSV046 +package builtin.kubernetes.KSV112 test_resource_verb_role_secrets { r := deny with input as { diff --git a/rules/kubernetes/policies/general/manage_all_resources_test.rego b/rules/kubernetes/policies/general/manage_all_resources_test.rego new file mode 100644 index 000000000..ca41938cc --- /dev/null +++ b/rules/kubernetes/policies/general/manage_all_resources_test.rego @@ -0,0 +1,145 @@ +package builtin.kubernetes.KSV046 + +test_resource_verb_role_secrets { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["*"], + "verbs": ["delete"], + }], + } + + count(r) > 0 +} + +test_resource_verb_role_pods { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["*"], + "verbs": ["deletecollection"], + }], + } + + count(r) > 0 +} + +test_resource_verb_role_deployments { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["*"], + "verbs": ["create"], + }], + } + + count(r) > 0 +} + +test_resource_verb_role_daemonsets { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["*"], + "verbs": ["list"], + }], + } + + count(r) > 0 +} + +test_resource_verb_role_statefulsets { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["*"], + "verbs": ["get"], + }], + } + + count(r) > 0 +} + +test_resource_verb_role_replicationcontrollers { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["*"], + "verbs": ["impersonate"], + }], + } + + count(r) > 0 +} + +test_resource_resource_role_no_specific_verb { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["impersonate"], + "verbs": ["aaa"], + }], + } + + count(r) == 0 +} + +test_resource_verb_role_no_any_verb { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["*"], + "verbs": ["*"], + }], + } + + count(r) > 0 +} diff --git a/rules/kubernetes/policies/general/manage_eks_iam_auth_configmap.rego b/rules/kubernetes/policies/general/manage_eks_iam_auth_configmap.rego new file mode 100644 index 000000000..13f5a35da --- /dev/null +++ b/rules/kubernetes/policies/general/manage_eks_iam_auth_configmap.rego @@ -0,0 +1,43 @@ +# METADATA +# title: "Manage EKS IAM Auth ConfigMap" +# description: "Ability to add AWS IAM to RBAC bindings via special EKS configmap." +# scope: package +# schemas: +# - input: schema["kubernetes"] +# related_resources: +# - https://kubernetes.io/docs/concepts/security/rbac-good-practices/ +# custom: +# id: KSV115 +# avd_id: AVD-KSV-0115 +# severity: CRITICAL +# short_code: eks-iam-configmap +# recommended_actions: "Remove write permission verbs for resource 'configmaps' named 'aws-auth'" +# input: +# selector: +# - type: kubernetes +package builtin.kubernetes.KSV115 + +import data.lib.kubernetes +import data.lib.utils + +readVerbs := ["create", "update", "patch", "delete", "deletecollection", "impersonate", "*"] + +readKinds := ["Role", "ClusterRole"] + +readResource = "configmaps" + +resourceName := "aws-auth" + +manageEKSIAMAuthConfigmap[input.rules[ru]] { + some ru, r, v + input.kind == readKinds[_] + input.rules[ru].resources[r] == readResource + input.rules[ru].verbs[v] == readVerbs[_] + input.rules[ru].resourceNames[rn] == resourceName +} + +deny[res] { + badRule := manageEKSIAMAuthConfigmap[_] + msg := kubernetes.format(sprintf("%s '%s' should not have access to resource '%s' named '%s' for verbs %s", [kubernetes.kind, kubernetes.name, readResource, resourceName, readVerbs])) + res := result.new(msg, badRule) +} diff --git a/rules/kubernetes/policies/general/manage_eks_iam_auth_configmap_test.rego b/rules/kubernetes/policies/general/manage_eks_iam_auth_configmap_test.rego new file mode 100644 index 000000000..b9de1d4c1 --- /dev/null +++ b/rules/kubernetes/policies/general/manage_eks_iam_auth_configmap_test.rego @@ -0,0 +1,153 @@ +package builtin.kubernetes.KSV115 + +test_manageEKSIAMAuthConfigmap_verb_create { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["configmaps"], + "verbs": ["create"], + "resourceNames": ["aws-auth"], + }], + } + + count(r) > 0 +} + +test_manageEKSIAMAuthConfigmap_verb_update { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["configmaps"], + "verbs": ["update"], + "resourceNames": ["aws-auth"], + }], + } + + count(r) > 0 +} + +test_manageEKSIAMAuthConfigmap_verb_patch { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["configmaps"], + "verbs": ["patch"], + "resourceNames": ["aws-auth"], + }], + } + + count(r) > 0 +} + +test_manageEKSIAMAuthConfigmap_verb_delete { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["configmaps"], + "verbs": ["delete"], + "resourceNames": ["aws-auth"], + }], + } + + count(r) > 0 +} + +test_manageEKSIAMAuthConfigmap_verb_deletecollection { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["configmaps"], + "verbs": ["deletecollection"], + "resourceNames": ["aws-auth"], + }], + } + + count(r) > 0 +} + +test_manageEKSIAMAuthConfigmap_verb_impersonate { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["configmaps"], + "verbs": ["impersonate"], + "resourceNames": ["aws-auth"], + }], + } + + count(r) > 0 +} + +test_manageEKSIAMAuthConfigmap_verb_all { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["configmaps"], + "verbs": ["*"], + "resourceNames": ["aws-auth"], + }], + } + + count(r) > 0 +} + +test_manageEKSIAMAuthConfigmap_verb_wrong { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["configmaps"], + "verbs": ["just"], + "resourceNames": ["aws-auth"], + }], + } + + count(r) == 0 +} diff --git a/rules/kubernetes/policies/general/manage_namespace_secrets.rego b/rules/kubernetes/policies/general/manage_namespace_secrets.rego new file mode 100644 index 000000000..46bcbfa97 --- /dev/null +++ b/rules/kubernetes/policies/general/manage_namespace_secrets.rego @@ -0,0 +1,38 @@ +# METADATA +# title: "Manage namespace secrets" +# description: "Viewing secrets at the namespace scope can lead to escalation if another service account in that namespace has a higher privileged rolebinding or clusterrolebinding bound." +# scope: package +# schemas: +# - input: schema["kubernetes"] +# related_resources: +# - https://kubernetes.io/docs/concepts/security/rbac-good-practices/ +# custom: +# id: KSV113 +# avd_id: AVD-KSV-0113 +# severity: Medium +# short_code: no-manage-ns-secrets +# recommended_actions: "Manage namespace secrets are not allowed. Remove resource 'secrets' from role" +# input: +# selector: +# - type: kubernetes +package builtin.kubernetes.KSV113 + +import data.lib.kubernetes +import data.lib.utils + +readVerbs := ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection", "impersonate", "*"] + +readKinds := ["Role"] + +resourceManageSecret[input.rules[ru]] { + some ru, r, v + input.kind == readKinds[_] + input.rules[ru].resources[r] == "secrets" + input.rules[ru].verbs[v] == readVerbs[_] +} + +deny[res] { + badRule := resourceManageSecret[_] + msg := kubernetes.format(sprintf("%s '%s' shouldn't have access to manage secrets in namespace '%s'", [kubernetes.kind, kubernetes.name, kubernetes.namespace])) + res := result.new(msg, badRule) +} diff --git a/rules/kubernetes/policies/general/manage_namespace_secrets_test.rego b/rules/kubernetes/policies/general/manage_namespace_secrets_test.rego new file mode 100644 index 000000000..e164fcc21 --- /dev/null +++ b/rules/kubernetes/policies/general/manage_namespace_secrets_test.rego @@ -0,0 +1,163 @@ +package builtin.kubernetes.KSV113 + +test_manage_secrets { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["secrets"], + "verbs": ["get"], + }], + } + + count(r) > 0 +} + +test_manage_verb_update { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["secrets"], + "verbs": ["update"], + }], + } + + count(r) > 0 +} + +test_manage_verb_list { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["secrets"], + "verbs": ["list"], + }], + } + + count(r) > 0 +} + +test_manage_not_secret_resource { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["secrets1"], + "verbs": ["list"], + }], + } + + count(r) == 0 +} + +test_manage_secret_verb_update { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["secrets"], + "verbs": ["update"], + }], + } + + count(r) > 0 +} + +test_manage_secret_verb_impersonate { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["secrets"], + "verbs": ["impersonate"], + }], + } + + count(r) > 0 +} + +test_manage_secret_verb_deletecollection { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["secrets"], + "verbs": ["deletecollection"], + }], + } + + count(r) > 0 +} + +test_manage_secret_verb_patch { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["secrets"], + "verbs": ["patch"], + }], + } + + count(r) > 0 +} + +test_manage_secret_verb_watch { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["secrets"], + "verbs": ["watch"], + }], + } + + count(r) > 0 +} diff --git a/rules/kubernetes/policies/general/manage_secrets.rego b/rules/kubernetes/policies/general/manage_secrets.rego index dad22a8e0..0087f0d50 100644 --- a/rules/kubernetes/policies/general/manage_secrets.rego +++ b/rules/kubernetes/policies/general/manage_secrets.rego @@ -1,6 +1,6 @@ # METADATA -# title: "Do not allow management of secrets" -# description: "Check whether role permits managing secrets" +# title: "Manage secrets" +# description: "Viewing secrets at the cluster-scope is akin to cluster-admin in most clusters as there are typically at least one service accounts (their token stored in a secret) bound to cluster-admin directly or a role/clusterrole that gives similar permissions." # scope: package # schemas: # - input: schema["kubernetes"] @@ -11,7 +11,7 @@ # avd_id: AVD-KSV-0041 # severity: CRITICAL # short_code: no-manage-secrets -# recommended_action: "Create a role which does not permit to manage secrets if not needed" +# recommended_actions: "Manage secrets are not allowed. Remove resource 'secrets' from cluster role" # input: # selector: # - type: kubernetes @@ -22,7 +22,7 @@ import data.lib.utils readVerbs := ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection", "impersonate", "*"] -readKinds := ["Role", "ClusterRole"] +readKinds := ["ClusterRole"] resourceManageSecret[input.rules[ru]] { some ru, r, v @@ -33,6 +33,6 @@ resourceManageSecret[input.rules[ru]] { deny[res] { badRule := resourceManageSecret[_] - msg := "Role permits management of secret(s)" + msg := kubernetes.format(sprintf("%s '%s' shouldn't have access to manage resource 'secrets'", [kubernetes.kind, kubernetes.name])) res := result.new(msg, badRule) } diff --git a/rules/kubernetes/policies/general/manage_secrets_test.rego b/rules/kubernetes/policies/general/manage_secrets_test.rego index 67f1f65ab..e384d8382 100644 --- a/rules/kubernetes/policies/general/manage_secrets_test.rego +++ b/rules/kubernetes/policies/general/manage_secrets_test.rego @@ -3,7 +3,7 @@ package builtin.kubernetes.KSV041 test_manage_secrets { r := deny with input as { "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "Role", + "kind": "ClusterRole", "metadata": { "namespace": "default", "name": "pod-reader", @@ -21,7 +21,7 @@ test_manage_secrets { test_manage_verb_update { r := deny with input as { "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "Role", + "kind": "ClusterRole", "metadata": { "namespace": "default", "name": "pod-reader", @@ -39,7 +39,7 @@ test_manage_verb_update { test_manage_verb_list { r := deny with input as { "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "Role", + "kind": "ClusterRole", "metadata": { "namespace": "default", "name": "pod-reader", @@ -57,7 +57,7 @@ test_manage_verb_list { test_manage_not_secret_resource { r := deny with input as { "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "Role", + "kind": "ClusterRole", "metadata": { "namespace": "default", "name": "pod-reader", @@ -75,7 +75,7 @@ test_manage_not_secret_resource { test_manage_secret_verb_update { r := deny with input as { "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "Role", + "kind": "ClusterRole", "metadata": { "namespace": "default", "name": "pod-reader", @@ -93,7 +93,7 @@ test_manage_secret_verb_update { test_manage_secret_verb_impersonate { r := deny with input as { "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "Role", + "kind": "ClusterRole", "metadata": { "namespace": "default", "name": "pod-reader", @@ -111,7 +111,7 @@ test_manage_secret_verb_impersonate { test_manage_secret_verb_deletecollection { r := deny with input as { "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "Role", + "kind": "ClusterRole", "metadata": { "namespace": "default", "name": "pod-reader", @@ -129,7 +129,7 @@ test_manage_secret_verb_deletecollection { test_manage_secret_verb_patch { r := deny with input as { "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "Role", + "kind": "ClusterRole", "metadata": { "namespace": "default", "name": "pod-reader", @@ -147,7 +147,7 @@ test_manage_secret_verb_patch { test_manage_secret_verb_watch { r := deny with input as { "apiVersion": "rbac.authorization.k8s.io/v1", - "kind": "Role", + "kind": "ClusterRole", "metadata": { "namespace": "default", "name": "pod-reader", diff --git a/rules/kubernetes/policies/general/manage_webhook_configurations.rego b/rules/kubernetes/policies/general/manage_webhook_configurations.rego new file mode 100644 index 000000000..43c479d8a --- /dev/null +++ b/rules/kubernetes/policies/general/manage_webhook_configurations.rego @@ -0,0 +1,40 @@ +# METADATA +# title: "Manage webhookconfigurations" +# description: "Webhooks can silently intercept or actively mutate/block resources as they are being created or updated. This includes secrets and pod specs." +# scope: package +# schemas: +# - input: schema["kubernetes"] +# related_resources: +# - https://kubernetes.io/docs/concepts/security/rbac-good-practices/ +# custom: +# id: KSV114 +# avd_id: AVD-KSV-0114 +# severity: Critical +# short_code: no-manage-webhook +# recommended_actions: "Remove webhook configuration resouces/verbs, acceptable values for verbs ['get', 'list', 'watch']" +# input: +# selector: +# - type: kubernetes +package builtin.kubernetes.KSV114 + +import data.lib.kubernetes +import data.lib.utils + +readVerbs := ["create", "update", "patch", "delete", "deletecollection", "impersonate", "*"] + +readKinds := ["Role", "ClusterRole"] + +readResource = ["mutatingwebhookconfigurations", "validatingwebhookconfigurations"] + +manageWebhookConfig[input.rules[ru]] { + some ru, r, v + input.kind == readKinds[_] + input.rules[ru].resources[r] == readResource[_] + input.rules[ru].verbs[v] == readVerbs[_] +} + +deny[res] { + badRule := manageWebhookConfig[_] + msg := kubernetes.format(sprintf("%s '%s' should not have access to resources %s for verbs %s", [kubernetes.kind, kubernetes.name, readResource, readVerbs])) + res := result.new(msg, badRule) +} diff --git a/rules/kubernetes/policies/general/manage_webhook_configurations_test.rego b/rules/kubernetes/policies/general/manage_webhook_configurations_test.rego new file mode 100644 index 000000000..20e5707db --- /dev/null +++ b/rules/kubernetes/policies/general/manage_webhook_configurations_test.rego @@ -0,0 +1,163 @@ +package builtin.kubernetes.KSV114 + +test_manageWebhookConfig_verb_create { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["mutatingwebhookconfigurations"], + "verbs": ["create"], + }], + } + + count(r) > 0 +} + +test_manageWebhookConfig_verb_update { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["mutatingwebhookconfigurations"], + "verbs": ["update"], + }], + } + + count(r) > 0 +} + +test_manageWebhookConfig_verb_patch { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["mutatingwebhookconfigurations"], + "verbs": ["patch"], + }], + } + + count(r) > 0 +} + +test_manageWebhookConfig_verb_delete { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["validatingwebhookconfigurations"], + "verbs": ["delete"], + }], + } + + count(r) > 0 +} + +test_manageWebhookConfig_verb_deletecollection { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["validatingwebhookconfigurations"], + "verbs": ["deletecollection"], + }], + } + + count(r) > 0 +} + +test_manageWebhookConfig_verb_impersonate { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "Role", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["validatingwebhookconfigurations"], + "verbs": ["impersonate"], + }], + } + + count(r) > 0 +} + +test_validatingWebhook_manageWebhookConfig_verb_all { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["validatingwebhookconfigurations"], + "verbs": ["*"], + }], + } + + count(r) > 0 +} + +test_mutatingWebhook_manageWebhookConfig_verb_all { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["mutatingwebhookconfigurations"], + "verbs": ["*"], + }], + } + + count(r) > 0 +} + +test_manageWebhookConfig_verb_wrong { + r := deny with input as { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRole", + "metadata": { + "namespace": "default", + "name": "pod-reader", + }, + "rules": [{ + "apiGroups": ["*"], + "resources": ["mutatingwebhookconfigurations"], + "verbs": ["just"], + }], + } + + count(r) == 0 +} diff --git a/rules/kubernetes/policies/general/runs_with_a_root_primary_or_supplementary_GID.rego b/rules/kubernetes/policies/general/runs_with_a_root_primary_or_supplementary_GID.rego new file mode 100644 index 000000000..10b37743e --- /dev/null +++ b/rules/kubernetes/policies/general/runs_with_a_root_primary_or_supplementary_GID.rego @@ -0,0 +1,56 @@ +# METADATA +# title: "Runs with a root primary or supplementary GID" +# description: "According to pod security standard 'Non-root groups', containers should be forbidden from running with a root primary or supplementary GID." +# scope: package +# schemas: +# - input: schema["kubernetes"] +# related_resources: +# - https://kubesec.io/basics/containers-securitycontext-runasuser/ +# custom: +# id: KSV116 +# avd_id: AVD-KSV-0116 +# severity: LOW +# short_code: primary-supplementary-gid +# recommended_actions: "Set 'containers[].securityContext.runAsGroup' to a non-zero integer or leave undefined." +# input: +# selector: +# - type: kubernetes +package builtin.kubernetes.KSV116 + +import data.lib.kubernetes +import data.lib.utils + +default failRootGroupId = false + +# getContainersWithRootGroupId returns a list of containers +# with root group id set +getContainersWithRootGroupId[name] { + container := kubernetes.containers[_] + container.securityContext.runAsGroup == 0 + name := container.name +} + +# failRootGroupId is true if root group id is set on pod +failRootGroupId { + pod := kubernetes.pods[_] + pod.spec.securityContext.runAsGroup == 0 +} + +# failRootGroupId is true if root group id is set on pod +failRootGroupId { + pod := kubernetes.pods[_] + gid := pod.spec.securityContext.supplementalGroups[_] + gid == 0 +} + +# failRootGroupId is true if root group id is set on pod +failRootGroupId { + pod := kubernetes.pods[_] + pod.spec.securityContext.fsGroup == 0 +} + +deny[res] { + output := failRootGroupId + msg := kubernetes.format(sprintf("%s %s in %s namespace should set spec.securityContext.runAsGroup, spec.securityContext.supplementalGroups[*] and spec.securityContext.fsGroup to integer greater than 0", [lower(kubernetes.kind), kubernetes.name, kubernetes.namespace])) + res := result.new(msg, output) +} diff --git a/rules/kubernetes/policies/general/runs_with_a_root_primary_or_supplementary_GID_test.rego b/rules/kubernetes/policies/general/runs_with_a_root_primary_or_supplementary_GID_test.rego new file mode 100644 index 000000000..c1f44dcd2 --- /dev/null +++ b/rules/kubernetes/policies/general/runs_with_a_root_primary_or_supplementary_GID_test.rego @@ -0,0 +1,31 @@ +package builtin.kubernetes.KSV116 + +test_failRootGroupId { + r := deny with input as { + "apiVersion": "v1", + "kind": "Pod", + "metadata": {"name": "hello-gid"}, + "spec": {"securityContext": { + "runAsGroup": 0, + "supplementalGroups": [0], + "fsGroup": 0, + }}, + } + + count(r) > 0 +} + +test_failRootGroupId_failed { + r := deny with input as { + "apiVersion": "v1", + "kind": "Pod", + "metadata": {"name": "hello-gid"}, + "spec": {"securityContext": { + "runAsGroup": 1001, # Non-zero value + "supplementalGroups": [1002], # Non-zero value + "fsGroup": 1003, # Non-zero value + }}, + } + + count(r) > 0 +}