diff --git a/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/rule.yml b/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/rule.yml new file mode 100644 index 00000000000..0970c2df91b --- /dev/null +++ b/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/rule.yml @@ -0,0 +1,52 @@ +documentation_complete: true + +title: 'Ensure no ClusterRoleBindings set for default Service Account' + +description: |- + Using the default service account prevents accurate application + rights review and audit tracing. Instead of default, create + a new and unique service account and associate the required ClusterRoleBindings. + +rationale: |- + Kubernetes provides a default service account which is used by + cluster workloads where no specific service account is assigned to the pod. + Where access to the Kubernetes API from a pod is required, a specific service account + should be created for that pod, and rights granted to that service account. + This increases auditability of service account rights and access making it + easier and more accurate to trace potential malicious behaviors to a specific + service account and project. + +severity: medium + +identifiers: {} + +references: + bsi: APP.4.4.A9 + +{{% set jqfilter = '[.items[] | select ( .subjects[]?.name == "default" ) | select(.subjects[].namespace | startswith("kube-") or startswith("openshift-") | not) | .metadata.name ] | unique' %}} + +ocil_clause: 'default service account is given permissions using ClusterRoleBindings' + +ocil: |- + Run the following command to retrieve a list of ClusterRoleBindings that are + associated to the default service account: +
$ oc get clusterrolebindings -o json | jq '{{{ jqfilter }}}'
+ There should be no ClusterRoleBindings associated with the the default service account + in any namespace. + +warnings: +- general: |- + {{{ openshift_filtered_cluster_setting({'/apis/rbac.authorization.k8s.io/v1/clusterrolebindings?limit=10000': jqfilter}) | indent(4) }}} + +template: + name: yamlfile_value + vars: + ocp_data: "true" + filepath: |- + {{{ openshift_filtered_path('/apis/rbac.authorization.k8s.io/v1/clusterrolebindings?limit=10000', jqfilter) }}} + yamlpath: "[:]" + check_existence: "none_exist" + entity_check: "all" + values: + - value: "(.*?)" + operation: "pattern match" diff --git a/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/clusterrolebindings_other_serviceaccounts.pass.sh b/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/clusterrolebindings_other_serviceaccounts.pass.sh new file mode 100644 index 00000000000..a4e4c7604bb --- /dev/null +++ b/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/clusterrolebindings_other_serviceaccounts.pass.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# remediation = none +# packages = jq + +kube_apipath="/kubernetes-api-resources" +mkdir -p "$kube_apipath/apis/rbac.authorization.k8s.io/v1" +crb_apipath="/apis/rbac.authorization.k8s.io/v1/clusterrolebindings?limit=10000" + +cat < "$kube_apipath$crb_apipath" +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "creationTimestamp": "2023-11-07T10:07:21Z", + "labels": { + "app": "controller.csi.trident.netapp.io", + "k8s_version": "v1.25.14", + "trident_version": "v23.01.0" + }, + "name": "trident-controller", + "ownerReferences": [ + { + "apiVersion": "trident.netapp.io/v1", + "controller": true, + "kind": "TridentOrchestrator", + "name": "trident", + "uid": "eb7fe92c-3378-4d27-98a1-d4b543772e3c" + } + ], + "resourceVersion": "989379693", + "uid": "8eab8cd7-8fc3-41a7-bd2a-1c9f6f7c2d8b" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "trident-controller" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "trident-controller", + "namespace": "trident" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "annotations": { + "rbac.authorization.kubernetes.io/autoupdate": "false" + }, + "creationTimestamp": "2021-01-04T14:30:26Z", + "labels": { + "app.kubernetes.io/instance": "roles" + }, + "name": "self-provisioners", + "resourceVersion": "268316753", + "uid": "7c5ad812-cab1-40bb-88dc-a014015f7ede" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "self-provisioner" + } + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "creationTimestamp": "2021-01-04T14:49:26Z", + "name": "prometheus-k8s", + "resourceVersion": "39547", + "uid": "57de031f-a360-483d-903f-19f0fb597cc9" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "prometheus-k8s" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "prometheus-k8s", + "namespace": "openshift-monitoring" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "creationTimestamp": "2021-01-04T14:27:59Z", + "name": "system:openshift:openshift-apiserver", + "resourceVersion": "5623", + "uid": "fe1c3593-7e89-4b2c-b47b-a6569f47d88f" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "cluster-admin" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "openshift-apiserver-sa", + "namespace": "openshift-apiserver" + } + ] + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "" + } +} +EOF + +jq_filter='[.items[] | select ( .subjects[]?.name == "default" ) | select(.subjects[].namespace | startswith("kube-") or startswith("openshift-") | not) | .metadata.name ] | unique' + +# Get file path. This will actually be read by the scan +filteredpath="$kube_apipath$crb_apipath#$(echo -n "$crb_apipath$jq_filter" | sha256sum | awk '{print $1}')" + +# populate filtered path with jq-filtered result +jq "$jq_filter" "$kube_apipath$crb_apipath" > "$filteredpath" diff --git a/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/clusterrolebindings_serviceaccounts.fail.sh b/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/clusterrolebindings_serviceaccounts.fail.sh new file mode 100644 index 00000000000..b65086aad90 --- /dev/null +++ b/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/clusterrolebindings_serviceaccounts.fail.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# remediation = none +# packages = jq + +kube_apipath="/kubernetes-api-resources" +mkdir -p "$kube_apipath/apis/rbac.authorization.k8s.io/v1" +crb_apipath="/apis/rbac.authorization.k8s.io/v1/clusterrolebindings?limit=10000" + +# This file assumes that we dont have any clusterrolebindings. +cat < "$kube_apipath$crb_apipath" +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "creationTimestamp": "2023-11-07T10:07:21Z", + "labels": { + "app": "controller.csi.trident.netapp.io", + "k8s_version": "v1.25.14", + "trident_version": "v23.01.0" + }, + "name": "trident-controller", + "ownerReferences": [ + { + "apiVersion": "trident.netapp.io/v1", + "controller": true, + "kind": "TridentOrchestrator", + "name": "trident", + "uid": "eb7fe92c-3378-4d27-98a1-d4b543772e3c" + } + ], + "resourceVersion": "989379693", + "uid": "8eab8cd7-8fc3-41a7-bd2a-1c9f6f7c2d8b" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "trident-controller" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "trident-controller", + "namespace": "trident" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "creationTimestamp": "2021-01-18T15:31:25Z", + "name": "system:openshift:scc:anyuid", + "resourceVersion": "5666124", + "uid": "a85b6fd7-2d53-4e98-9c12-afc8d2e0896f" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "system:openshift:scc:anyuid" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "default", + "namespace": "app1" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": { + "creationTimestamp": "2021-08-10T10:35:31Z", + "name": "ds-operator-manager-rolebinding", + "resourceVersion": "1116129289", + "uid": "0deb4996-a03c-4428-8135-5468cb218c8f" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "ds-operator-manager-role" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "default", + "namespace": "ds-system" + }, + { + "kind": "ServiceAccount", + "name": "default", + "namespace": "magic-controller" + } + ] + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "" + } +} +EOF + + +jq_filter='[.items[] | select ( .subjects[]?.name == "default" ) | select(.subjects[].namespace | startswith("kube-") or startswith("openshift-") | not) | .metadata.name ] | unique' + +# Get file path. This will actually be read by the scan +filteredpath="$kube_apipath$crb_apipath#$(echo -n "$crb_apipath$jq_filter" | sha256sum | awk '{print $1}')" + +# populate filtered path with jq-filtered result +jq "$jq_filter" "$kube_apipath$crb_apipath" > "$filteredpath" diff --git a/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/no_clusterrolebindings.pass.sh b/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/no_clusterrolebindings.pass.sh new file mode 100644 index 00000000000..f62a0669976 --- /dev/null +++ b/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/no_clusterrolebindings.pass.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# remediation = none +# packages = jq + +kube_apipath="/kubernetes-api-resources" +mkdir -p "$kube_apipath/apis/rbac.authorization.k8s.io/v1" +crb_apipath="/apis/rbac.authorization.k8s.io/v1/clusterrolebindings?limit=10000" + +# This file assumes that we dont have any clusterrolebindings. +cat < "$kube_apipath$crb_apipath" +{ + "apiVersion": "v1", + "items": [], + "kind": "List", + "metadata": { + "resourceVersion": "", + "selfLink": "" + } +} +EOF + + +jq_filter='[.items[] | select ( .subjects[]?.name == "default" ) | select(.subjects[].namespace | startswith("kube-") or startswith("openshift-") | not) | .metadata.name ] | unique' + +# Get file path. This will actually be read by the scan +filteredpath="$kube_apipath$crb_apipath#$(echo -n "$crb_apipath$jq_filter" | sha256sum | awk '{print $1}')" + +# populate filtered path with jq-filtered result +jq "$jq_filter" "$kube_apipath$crb_apipath" > "$filteredpath" diff --git a/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/ocp4/e2e.yml b/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/ocp4/e2e.yml new file mode 100644 index 00000000000..b49fd368b98 --- /dev/null +++ b/applications/openshift/accounts/accounts_no_clusterrolebindings_default_service_account/tests/ocp4/e2e.yml @@ -0,0 +1,2 @@ +--- +default_result: PASS diff --git a/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/rule.yml b/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/rule.yml new file mode 100644 index 00000000000..4726aa0471a --- /dev/null +++ b/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/rule.yml @@ -0,0 +1,52 @@ +documentation_complete: true + +title: 'Ensure no RoleBindings set for default Service Account' + +description: |- + Using the default service account prevents accurate application + rights review and audit tracing. Instead of default, create + a new and unique service account and associate the required RoleBindings. + +rationale: |- + Kubernetes provides a default service account which is used by + cluster workloads where no specific service account is assigned to the pod. + Where access to the Kubernetes API from a pod is required, a specific service account + should be created for that pod, and rights granted to that service account. + This increases auditability of service account rights and access making it + easier and more accurate to trace potential malicious behaviors to a specific + service account and project. + +severity: medium + +identifiers: {} + +references: + bsi: APP.4.4.A9 + +{{% set jqfilter = '[.items[] | select(.metadata.namespace | startswith("kube-") or startswith("openshift-") | not) | select ( .subjects[]?.name == "default" ) | .metadata.namespace + "/" + .metadata.name ] | unique' %}} + +ocil_clause: 'default service account is given permissions using RoleBindings' + +ocil: |- + Run the following command to retrieve a list of RoleBindings that are + associated to the default service account: +
$ oc get rolebindings --all-namespaces -o json | jq '{{{ jqfilter }}}'
+ There should be no RoleBindings associated with the the default service account + in any namespace. + +warnings: +- general: |- + {{{ openshift_filtered_cluster_setting({'/apis/rbac.authorization.k8s.io/v1/rolebindings?limit=10000': jqfilter}) | indent(4) }}} + +template: + name: yamlfile_value + vars: + ocp_data: "true" + filepath: |- + {{{ openshift_filtered_path('/apis/rbac.authorization.k8s.io/v1/rolebindings?limit=10000', jqfilter) }}} + yamlpath: "[:]" + check_existence: "none_exist" + entity_check: "all" + values: + - value: "(.*?)" + operation: "pattern match" diff --git a/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/no_rolebindings.pass.sh b/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/no_rolebindings.pass.sh new file mode 100644 index 00000000000..9d502ad1951 --- /dev/null +++ b/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/no_rolebindings.pass.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# remediation = none +# packages = jq + +kube_apipath="/kubernetes-api-resources" +mkdir -p "$kube_apipath/apis/rbac.authorization.k8s.io/v1" +crb_apipath="/apis/rbac.authorization.k8s.io/v1/rolebindings?limit=10000" + +# This file assumes that we dont have any rolebindings. +cat < "$kube_apipath$crb_apipath" +{ + "apiVersion": "v1", + "items": [], + "kind": "List", + "metadata": { + "resourceVersion": "", + "selfLink": "" + } +} +EOF + + +jq_filter='[.items[] | select(.metadata.namespace | startswith("kube-") or startswith("openshift-") | not) | select ( .subjects[]?.name == "default" ) | .metadata.namespace + "/" + .metadata.name ] | unique' + +# Get file path. This will actually be read by the scan +filteredpath="$kube_apipath$crb_apipath#$(echo -n "$crb_apipath$jq_filter" | sha256sum | awk '{print $1}')" + +# populate filtered path with jq-filtered result +jq "$jq_filter" "$kube_apipath$crb_apipath" > "$filteredpath" diff --git a/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/ocp4/e2e.yml b/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/ocp4/e2e.yml new file mode 100644 index 00000000000..b49fd368b98 --- /dev/null +++ b/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/ocp4/e2e.yml @@ -0,0 +1,2 @@ +--- +default_result: PASS diff --git a/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/rolebindings_other_serviceaccounts.pass.sh b/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/rolebindings_other_serviceaccounts.pass.sh new file mode 100644 index 00000000000..d3dabeb52a2 --- /dev/null +++ b/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/rolebindings_other_serviceaccounts.pass.sh @@ -0,0 +1,174 @@ +#!/bin/bash +# remediation = none +# packages = jq + +kube_apipath="/kubernetes-api-resources" +mkdir -p "$kube_apipath/apis/rbac.authorization.k8s.io/v1" +crb_apipath="/apis/rbac.authorization.k8s.io/v1/rolebindings?limit=10000" + +# This file assumes that we dont have any rolebindings. +cat < "$kube_apipath$crb_apipath" +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "annotations": { + "openshift.io/description": "Allows deploymentconfigs in this namespace to rollout pods in this namespace. It is auto-managed by a controller; remove subjects to disable." + }, + "creationTimestamp": "2021-01-06T13:19:28Z", + "name": "system:deployers", + "namespace": "trident", + "resourceVersion": "843465", + "uid": "3399b7c0-d962-4ad8-8de4-c04dab0dfd2b" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "system:deployer" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "deployer", + "namespace": "trident" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "annotations": { + "openshift.io/description": "Allows builds in this namespace to push images to this namespace. It is auto-managed by a controller; remove subjects to disable." + }, + "creationTimestamp": "2021-01-06T13:19:28Z", + "name": "system:image-builders", + "namespace": "trident", + "resourceVersion": "843464", + "uid": "58fe54b0-4954-4d65-a479-a4e19853df37" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "system:image-builder" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "builder", + "namespace": "trident" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "annotations": { + "openshift.io/description": "Allows all pods in this namespace to pull images from this namespace. It is auto-managed by a controller; remove subjects to disable." + }, + "creationTimestamp": "2021-01-06T13:19:28Z", + "name": "system:image-pullers", + "namespace": "trident", + "resourceVersion": "843445", + "uid": "3e83e155-123b-4b83-af19-22e996f7b96f" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "system:image-puller" + }, + "subjects": [ + { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Group", + "name": "system:serviceaccounts:trident" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "creationTimestamp": "2023-11-07T10:07:21Z", + "labels": { + "app": "controller.csi.trident.netapp.io" + }, + "name": "trident-controller", + "namespace": "trident", + "ownerReferences": [ + { + "apiVersion": "trident.netapp.io/v1", + "controller": true, + "kind": "TridentOrchestrator", + "name": "trident", + "uid": "eb7fe92c-3378-4d27-98a1-d4b543772e3c" + } + ], + "resourceVersion": "989379714", + "uid": "80a292a1-660e-41e2-91f9-3a6758b68b06" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "trident-controller" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "trident-controller" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "creationTimestamp": "2023-10-05T11:21:08Z", + "labels": { + "app": "node.csi.trident.netapp.io" + }, + "name": "trident-node-linux", + "namespace": "trident", + "ownerReferences": [ + { + "apiVersion": "trident.netapp.io/v1", + "controller": true, + "kind": "TridentOrchestrator", + "name": "trident", + "uid": "eb7fe92c-3378-4d27-98a1-d4b543772e3c" + } + ], + "resourceVersion": "947943755", + "uid": "f53187bd-4a6d-4ce8-9469-66e4f0fbb0c1" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "trident-node-linux" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "trident-node-linux" + } + ] + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "" + } +} +EOF + + +jq_filter='[.items[] | select(.metadata.namespace | startswith("kube-") or startswith("openshift-") | not) | select ( .subjects[]?.name == "default" ) | .metadata.namespace + "/" + .metadata.name ] | unique' +# Get file path. This will actually be read by the scan +filteredpath="$kube_apipath$crb_apipath#$(echo -n "$crb_apipath$jq_filter" | sha256sum | awk '{print $1}')" + +# populate filtered path with jq-filtered result +jq "$jq_filter" "$kube_apipath$crb_apipath" > "$filteredpath" diff --git a/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/rolebindings_serviceaccounts.fail.sh b/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/rolebindings_serviceaccounts.fail.sh new file mode 100644 index 00000000000..088d13f9a4b --- /dev/null +++ b/applications/openshift/accounts/accounts_no_rolebindings_default_service_account/tests/rolebindings_serviceaccounts.fail.sh @@ -0,0 +1,182 @@ +#!/bin/bash +# remediation = none +# packages = jq + +kube_apipath="/kubernetes-api-resources" +mkdir -p "$kube_apipath/apis/rbac.authorization.k8s.io/v1" +crb_apipath="/apis/rbac.authorization.k8s.io/v1/rolebindings?limit=10000" + +# This file assumes that we dont have any rolebindings. +cat < "$kube_apipath$crb_apipath" +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "creationTimestamp": "2022-01-26T13:38:26Z", + "name": "nexus-operator.v0.6.0", + "namespace": "nexus", + "ownerReferences": [ + { + "apiVersion": "operators.coreos.com/v2", + "blockOwnerDeletion": false, + "controller": true, + "kind": "OperatorCondition", + "name": "nexus-operator.v0.6.0", + "uid": "762911b0-de01-46ca-8230-9fc68e0cb3c0" + } + ], + "resourceVersion": "268263243", + "uid": "9059f140-d133-4c28-a62a-7b0c37ccdb75" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "nexus-operator.v0.6.0" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "default" + }, + { + "kind": "ServiceAccount", + "name": "default" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "creationTimestamp": "2022-01-26T13:38:27Z", + "labels": { + "olm.owner": "nexus-operator.v0.6.0", + "olm.owner.kind": "ClusterServiceVersion", + "olm.owner.namespace": "nexus", + "operators.coreos.com/nexus-operator-m88i.nexus": "" + }, + "name": "nexus-operator.v0.6.0-default-6b88497c67", + "namespace": "nexus", + "ownerReferences": [ + { + "apiVersion": "operators.coreos.com/v1alpha1", + "blockOwnerDeletion": false, + "controller": false, + "kind": "ClusterServiceVersion", + "name": "nexus-operator.v0.6.0", + "uid": "7e146fba-7dcc-4f03-a2d1-b4e21978ca4c" + } + ], + "resourceVersion": "268263381", + "uid": "b15ac38a-e9c2-4a98-bcc6-62ae3466ca5b" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "nexus-operator.v0.6.0-default-6b88497c67" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "default", + "namespace": "nexus" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "annotations": { + "openshift.io/description": "Allows deploymentconfigs in this namespace to rollout pods in this namespace. It is auto-managed by a controller; remove subjects to disable." + }, + "creationTimestamp": "2021-07-27T08:31:13Z", + "name": "system:deployers", + "namespace": "nexus", + "resourceVersion": "114455275", + "uid": "9806c445-5476-4f12-8ade-5078514aaaf9" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "system:deployer" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "deployer", + "namespace": "nexus" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "annotations": { + "openshift.io/description": "Allows builds in this namespace to push images to this namespace. It is auto-managed by a controller; remove subjects to disable." + }, + "creationTimestamp": "2021-07-27T08:31:13Z", + "name": "system:image-builders", + "namespace": "nexus", + "resourceVersion": "114455272", + "uid": "802555f7-136c-4739-b7cd-6d0c038acd8a" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "system:image-builder" + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "builder", + "namespace": "nexus" + } + ] + }, + { + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": { + "annotations": { + "openshift.io/description": "Allows all pods in this namespace to pull images from this namespace. It is auto-managed by a controller; remove subjects to disable." + }, + "creationTimestamp": "2021-07-27T08:31:13Z", + "name": "system:image-pullers", + "namespace": "nexus", + "resourceVersion": "114455261", + "uid": "2980bbfd-ab40-4576-843c-864386deffa4" + }, + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "system:image-puller" + }, + "subjects": [ + { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Group", + "name": "system:serviceaccounts:nexus" + } + ] + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "" + } +} +EOF + + +jq_filter='[.items[] | select(.metadata.namespace | startswith("kube-") or startswith("openshift-") | not) | select ( .subjects[]?.name == "default" ) | .metadata.namespace + "/" + .metadata.name ] | unique' + +# Get file path. This will actually be read by the scan +filteredpath="$kube_apipath$crb_apipath#$(echo -n "$crb_apipath$jq_filter" | sha256sum | awk '{print $1}')" + +# populate filtered path with jq-filtered result +jq "$jq_filter" "$kube_apipath$crb_apipath" > "$filteredpath" diff --git a/applications/openshift/accounts/accounts_restrict_service_account_tokens/rule.yml b/applications/openshift/accounts/accounts_restrict_service_account_tokens/rule.yml index d600683ecc0..fdb1062a7cb 100644 --- a/applications/openshift/accounts/accounts_restrict_service_account_tokens/rule.yml +++ b/applications/openshift/accounts/accounts_restrict_service_account_tokens/rule.yml @@ -17,6 +17,7 @@ rationale: |- severity: medium references: + bsi: APP.4.4.A9 cis@ocp4: 5.1.6 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/accounts/accounts_unique_service_account/rule.yml b/applications/openshift/accounts/accounts_unique_service_account/rule.yml index e50e7997c82..c0a0763a1dc 100644 --- a/applications/openshift/accounts/accounts_unique_service_account/rule.yml +++ b/applications/openshift/accounts/accounts_unique_service_account/rule.yml @@ -23,6 +23,7 @@ rationale: |- severity: medium references: + bsi: APP.4.4.A9 cis@ocp4: 5.1.5 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/general/liveness_readiness_probe_in_workload/rule.yml b/applications/openshift/general/liveness_readiness_probe_in_workload/rule.yml new file mode 100644 index 00000000000..40c5d783e2a --- /dev/null +++ b/applications/openshift/general/liveness_readiness_probe_in_workload/rule.yml @@ -0,0 +1,34 @@ +title: Ensure that all workloads have liveness and readiness probes + +description: |- + Configuring Kubernetes liveness and readiness probes is essential for ensuring the security and + reliability of a system. These probes actively monitor container health and readiness, facilitating + automatic actions like restarting or rescheduling unresponsive instances for improved reliability. + They play a proactive role in issue detection, allowing timely problem resolution and contribute + to efficient scaling and traffic distribution. + +rationale: |- + Many applications running for long periods of time eventually transition to broken states, and + cannot recover except by being restarted. Kubernetes provides liveness probes to detect and remedy + such situations. + Sometimes, applications are temporarily unable to serve traffic. For example, an application might + need to load large data or configuration files during startup, or depend on external services after + startup. In such cases, you don't want to kill the application, but you don't want to send it + requests either. Kubernetes provides readiness probes to detect and mitigate these situations. + A pod with containers reporting that they are not ready does not receive traffic through Kubernetes + Services. + +references: + bsi: APP.4.4.A11 + +severity: medium + +ocil_clause: 'Liveness or readiness probe is not set' + +ocil: |- + Run the following command to retrieve a list of deployments, daemonsets and statefulsets that + do not have liveness or readiness probes set for their containers: +
$ oc get deployments,statefulsets,daemonsets --all-namespaces -o json | jq '[ .items[] | select(.metadata.namespace | startswith("kube-") or startswith("openshift-") | not) | select( .spec.template.spec.containers[].readinessProbe != null and .spec.template.spec.containers[].livenessProbe != null ) | "\(.kind): \(.metadata.namespace)/\(.metadata.name)" ] | unique'
+ + Make sure that there is output nothing in the result or there are valid reason not to set a + readiness or liveness probe for those workloads. diff --git a/applications/openshift/general/liveness_readiness_probe_in_workload/tests/ocp4/e2e.yml b/applications/openshift/general/liveness_readiness_probe_in_workload/tests/ocp4/e2e.yml new file mode 100644 index 00000000000..69a7d085eb4 --- /dev/null +++ b/applications/openshift/general/liveness_readiness_probe_in_workload/tests/ocp4/e2e.yml @@ -0,0 +1,2 @@ +--- +default_result: MANUAL diff --git a/applications/openshift/rbac/rbac_least_privilege/rule.yml b/applications/openshift/rbac/rbac_least_privilege/rule.yml index 7b7c7f06184..5dce32016e2 100644 --- a/applications/openshift/rbac/rbac_least_privilege/rule.yml +++ b/applications/openshift/rbac/rbac_least_privilege/rule.yml @@ -26,7 +26,7 @@ identifiers: cce@ocp4: CCE-90678-4 references: - bsi: APP.4.4.A3,APP.4.4.A7 + bsi: APP.4.4.A3,APP.4.4.A7,APP.4.4.A9 cis@ocp4: 5.2.10 nist: AC-3,CM-5(6),IA-2,IA-2(5),AC-6(10),CM-11(2),CM-5(1),CM-7(5)(b) srg: SRG-APP-000033-CTR-000090,SRG-APP-000033-CTR-000095,SRG-APP-000033-CTR-000100,SRG-APP-000133-CTR-000290,SRG-APP-000133-CTR-000295,SRG-APP-000133-CTR-000300,SRG-APP-000133-CTR-000305,SRG-APP-000133-CTR-000310,SRG-APP-000148-CTR-000350,SRG-APP-000153-CTR-000375,SRG-APP-000340-CTR-000770,SRG-APP-000378-CTR-000880,SRG-APP-000378-CTR-000885,SRG-APP-000378-CTR-000890,SRG-APP-000380-CTR-000900,SRG-APP-000386-CTR-000920 diff --git a/applications/openshift/rbac/rbac_wildcard_use/rule.yml b/applications/openshift/rbac/rbac_wildcard_use/rule.yml index 9e589e15bce..2778d8061ae 100644 --- a/applications/openshift/rbac/rbac_wildcard_use/rule.yml +++ b/applications/openshift/rbac/rbac_wildcard_use/rule.yml @@ -20,6 +20,7 @@ rationale: |- severity: medium references: + bsi: APP.4.4.A9 cis@ocp4: 5.1.3 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/scc/scc_drop_container_capabilities/rule.yml b/applications/openshift/scc/scc_drop_container_capabilities/rule.yml index 7ed4e5dfde8..e883fb90269 100644 --- a/applications/openshift/scc/scc_drop_container_capabilities/rule.yml +++ b/applications/openshift/scc/scc_drop_container_capabilities/rule.yml @@ -20,6 +20,7 @@ rationale: |- severity: medium references: + bsi: APP.4.4.A9 cis@ocp4: 5.2.9 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/scc/scc_limit_container_allowed_capabilities/rule.yml b/applications/openshift/scc/scc_limit_container_allowed_capabilities/rule.yml index 2fd0ad6e779..a00e5611eb1 100644 --- a/applications/openshift/scc/scc_limit_container_allowed_capabilities/rule.yml +++ b/applications/openshift/scc/scc_limit_container_allowed_capabilities/rule.yml @@ -50,6 +50,7 @@ rationale: |- severity: medium references: + bsi: APP.4.4.A9 cis@ocp4: 5.2.8 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/scc/scc_limit_host_dir_volume_plugin/rule.yml b/applications/openshift/scc/scc_limit_host_dir_volume_plugin/rule.yml index 7cf4a76a2f6..2a1f2bb877e 100644 --- a/applications/openshift/scc/scc_limit_host_dir_volume_plugin/rule.yml +++ b/applications/openshift/scc/scc_limit_host_dir_volume_plugin/rule.yml @@ -21,7 +21,7 @@ identifiers: cce@ocp4: CCE-86255-7 references: - bsi: APP.4.4.A4 + bsi: APP.4.4.A4,APP.4.4.A9 cis@ocp4: 5.2.12 nist: AC-6,AC-6(1) srg: SRG-APP-000142-CTR-000330 diff --git a/applications/openshift/scc/scc_limit_host_ports/rule.yml b/applications/openshift/scc/scc_limit_host_ports/rule.yml index c015e319d07..a211cb16e0e 100644 --- a/applications/openshift/scc/scc_limit_host_ports/rule.yml +++ b/applications/openshift/scc/scc_limit_host_ports/rule.yml @@ -24,6 +24,7 @@ identifiers: cce@ocp4: CCE-86205-2 references: + bsi: APP.4.4.A9 nist: CM-6,CM-6(1) srg: SRG-APP-000142-CTR-000330 diff --git a/applications/openshift/scc/scc_limit_ipc_namespace/rule.yml b/applications/openshift/scc/scc_limit_ipc_namespace/rule.yml index e521a48e1ba..e8bc677ac73 100644 --- a/applications/openshift/scc/scc_limit_ipc_namespace/rule.yml +++ b/applications/openshift/scc/scc_limit_ipc_namespace/rule.yml @@ -21,7 +21,7 @@ identifiers: cce@ocp4: CCE-84042-1 references: - bsi: APP.4.4.A4 + bsi: APP.4.4.A4,APP.4.4.A9 cis@ocp4: 5.2.3 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/scc/scc_limit_net_raw_capability/rule.yml b/applications/openshift/scc/scc_limit_net_raw_capability/rule.yml index ea964b23f46..2548821254d 100644 --- a/applications/openshift/scc/scc_limit_net_raw_capability/rule.yml +++ b/applications/openshift/scc/scc_limit_net_raw_capability/rule.yml @@ -19,7 +19,7 @@ rationale: |- severity: medium references: - bsi: APP.4.4.A4 + bsi: APP.4.4.A4,APP.4.4.A9 cis@ocp4: 5.2.7 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/scc/scc_limit_network_namespace/rule.yml b/applications/openshift/scc/scc_limit_network_namespace/rule.yml index a2744d8021c..bdc31e9a228 100644 --- a/applications/openshift/scc/scc_limit_network_namespace/rule.yml +++ b/applications/openshift/scc/scc_limit_network_namespace/rule.yml @@ -21,7 +21,7 @@ identifiers: cce@ocp4: CCE-83492-9 references: - bsi: APP.4.4.A4 + bsi: APP.4.4.A4,APP.4.4.A9 cis@ocp4: 5.2.4 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/scc/scc_limit_privilege_escalation/rule.yml b/applications/openshift/scc/scc_limit_privilege_escalation/rule.yml index 4d194c37b43..fdb33fc2bf5 100644 --- a/applications/openshift/scc/scc_limit_privilege_escalation/rule.yml +++ b/applications/openshift/scc/scc_limit_privilege_escalation/rule.yml @@ -22,6 +22,7 @@ identifiers: cce@ocp4: CCE-83447-3 references: + bsi: APP.4.4.A9 cis@ocp4: 5.2.5 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/scc/scc_limit_privileged_containers/rule.yml b/applications/openshift/scc/scc_limit_privileged_containers/rule.yml index d4bcc2491c8..763a3807215 100644 --- a/applications/openshift/scc/scc_limit_privileged_containers/rule.yml +++ b/applications/openshift/scc/scc_limit_privileged_containers/rule.yml @@ -18,7 +18,7 @@ rationale: |- severity: medium references: - bsi: APP.4.4.A4 + bsi: APP.4.4.A4,APP.4.4.A9 cis@ocp4: 5.2.1 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/scc/scc_limit_process_id_namespace/rule.yml b/applications/openshift/scc/scc_limit_process_id_namespace/rule.yml index 272c58b177b..3b6b459d74e 100644 --- a/applications/openshift/scc/scc_limit_process_id_namespace/rule.yml +++ b/applications/openshift/scc/scc_limit_process_id_namespace/rule.yml @@ -17,7 +17,7 @@ rationale: |- severity: medium references: - bsi: APP.4.4.A4 + bsi: APP.4.4.A4,APP.4.4.A9 cis@ocp4: 5.2.2 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/applications/openshift/scc/scc_limit_root_containers/rule.yml b/applications/openshift/scc/scc_limit_root_containers/rule.yml index b519f72c0e8..29c4ca3ed4b 100644 --- a/applications/openshift/scc/scc_limit_root_containers/rule.yml +++ b/applications/openshift/scc/scc_limit_root_containers/rule.yml @@ -25,7 +25,7 @@ rationale: |- severity: medium references: - bsi: APP.4.4.A4 + bsi: APP.4.4.A4,APP.4.4.A9 cis@ocp4: 5.2.6 nerc-cip: CIP-003-8 R6,CIP-004-6 R3,CIP-007-3 R6.1 nist: CM-6,CM-6(1) diff --git a/controls/bsi_app_4_4.yml b/controls/bsi_app_4_4.yml index b73e418886e..e1172d7d1e7 100644 --- a/controls/bsi_app_4_4.yml +++ b/controls/bsi_app_4_4.yml @@ -218,14 +218,27 @@ controls: levels: - standard description: >- - The configuration files of a Kubernetes cluster, including all its extensions and applications, + (1) The configuration files of a Kubernetes cluster, including all its extensions and applications, SHOULD be versioned and annotated. - Access rights to configuration file management software SHOULD be granted in a restrictive - manner. Read and write access rights to the configuration files of the control plane SHOULD + (2) Access rights to configuration file management software SHOULD be granted in a restrictive + manner. (3) Read and write access rights to the configuration files of the control plane SHOULD be assigned and restricted with particular care. notes: >- - TBD - status: pending + OpenShift is fully configured using Kubernetes resources including CustomResources (CR). All + resources that are created after the initial cluster installation can be considered configuration + files as described in this control. + + Section 1: This control needs to be adressed on an organizational level. To achieve versioning, + the configuration files should be stored in a Git repository. The Git repository is considered + the only source of truth and provides a visible and auditable trail of changes. To automatically + apply the configuration, GitOps processes and tools like OpenShift GitOps can be used. + + Section 2: This control needs to be adressed in the respective external systems. Access rights + to the Git repository and GitOps controller should be granted in a restrictive manner. + + Section 3: The relevant Kubernetes resources for configuring the control plane are inherently + protected by Kubernetes RBAC and can only be modified by cluster administrators. + status: manual rules: [] - id: APP.4.4.A9 @@ -233,30 +246,59 @@ controls: levels: - standard description: >- - Pods SHOULD NOT use the "default" service account. Rights SHOULD NOT be granted to the - "default" service account. Pods for different applications SHOULD run under their own service - accounts. Access rights for the service accounts of the applications' pods SHOULD be limited + (1) Pods SHOULD NOT use the "default" service account. (2) Rights SHOULD NOT be granted to the + "default" service account. (3) Pods for different applications SHOULD run under their own service + accounts. (4) Access rights for the service accounts of the applications' pods SHOULD be limited to those that are strictly necessary. - Pods that do not require a service account SHOULD not be able to view it or have access to + (5) Pods that do not require a service account SHOULD not be able to view it or have access to corresponding tokens. - Only control plane pods and pods that absolutely need them SHOULD use privileged service + (6) Only control plane pods and pods that absolutely need them SHOULD use privileged service accounts. - Automation programs SHOULD each receive their own tokens, even if they share a common + (7) Automation programs SHOULD each receive their own tokens, even if they share a common service account due to similar tasks. notes: >- - TBD - status: pending - rules: [] + Section 1-5: This needs to be adressed in the individual application deployments. The + associated rules provide additional guidance. + + Section 6: The usage of privileged service accounts is controlled by Security Context + Constraints (SCC), which should be configured and granted according to the principle of least + privilege. + + Section 7: This control needs to be adressed on an organizational level. + status: partial + rules: + # Section 1-3: + - accounts_unique_service_account + # Section 2: + - accounts_no_rolebindings_default_service_account + - accounts_no_clusterrolebindings_default_service_account + # Section 4: + - rbac_least_privilege + - rbac_wildcard_use + # Section 5: + - accounts_restrict_service_account_tokens + # Section 6: + - scc_drop_container_capabilities + - scc_limit_container_allowed_capabilities + - scc_limit_host_dir_volume_plugin + - scc_limit_host_ports + - scc_limit_ipc_namespace + - scc_limit_net_raw_capability + - scc_limit_network_namespace + - scc_limit_privilege_escalation + - scc_limit_privileged_containers + - scc_limit_process_id_namespace + - scc_limit_root_containers - id: APP.4.4.A10 title: Securing Automation Processes levels: - standard description: >- - All automation software processes, such as CI/CD and their pipelines, SHOULD only operate - with the rights that are strictly necessary. If different user groups can change configurations or - start pods via automation software, this SHOULD be done for each group through separate - processes that only have the rights necessary for the respective user group. + (1) All automation software processes, such as CI/CD and their pipelines, SHOULD only operate + with the rights that are strictly necessary. (2) If different user groups can change + configurations or start pods via automation software, this SHOULD be done for each group + through separate processes that only have the rights necessary for the respective user group. notes: >- This control needs to be adressed on an organizational level. All service accounts used by automation software need to adhere to the principle of least privilege. @@ -268,16 +310,22 @@ controls: levels: - standard description: >- - In pods, each container SHOULD define a health check for start-up and operation ("readiness" - and "liveness"). These checks SHOULD provide information about the availability of the - software running in a pod. The checks SHOULD fail if the monitored software cannot perform - its tasks properly. For each of these checks, a time period SHOULD be defined that is - appropriate for the service running in the pod. Based on these checks, Kubernetes SHOULD + (1) In pods, each container SHOULD define a health check for start-up and operation ("readiness" + and "liveness"). (2) These checks SHOULD provide information about the availability of the + software running in a pod. (3) The checks SHOULD fail if the monitored software cannot perform + its tasks properly. (4) For each of these checks, a time period SHOULD be defined that is + appropriate for the service running in the pod. (5) Based on these checks, Kubernetes SHOULD delete or restart the pods. notes: >- - TBD - status: pending - rules: [] + Section 1-3: The existance of readiness und liveness probes can be validated technically. This + check needs to be performed for each container in every pod individually. + Section 4: The adequacy of the checks and the configured time periods needs to be ensured by + the application owner. + Section 5: This functionality is inherently met by OpenShift. + status: manual + rules: + # Section 1-4: + - liveness_readiness_probe_in_workload - id: APP.4.4.A12 title: Securing Infrastructure Applications