diff --git a/cfg/config.yaml b/cfg/config.yaml index d5d170bbd..c0f22d021 100644 --- a/cfg/config.yaml +++ b/cfg/config.yaml @@ -292,6 +292,7 @@ version_mapping: "ocp-3.10": "rh-0.7" "ocp-3.11": "rh-0.7" "ocp-4.0": "rh-1.0" + "ocp-4.15": "rh-1.6" "aks-1.0": "aks-1.0" "ack-1.0": "ack-1.0" "cis-1.6-k3s": "cis-1.6-k3s" @@ -427,6 +428,12 @@ target_mapping: - "controlplane" - "policies" - "etcd" + "rh-1.6": + - "master" + - "node" + - "controlplane" + - "policies" + - "etcd" "eks-stig-kubernetes-v1r6": - "node" - "controlplane" diff --git a/cfg/rh-1.0/policies.yaml b/cfg/rh-1.0/policies.yaml index e90cd877f..29653c809 100644 --- a/cfg/rh-1.0/policies.yaml +++ b/cfg/rh-1.0/policies.yaml @@ -13,7 +13,7 @@ groups: type: "manual" audit: | #To get a list of users and service accounts with the cluster-admin role - oc get clusterrolebindings -o=customcolumns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].kind | + oc get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].kind | grep cluster-admin #To verity that kbueadmin is removed, no results should be returned oc get secrets kubeadmin -n kube-system diff --git a/cfg/rh-1.6/config.yaml b/cfg/rh-1.6/config.yaml new file mode 100644 index 000000000..b7839455a --- /dev/null +++ b/cfg/rh-1.6/config.yaml @@ -0,0 +1,2 @@ +--- +## Version-specific settings that override the values in cfg/config.yaml diff --git a/cfg/rh-1.6/controlplane.yaml b/cfg/rh-1.6/controlplane.yaml new file mode 100644 index 000000000..2e34bc6aa --- /dev/null +++ b/cfg/rh-1.6/controlplane.yaml @@ -0,0 +1,67 @@ +--- +controls: +version: rh-1.6 +id: 3 +text: "Control Plane Configuration" +type: "controlplane" +groups: + - id: 3.1 + text: "Authentication and Authorization" + checks: + - id: 3.1.1 + text: "Client certificate authentication should not be used for users (Manual)" + audit: | + # To verify user authentication is enabled + oc describe authentication + # To verify that an identity provider is configured + oc get oauth -o json | jq '.items[].spec.identityProviders' + # To verify that a custom cluster-admin user exists + oc get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].kind | grep cluster-admin | grep User + # To verity that kbueadmin is removed, no results should be returned + oc get secrets kubeadmin -n kube-system + type: manual + remediation: | + Configure an identity provider for the OpenShift cluster. + Understanding identity provider configuration | Authentication | OpenShift + Container Platform 4.15. Once an identity provider has been defined, + you can use RBAC to define and apply permissions. + After you define an identity provider and create a new cluster-admin user, + remove the kubeadmin user to improve cluster security. + scored: false + + - id: 3.2 + text: "Logging" + checks: + - id: 3.2.1 + text: "Ensure that a minimal audit policy is created (Manual)" + audit: | + #View the audit log profile + oc get apiserver cluster -o json | jq .spec.audit.profile + #To verify kube apiserver audit config + oc get cm -n openshift-kube-apiserver config -o json | jq -r '.data."config.yaml"' | jq .apiServerArguments + #To verify openshift apiserver audit config + oc get cm -n openshift-apiserver config -o json | jq -r '.data."config.yaml"' | jq .apiServerArguments + #Review the audit policies of openshift apiserver + oc get cm -n openshift-apiserver audit -o json | jq -r '.data."policy.yaml"' + #Review the audit policies of kube apiserver + oc get cm -n openshift-kube-apiserver kube-apiserver-audit-policies -o json | jq -r '.data."policy.yaml"' + #To view kube apiserver log files + oc adm node-logs --role=master --path=kube-apiserver/ + #To view openshift apiserver log files + oc adm node-logs --role=master --path=openshift-apiserver/ + type: manual + remediation: | + No remediation required. + scored: false + + - id: 3.2.2 + text: "Ensure that the audit policy covers key security concerns (Manual)" + audit: | + #To verify openshift apiserver audit config + oc get configmap -n openshift-kube-apiserver kube-apiserver-audit-policies -ojson | jq -r '.data."policy.yaml"' + #To verify kube apiserver audit config + oc get configmap -n openshift-apiserver audit -o json | jq -r '.data."policy.yaml"' + type: manual + remediation: | + Update the audit log policy profile to use WriteRequestBodies. + scored: false diff --git a/cfg/rh-1.6/etcd.yaml b/cfg/rh-1.6/etcd.yaml new file mode 100644 index 000000000..4b589ff54 --- /dev/null +++ b/cfg/rh-1.6/etcd.yaml @@ -0,0 +1,185 @@ +--- +controls: +version: rh-1.6 +id: 2 +text: "Etcd Node Configuration" +type: "etcd" +groups: + - id: 2 + text: "Etcd Node Configuration Files" + checks: + - id: 2.1 + text: "Ensure that the --cert-file and --key-file arguments are set as appropriate (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -z "$POD_NAME" ]; then + echo "No matching file found on the current node." + else + # Execute the stat command + oc exec -n openshift-etcd -c etcd "$POD_NAME" -- ps -o command= -C etcd | sed 's/.*\(--cert-file=[^ ]*\).*/\1/' + oc exec -n openshift-etcd -c etcd "$POD_NAME" -- ps -o command= -C etcd | sed 's/.*\(--key-file=[^ ]*\).*/\1/' + fi + use_multiple_values: true + tests: + test_items: + - flag: "file" + compare: + op: regex + # some systems have certs in directory '/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs' + value: \/etc\/kubernetes\/static-pod-certs\/secrets\/etcd-all-(?:serving|certs)\/etcd-serving-.*\.(?:crt|key) + remediation: | + OpenShift does not use the etcd-certfile or etcd-keyfile flags. + Certificates for etcd are managed by the etcd cluster operator. + scored: false + + - id: 2.2 + text: "Ensure that the --client-cert-auth argument is set to true (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -z "$POD_NAME" ]; then + echo "No matching file found on the current node." + else + # Execute the stat command + oc exec -n openshift-etcd -c etcd "$POD_NAME" -- ps -o command= -C etcd | sed 's/.*\(--client-cert-auth=[^ ]*\).*/\1/' + fi + use_multiple_values: true + tests: + test_items: + - flag: "--client-cert-auth" + compare: + op: eq + value: true + remediation: | + This setting is managed by the cluster etcd operator. No remediation required." + scored: false + + - id: 2.3 + text: "Ensure that the --auto-tls argument is not set to true (Manual)" + audit: | + # Returns 0 if found, 1 if not found + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -z "$POD_NAME" ]; then + echo "No matching file found on the current node." + else + # Execute the stat command + oc exec -n openshift-etcd -c etcd "$POD_NAME" -- ps -o command= -C etcd | grep -- --auto-tls=true 2>/dev/null ; echo exit_code=$? + fi + use_multiple_values: true + tests: + test_items: + - flag: "exit_code" + compare: + op: eq + value: "1" + remediation: | + This setting is managed by the cluster etcd operator. No remediation required. + scored: false + + - id: 2.4 + text: "Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -z "$POD_NAME" ]; then + echo "No matching file found on the current node." + else + # Execute the stat command + oc exec -n openshift-etcd -c etcd "$POD_NAME" -- ps -o command= -C etcd | sed 's/.*\(--peer-cert-file=[^ ]*\).*/\1/' + oc exec -n openshift-etcd -c etcd "$POD_NAME" -- ps -o command= -C etcd | sed 's/.*\(--peer-key-file=[^ ]*\).*/\1/' + fi + use_multiple_values: true + tests: + test_items: + - flag: "file" + compare: + op: regex + # some systems have certs in directory '/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs' + value: '\/etc\/kubernetes\/static-pod-certs\/secrets\/etcd-all-(?:peer|certs)\/etcd-peer-.*\.(?:crt|key)' + remediation: | + None. This configuration is managed by the etcd operator. + scored: false + + - id: 2.5 + text: "Ensure that the --peer-client-cert-auth argument is set to true (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -z "$POD_NAME" ]; then + echo "No matching file found on the current node." + else + # Execute the stat command + oc exec -n openshift-etcd -c etcd "$POD_NAME" -- ps -o command= -C etcd | sed 's/.*\(--peer-client-cert-auth=[^ ]*\).*/\1/' + fi + use_multiple_values: true + tests: + test_items: + - flag: "--peer-client-cert-auth" + compare: + op: eq + value: true + remediation: | + This setting is managed by the cluster etcd operator. No remediation required. + scored: false + + - id: 2.6 + text: "Ensure that the --peer-auto-tls argument is not set to true (Manual)" + audit: | + # Returns 0 if found, 1 if not found + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -z "$POD_NAME" ]; then + echo "No matching file found on the current node." + else + # Execute the stat command + oc exec -n openshift-etcd -c etcd "$POD_NAME" -- ps -o command= -C etcd | grep -- --peer-auto-tls=true 2>/dev/null ; echo exit_code=$? + fi + use_multiple_values: true + tests: + test_items: + - flag: "exit_code" + compare: + op: eq + value: "1" + remediation: | + This setting is managed by the cluster etcd operator. No remediation required. + scored: false + + - id: 2.7 + text: "Ensure that a unique Certificate Authority is used for etcd (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -z "$POD_NAME" ]; then + echo "No matching file found on the current node." + else + # Execute the stat command + oc exec -n openshift-etcd -c etcd "$POD_NAME" -- ps -o command= -C etcd | sed 's/.*\(--trusted-ca-file=[^ ]*\).*/\1/' + oc exec -n openshift-etcd -c etcd "$POD_NAME" -- ps -o command= -C etcd | sed 's/.*\(--peer-trusted-ca-file=[^ ]*\).*/\1/' + fi + use_multiple_values: true + tests: + test_items: + - flag: "file" + compare: + op: regex + value: '\/etc\/kubernetes\/static-pod-certs\/configmaps\/etcd-(?:serving|peer-client)-ca\/ca-bundle\.(?:crt|key)' + remediation: | + None required. Certificates for etcd are managed by the OpenShift cluster etcd operator. + scored: false diff --git a/cfg/rh-1.6/master.yaml b/cfg/rh-1.6/master.yaml new file mode 100644 index 000000000..b025a5dea --- /dev/null +++ b/cfg/rh-1.6/master.yaml @@ -0,0 +1,1229 @@ +--- +controls: +version: rh-1.6 +id: 1 +text: "Master Node Security Configuration" +type: "master" +groups: + - id: 1.1 + text: "Master Node Configuration Files" + checks: + - id: 1.1.1 + text: "Ensure that the API server pod specification file permissions are set to 600 or more restrictive (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-apiserver namespace + POD_NAME=$(oc get pods -n openshift-kube-apiserver -l app=openshift-kube-apiserver --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-apiserver "$POD_NAME" -- stat -c "$POD_NAME %n permissions=%a" /etc/kubernetes/static-pod-resources/kube-apiserver-pod.yaml + fi + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + There is no remediation for updating the permissions of kube-apiserver-pod.yaml. + The file is owned by an OpenShift operator and any changes to the file will result in a degraded cluster state. + Please do not attempt to remediate the permissions of this file. + scored: false + + - id: 1.1.2 + text: "Ensure that the API server pod specification file ownership is set to root:root (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-apiserver namespace + POD_NAME=$(oc get pods -n openshift-kube-apiserver -l app=openshift-kube-apiserver --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-apiserver "$POD_NAME" -- stat -c "$POD_NAME %n %U:%G" /etc/kubernetes/static-pod-resources/kube-apiserver-pod.yaml + fi + use_multiple_values: true + tests: + test_items: + - flag: "root:root" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.3 + text: "Ensure that the controller manager pod specification file permissions are set to 600 or more restrictive (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-controller-manager namespace + POD_NAME=$(oc get pods -n openshift-kube-controller-manager -l app=kube-controller-manager --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-controller-manager "$POD_NAME" -- stat -c "$POD_NAME %n permissions=%a" /etc/kubernetes/static-pod-resources/kube-controller-manager-pod.yaml + fi + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + There is no remediation for updating the permissions of kube-controller-manager-pod.yaml. + The file is owned by an OpenShift operator and any changes to the file will result in a degraded cluster state. + Please do not attempt to remediate the permissions of this file. + scored: false + + - id: 1.1.4 + text: "Ensure that the controller manager pod specification file ownership is set to root:root (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-controller-manager namespace + POD_NAME=$(oc get pods -n openshift-kube-controller-manager -l app=kube-controller-manager --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-controller-manager "$POD_NAME" -- stat -c "$POD_NAME %n %U:%G" /etc/kubernetes/static-pod-resources/kube-controller-manager-pod.yaml + fi + use_multiple_values: true + tests: + test_items: + - flag: "root:root" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.5 + text: "Ensure that the scheduler pod specification file permissions are set to 600 or more restrictive (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-scheduler namespace + POD_NAME=$(oc get pods -n openshift-kube-scheduler -l app=openshift-kube-scheduler --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-scheduler "$POD_NAME" -- stat -c "$POD_NAME %n permissions=%a" /etc/kubernetes/static-pod-resources/kube-scheduler-pod.yaml + fi + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + There is no remediation for updating the permissions of kube-scheduler-pod.yaml. + The file is owned by an OpenShift operator and any changes to the file will result in a degraded cluster state. + Please do not attempt to remediate the permissions of this file. + scored: false + + - id: 1.1.6 + text: "Ensure that the scheduler pod specification file ownership is set to root:root (Manual))" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-scheduler namespace + POD_NAME=$(oc get pods -n openshift-kube-scheduler -l app=openshift-kube-scheduler --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-scheduler "$POD_NAME" -- stat -c "$POD_NAME %n %U:%G" /etc/kubernetes/static-pod-resources/kube-scheduler-pod.yaml + fi + use_multiple_values: true + tests: + test_items: + - flag: "root:root" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.7 + text: "Ensure that the etcd pod specification file permissions are set to 600 or more restrictive (Manual))" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc rsh -n openshift-etcd "$POD_NAME" stat -c "$POD_NAME %n permissions=%a" /etc/kubernetes/manifests/etcd-pod.yaml + fi + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + There is no remediation for updating the permissions of etcd-pod.yaml. + The file is owned by an OpenShift operator and any changes to the file will result in a degraded cluster state. + Please do not attempt to remediate the permissions of this file. + scored: false + + - id: 1.1.8 + text: "Ensure that the etcd pod specification file ownership is set to root:root (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc rsh -n openshift-etcd "$POD_NAME" stat -c "$POD_NAME %n %U:%G" /etc/kubernetes/manifests/etcd-pod.yaml + fi + use_multiple_values: true + tests: + test_items: + - flag: "root:root" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.9 + text: "Ensure that the Container Network Interface file permissions are set to 600 or more restrictive (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + # For CNI multus + # Get the pod name in the openshift-multus namespace + POD_NAME=$(oc get pods -n openshift-multus -l app=multus --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -n "$POD_NAME" ]; then + # Execute the stat command + oc exec -n openshift-multus "$POD_NAME" -- /bin/bash -c "stat -c \"$i %n permissions=%a\" /host/etc/cni/net.d/*.conf" 2>/dev/null + oc exec -n openshift-multus "$POD_NAME" -- /bin/bash -c "stat -c \"$i %n permissions=%a\" /host/var/run/multus/cni/net.d/*.conf" 2>/dev/null + fi + + # For SDN pods + POD_NAME=$(oc get pods -n openshift-sdn -l app=sdn --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -n "$POD_NAME" ]; then + # Execute the stat command + oc exec -n openshift-sdn "$POD_NAME" -- find /var/lib/cni/networks/openshift-sdn -type f -exec stat -c "$i %n permissions=%a" {} \; 2>/dev/null + oc exec -n openshift-sdn "$POD_NAME" -- find /var/run/openshift-sdn -type f -exec stat -c "$i %n permissions=%a" {} \; 2>/dev/null + fi + + # For OVS pods + POD_NAME=$(oc get pods -n openshift-sdn -l app=ovs --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -n "$POD_NAME" ]; then + # Execute the stat command + oc exec -n openshift-sdn "$POD_NAME" -- find /var/run/openvswitch -type f -exec stat -c "$i %n permissions=%a" {} \; 2>/dev/null + oc exec -n openshift-sdn "$POD_NAME" -- find /etc/openvswitch -type f -exec stat -c "$i %n permissions=%a" {} \; 2>/dev/null + oc exec -n openshift-sdn "$POD_NAME" -- find /run/openvswitch -type f -exec stat -c "$i %n permissions=%a" {} \; 2>/dev/null + fi + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.10 + text: "Ensure that the Container Network Interface file ownership is set to root:root (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # For CNI multus + # Get the pod name in the openshift-multus namespace + POD_NAME=$(oc get pods -n openshift-multus -l app=multus --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -n "$POD_NAME" ]; then + # Execute the stat command + oc exec -n openshift-multus "$POD_NAME" -- /bin/bash -c "stat -c '$i %n %U:%G' /host/etc/cni/net.d/*.conf" 2>/dev/null + oc exec -n openshift-multus $i -- /bin/bash -c "stat -c '$i %n %U:%G' /host/var/run/multus/cni/net.d/*.conf" 2>/dev/null + fi + + # For SDN pods + POD_NAME=$(oc get pods -n openshift-sdn -l app=sdn --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -n "$POD_NAME" ]; then + # Execute the stat command + oc exec -n openshift-sdn "$POD_NAME" -- find /var/lib/cni/networks/openshift-sdn -type f -exec stat -c "$i %n %U:%G" {} \; 2>/dev/null + oc exec -n openshift-sdn "$POD_NAME" -- find /var/run/openshift-sdn -type f -exec stat -c "$i %n %U:%G" {} \; 2>/dev/null + fi + + # For OVS pods in 4.5 + POD_NAME=$(oc get pods -n openshift-sdn -l app=ovs --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -n "$POD_NAME" ]; then + # Execute the stat command + oc exec -n openshift-sdn "$POD_NAME" -- find /var/run/openvswitch -type f -exec stat -c "$i %n %U:%G" {} \; 2>/dev/null + oc exec -n openshift-sdn "$POD_NAME" -- find /etc/openvswitch -type f -exec stat -c "$i %n %U:%G" {} \; 2>/dev/null + oc exec -n openshift-sdn "$POD_NAME" -- find /run/openvswitch -type f -exec stat -c "$i %n %U:%G" {} \; 2>/dev/null + fi + use_multiple_values: true + tests: + bin_op: or + test_items: + - flag: "root:root" + - flag: "openvswitch:openvswitch" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.11 + text: "Ensure that the etcd data directory permissions are set to 700 or more restrictive (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-etcd "$POD_NAME" -- stat -c "$POD_NAME %n permissions=%a" /var/lib/etcd/member + fi + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "700" + remediation: | + No remediation required; file permissions are managed by the etcd operator. + scored: false + + - id: 1.1.12 + text: "Ensure that the etcd data directory ownership is set to etcd:etcd (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-etcd namespace + POD_NAME=$(oc get pods -n openshift-etcd -l app=etcd --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-etcd "$POD_NAME" -- stat -c "$POD_NAME %n %U:%G" /var/lib/etcd/member + fi + use_multiple_values: true + tests: + test_items: + - flag: "root:root" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.13 + text: "Ensure that the kubeconfig file ownership are set to 600 or more restrictive (Manual))" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME %n permissions=%a" /etc/kubernetes/kubeconfig 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + There is no remediation for updating the permissions of kubeconfig. + The file is owned by an OpenShift operator and any changes to the file will result in a degraded cluster state. + Please do not attempt to remediate the permissions of this file. + scored: false + + - id: 1.1.14 + text: "Ensure that the kubeconfig file ownership is set to root:root (Manual)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME %n %U:%G" /etc/kubernetes/kubeconfig 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: "root:root" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.15 + text: "Ensure that the Scheduler kubeconfig file permissions are set to 600 or more restrictive (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-scheduler namespace + POD_NAME=$(oc get pods -n openshift-kube-scheduler -l app=openshift-kube-scheduler --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-scheduler "$POD_NAME" -- stat -c "$POD_NAME %n permissions=%a" /etc/kubernetes/static-pod-resources/configmaps/scheduler-kubeconfig/kubeconfig + fi + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + There is no remediation for updating the permissions of the kubeconfig file. + The file is owned by an OpenShift operator and any changes to the file will result in a degraded cluster state. + Please do not attempt to remediate the permissions of this file. + scored: false + + - id: 1.1.16 + text: "Ensure that the scheduler.conf file ownership is set to root:root (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-scheduler namespace + POD_NAME=$(oc get pods -n openshift-kube-scheduler -l app=openshift-kube-scheduler --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-scheduler "$POD_NAME" -- stat -c "$POD_NAME %n %U:%G" /etc/kubernetes/static-pod-resources/configmaps/scheduler-kubeconfig/kubeconfig + fi + use_multiple_values: true + tests: + test_items: + - flag: "root:root" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.17 + text: "Ensure that the controller-manager.conf file permissions are set to 600 or more restrictive (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-controller-manager namespace + POD_NAME=$(oc get pods -n openshift-kube-controller-manager -l app=kube-controller-manager --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-controller-manager "$POD_NAME" -- stat -c "$POD_NAME %n permissions=%a" /etc/kubernetes/static-pod-resources/configmaps/controller-manager-kubeconfig/kubeconfig + fi + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.18 + text: "Ensure that the controller-manager.conf file ownership is set to root:root (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-controller-manager namespace + POD_NAME=$(oc get pods -n openshift-kube-controller-manager -l app=kube-controller-manager --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-controller-manager "$POD_NAME" -- stat -c "$POD_NAME %n %U:%G" /etc/kubernetes/static-pod-resources/configmaps/controller-manager-kubeconfig/kubeconfig + fi + use_multiple_values: true + tests: + test_items: + - flag: "root:root" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.19 + text: "Ensure that the OpenShift PKI directory and file ownership is set to root:root (Manual)" + audit: | + # Should return root:root for all files and directories + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-controller-manager namespace + POD_NAME=$(oc get pods -n openshift-kube-apiserver -l app=openshift-kube-apiserver --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # echo $i static-pod-certs + oc exec -n openshift-kube-apiserver "$POD_NAME" -c kube-apiserver -- find /etc/kubernetes/static-pod-certs -type d -wholename '*/secrets*' -exec stat -c "$i %n %U:%G" {} \; + oc exec -n openshift-kube-apiserver "$POD_NAME" -c kube-apiserver -- find /etc/kubernetes/static-pod-certs -type f -wholename '*/secrets*' -exec stat -c "$i %n %U:%G" {} \; + # echo $i static-pod-resources + oc exec -n openshift-kube-apiserver "$POD_NAME" -c kube-apiserver -- find /etc/kubernetes/static-pod-resources -type d -wholename '*/secrets*' -exec stat -c "$i %n %U:%G" {} \; + oc exec -n openshift-kube-apiserver "$POD_NAME" -c kube-apiserver -- find /etc/kubernetes/static-pod-resources -type f -wholename '*/secrets*' -exec stat -c "$i %n %U:%G" {} \; + fi + use_multiple_values: true + tests: + test_items: + - flag: "root:root" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.20 + text: "Ensure that the OpenShift PKI certificate file permissions are set to 600 or more restrictive (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-apiserver namespace + POD_NAME=$(oc get pods -n openshift-kube-apiserver -l app=openshift-kube-apiserver --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-apiserver "$POD_NAME" -c kube-apiserver -- find /etc/kubernetes/static-pod-certs -type f -wholename '*/secrets/*.crt' -exec stat -c "$POD_NAME %n permissions=%a" {} \; + fi + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.1.21 + text: "Ensure that the OpenShift PKI key file permissions are set to 600 (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + + # Get the pod name in the openshift-kube-apiserver namespace + POD_NAME=$(oc get pods -n openshift-kube-apiserver -l app=openshift-kube-apiserver --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-kube-apiserver "$POD_NAME" -c kube-apiserver -- find /etc/kubernetes/static-pod-certs -type f -wholename '*/secrets/*.key' -exec stat -c "$POD_NAME %n permissions=%a" {} \; + fi + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + No remediation required; file permissions are managed by the operator. + scored: false + + - id: 1.2 + text: "API Server" + checks: + - id: 1.2.1 + text: "Ensure that anonymous requests are authorized (Manual)" + audit: | + # To see what unauthenticated users are allowed to do. + oc get clusterrolebindings -o json | jq '.items[] | select(.subjects[]?.kind == "Group" and .subjects[]?.name == "system:unauthenticated") | .metadata.name' | uniq + tests: + bin_op: or + test_items: + - flag: "self-access-reviewers" + - flag: "system:oauth-token-deleters" + - flag: "system:openshift:public-info-viewer" + - flag: "system:public-info-viewer" + - flag: "system:scope-impersonation" + - flag: "system:webhooks" + remediation: | + None required. The default configuration should not be modified. + scored: false + + - id: 1.2.2 + text: "Ensure that the --basic-auth-file argument is not set (Manual)" + audit: | + oc -n openshift-kube-apiserver get cm config -o yaml | grep --color "basic-auth" + oc -n openshift-apiserver get cm config -o yaml | grep --color "basic-auth" + # Add | awk '$3 != "AVAILABLE" { if ($3){print "available=true"}else{print "available=false"} }; to create AVAILABLE = true/false form + oc get clusteroperator authentication | awk '$3 != "AVAILABLE" { if ($3){print "available=true"}else{print "available=false"} }' + tests: + bin_op: and + test_items: + - flag: "basic-auth-file" + set: false + - flag: "available" + compare: + op: eq + value: true + remediation: | + None required. --basic-auth-file cannot be configured on OpenShift. + scored: false + + - id: 1.2.3 + text: "Ensure that the --token-auth-file parameter is not set (Manual)" + audit: | + # Verify that the token-auth-file flag is not present + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.apiServerArguments' | grep --color "token-auth-file" + oc get configmap config -n openshift-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.apiServerArguments' | grep --color "token-auth-file" + oc get kubeapiservers.operator.openshift.io cluster -o json | jq '.spec.observedConfig.apiServerArguments' | grep --color "token-auth-file" + #Verify that the authentication operator is running + oc get clusteroperator authentication | awk '$3 != "AVAILABLE" { if ($3){print "available=true"}else{print "available=false"} }' + tests: + bin_op: and + test_items: + - flag: "token-auth-file" + set: false + - flag: "available" + compare: + op: eq + value: true + remediation: | + None is required. + scored: false + + - id: 1.2.4 + text: "Use https for kubelet connections (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.apiServerArguments' + oc -n openshift-apiserver describe secret serving-cert + tests: + bin_op: and + test_items: + - flag: "/etc/kubernetes/static-pod-resources/kube-apiserver-certs/secrets/kubelet-client/tls.crt" + - flag: "/etc/kubernetes/static-pod-resources/kube-apiserver-certs/secrets/kubelet-client/tls.key" + remediation: | + No remediation is required. + OpenShift platform components use X.509 certificates for authentication. + OpenShift manages the CAs and certificates for platform components. This is not configurable. + scored: false + + - id: 1.2.5 + text: "Ensure that the kubelet uses certificates to authenticate (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.apiServerArguments' + oc -n openshift-apiserver describe secret serving-cert + tests: + bin_op: and + test_items: + - flag: "/etc/kubernetes/static-pod-certs/secrets/kubelet-client/tls.crt" + - flag: "/etc/kubernetes/static-pod-certs/secrets/kubelet-client/tls.key" + remediation: | + No remediation is required. + OpenShift platform components use X.509 certificates for authentication. + OpenShift manages the CAs and certificates for platform components. + This is not configurable. + scored: false + + - id: 1.2.6 + text: "Verify that the kubelet certificate authority is set as appropriate (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.apiServerArguments' + tests: + test_items: + - flag: "/etc/kubernetes/static-pod-resources/configmaps/kubelet-serving-ca/ca-bundle.crt" + remediation: | + No remediation is required. + OpenShift platform components use X.509 certificates for authentication. + OpenShift manages the CAs and certificates for platform components. + This is not configurable. + scored: false + + - id: 1.2.7 + text: "Ensure that the --authorization-mode argument is not set to AlwaysAllow (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments."authorization-mode"[]' + tests: + test_items: + - flag: "AlwaysAllow" + set: false + remediation: | + None. RBAC is always on and the OpenShift API server does not use the values assigned to the flag authorization-mode. + scored: false + + - id: 1.2.8 + text: "Verify that RBAC is enabled (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments."authorization-mode"[]' + tests: + test_items: + - flag: "RBAC" + remediation: | + None. It is not possible to disable RBAC. + scored: false + + - id: 1.2.9 + text: "Ensure that the APIPriorityAndFairness feature gate is enabled (Manual)" + audit: | + #Verify the APIPriorityAndFairness feature-gate + oc get kubeapiservers.operator.openshift.io cluster -o json | jq '.spec.observedConfig.apiServerArguments' + tests: + test_items: + - flag: "APIPriorityAndFairness=true" + remediation: | + No remediation is required. By default, the OpenShift kubelet has been fixed to send fewer requests. + scored: false + + - id: 1.2.10 + text: "Ensure that the admission control plugin AlwaysAdmit is not set (Manual)" + audit: | + #Verify the set of admission-plugins for OCP 4.6 and higher + oc -n openshift-kube-apiserver get configmap config -o json | jq -r '.data."config.yaml"' | jq '.apiServerArguments."enable-admission-plugins"' + tests: + test_items: + - flag: "AlwaysAdmit" + set: false + remediation: | + No remediation is required. The AlwaysAdmit admission controller cannot be enabled in OpenShift. + scored: false + + - id: 1.2.11 + text: "Ensure that the admission control plugin AlwaysPullImages is set (Manual)" + audit: | + #Verify the set of admissi on-plugins for OCP 4.6 and higher + oc -n openshift-kube-apiserver get configmap config -o json | jq -r '.data."config.yaml"' | jq '.apiServerArguments."enable-admission-plugins"' + tests: + test_items: + - flag: "AlwaysPullImages" + set: false + remediation: | + None required. + scored: false + + - id: 1.2.12 + text: "Ensure that the admission control plugin ServiceAccount is set (Manual)" + audit: | + oc -n openshift-kube-apiserver get configmap config -o json | jq -r '.data."config.yaml"' | jq '.apiServerArguments."enable-admission-plugins"' + tests: + test_items: + - flag: "ServiceAccount" + set: true + remediation: | + None required. By default, OpenShift configures the ServiceAccount admission controller. + scored: false + + - id: 1.2.13 + text: "Ensure that the admission control plugin NamespaceLifecycle is set (Manual)" + audit: | + oc -n openshift-kube-apiserver get configmap config -o json | jq -r '.data."config.yaml"' | jq '.apiServerArguments."enable-admission-plugins"' + tests: + test_items: + - flag: "NamespaceLifecycle" + remediation: | + None required. OpenShift configures NamespaceLifecycle admission controller by default. + scored: false + + - id: 1.2.14 + text: "Ensure that the admission control plugin SecurityContextConstraint is set (Manual)" + audit: | + oc -n openshift-kube-apiserver get configmap config -o json | jq -r '.data."config.yaml"' | jq '.apiServerArguments."enable-admission-plugins"' + tests: + test_items: + - flag: "security.openshift.io/SecurityContextConstraint" + remediation: | + None required. By default, the SecurityContextConstraints admission controller is configured and cannot be disabled. + scored: false + + - id: 1.2.15 + text: "Ensure that the admission control plugin NodeRestriction is set (Manual)" + audit: | + oc -n openshift-kube-apiserver get configmap config -o json | jq -r '.data."config.yaml"' | jq '.apiServerArguments."enable-admission-plugins"' + tests: + test_items: + - flag: "NodeRestriction" + remediation: | + None required. In OpenShift, the NodeRestriction admission plugin is enabled by default and cannot be disabled. + scored: false + + - id: 1.2.16 + text: "Ensure that the --insecure-bind-address argument is not set (Manual)" + audit: | + # InsecureBindAddress=true should not be in the results + oc get kubeapiservers.operator.openshift.io cluster -ojson | jq '.spec.observedConfig.apiServerArguments."feature-gates"' + # Result should be only 6443 + oc -n openshift-kube-apiserver get endpoints -o jsonpath='{.items[*].subsets[*].ports[*].port}' + # Result should be only 8443 + oc -n openshift-apiserver get endpoints -o jsonpath='{.items[*].subsets[*].ports[*].port}' + tests: + bin_op: and + test_items: + - flag: "InsecureBindAddress=true" + set: false + - flag: 6443 + - flag: 8443 + remediation: | + None required. By default, the openshift-kube-apiserver is served over HTTPS with authentication and authorization. + scored: false + + - id: 1.2.17 + text: "Ensure that the --insecure-port argument is set to 0 (Manual)" + audit: | + # Should return 6443 + oc -n openshift-kube-apiserver get endpoints -o jsonpath='{.items[*].subsets[*].ports[*].port}' + tests: + test_items: + - flag: "6443" + remediation: | + None required. By default, the openshift-kube-server is served over HTTPS with authentication and authorization. + scored: false + + - id: 1.2.18 + text: "Ensure that the --secure-port argument is not set to 0 (Manual)" + audit: | + echo bindAddress=$(oc get kubeapiservers.operator.openshift.io cluster -o json | jq '.spec.observedConfig.servingInfo.bindAddress') + # Should return only 6443 + echo ports=$(oc get pods -n openshift-kube-apiserver -l app=openshift-kube-apiserver -o jsonpath='{.items[*].spec.containers[?(@.name=="kube-apiserver")].ports[*].containerPort}') + tests: + bin_op: and + test_items: + - flag: 'bindAddress' + compare: + op: eq + value: '"0.0.0.0:6443"' + - flag: "ports" + compare: + op: eq + value: '6443' + remediation: | + None required. By default, the openshift-kube-apiserver is served over HTTPS with authentication and authorization; + the secure API endpoint is bound to 0.0.0.0:6443. + scored: false + + - id: 1.2.19 + text: "Ensure that the healthz endpoint is protected by RBAC (Manual)" + type: manual + audit: | + # Verify endpoints + oc -n openshift-kube-apiserver describe endpoints + # Check config for ports, livenessProbe, readinessProbe, healthz + oc -n openshift-kube-apiserver get cm kube-apiserver-pod -o json | jq -r '.data."pod.yaml"' | jq '.spec.containers' + # Test to validate RBAC enabled on the apiserver endpoint; check with non-admin role + oc project openshift-kube-apiserver POD=$(oc get pods -n openshift-kube-apiserver -l app=openshift-kube-apiserver -o jsonpath='{.items[0].metadata.name}') PORT=$(oc get pods -n openshift-kube-apiserver -l app=openshift-kube-apiserver -o jsonpath='{.items[0].spec.containers[0].ports[0].hostPort}') + # Following should return 403 Forbidden + oc rsh -n openshift-kube-apiserver ${POD} curl https://localhost:${PORT}/metrics -k + # Create a service account to test RBAC + oc create -n openshift-kube-apiserver sa permission-test-sa + # Should return 403 Forbidden + export SA_TOKEN=$(oc create token -n openshift-kube-apiserver permission-test-sa) + oc rsh -n openshift-kube-apiserver ${POD} curl https://localhost:${PORT}/metrics -H "Authorization: Bearer $SA_TOKEN" -k + # As cluster admin, should succeed + export CLUSTER_ADMIN_TOKEN=$(oc whoami -t) + oc rsh -n openshift-kube-apiserver ${POD} curl https://localhost:${PORT}/metrics -H "Authorization: Bearer $CLUSTER_ADMIN_TOKEN" -k + # Cleanup + unset CLUSTER_ADMIN_TOKEN SA_TOKEN + oc delete -n openshift-kube-apiserver sa permission-test-sa + remediation: | + None required as profiling data is protected by RBAC. + scored: false + + - id: 1.2.20 + text: "Ensure that the --audit-log-path argument is set (Manual)" + audit: | + # Should return “/var/log/kube-apiserver/audit.log" + output=$(oc get configmap config -n openshift-kube-apiserver -o json | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["audit-log-path"][]?') + [ "$output" != "" ] && [ "$output" != "null" ] && echo "$output" || true + POD=$(oc get pods -n openshift-kube-apiserver -l app=openshift-kube-apiserver -o jsonpath='{.items[0].metadata.name}') + oc rsh -n openshift-kube-apiserver -c kube-apiserver $POD ls /var/log/kube-apiserver/audit.log 2>/dev/null + # Should return 0 + echo kube_apiserver_exit_code=$? + # Should return "/var/log/openshift-apiserver/audit.log" + output=$(oc get configmap config -n openshift-apiserver -o json | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["audit-log-path"][]?') + [ "$output" != "" ] && [ "$output" != "null" ] && echo "$output" || true + POD=$(oc get pods -n openshift-apiserver -l apiserver=true -o jsonpath='{.items[0].metadata.name}') + oc rsh -n openshift-apiserver $POD ls /var/log/openshift-apiserver/audit.log 2>/dev/null + # Should return 0 + echo apiserver_exit_code=$? + tests: + bin_op: and + test_items: + - flag: "/var/log/kube-apiserver/audit.log" + - flag: "/var/log/kube-apiserver/audit.log" # This is needed for second printing in ls command. + - flag: "kube_apiserver_exit_code=0" + - flag: "/var/log/openshift-apiserver/audit.log" + - flag: "/var/log/openshift-apiserver/audit.log" # This is needed for second printing in ls command. + - flag: "apiserver_exit_code=0" + remediation: | + None required. This is managed by the cluster apiserver operator. By default, auditing is enabled. + scored: false + + - id: 1.2.21 + text: "Ensure that the audit logs are forwarded off the cluster for retention (Manual)" + type: "manual" + remediation: | + Follow the documentation for log forwarding. Forwarding logs to third party systems + https://docs.openshift.com/container-platform/4.15/observability/logging/log_collection_forwarding/configuring-log-forwarding.html + scored: false + + - id: 1.2.22 + text: "Ensure that the maximumRetainedFiles argument is set to 10 or as appropriate (Manual)" + audit: | + output=$(oc get configmap config -n openshift-kube-apiserver -o json | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["audit-log-maxbackup"][]?') + [ "$output" != "" ] && [ "$output" != "null" ] && echo "audit-log-maxbackup=$output" || true + tests: + test_items: + - flag: "audit-log-maxbackup" + compare: + op: gte + value: 10 + remediation: | + None required. By default, auditing is enabled and the maximum audit log backup is set to 10. + scored: false + + - id: 1.2.23 + text: "Ensure that the maximumFileSizeMegabytes argument is set to 100 (Manual)" + audit: | + output=$(oc get configmap config -n openshift-kube-apiserver -o json | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["audit-log-maxsize"][]?') + [ "$output" != "" ] && [ "$output" != "null" ] && echo "audit-log-maxsize=$output" || true + tests: + test_items: + - flag: "audit-log-maxsize" + compare: + op: gte + value: 100 + remediation: | + None. The audit-log-maxsize parameter is by default set to 100 and not supported to change. + maximumFileSizeMegabytes: 100 + scored: false + + - id: 1.2.24 + text: "Ensure that the --request-timeout argument is set (Manual)" + audit: | + echo requestTimeoutSeconds=`oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["min-request-timeout"][]?'` + tests: + test_items: + - flag: "requestTimeoutSeconds" + compare: + op: gte + value: 100 + remediation: | + None required. By default, min-request-timeout is set to 3600 seconds in OpenShift. + scored: false + + - id: 1.2.25 + text: "Ensure that the --service-account-lookup argument is set to true (Manual)" + audit: | + output=$(oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["service-account-lookup"][0]') + [ "$output" == "null" ] && echo "ocp 4.5 has service-account-lookup=true compiled" || echo service-account-lookup=$output + tests: + test_items: + - flag: "service-account-lookup=true" + remediation: | + None required. Service account lookup is enabled by default. + scored: false + + - id: 1.2.26 + text: "Ensure that the --service-account-key-file argument is set as appropriate (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r .serviceAccountPublicKeyFiles[] + tests: + bin_op: and + test_items: + - flag: "/etc/kubernetes/static-pod-resources/configmaps/sa-token-signing-certs" + - flag: "/etc/kubernetes/static-pod-resources/configmaps/bound-sa-token-signing-certs" + remediation: | + The OpenShift API server does not use the service-account-key-file argument. + The ServiceAccount token authenticator is configured with serviceAccountConfig.publicKeyFiles. + OpenShift does not reuse the apiserver TLS key. This is not configurable. + scored: false + + - id: 1.2.27 + text: "Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["etcd-certfile"]' + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["etcd-keyfile"]' + tests: + bin_op: and + test_items: + - flag: "/etc/kubernetes/static-pod-resources/secrets/etcd-client/tls.crt" + - flag: "/etc/kubernetes/static-pod-resources/secrets/etcd-client/tls.key" + remediation: | + OpenShift automatically manages TLS and client certificate authentication for etcd. + This is not configurable. + scored: false + + - id: 1.2.28 + text: "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["tls-cert-file"][]' + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["tls-private-key-file"][]' + tests: + bin_op: and + test_items: + - flag: "/etc/kubernetes/static-pod-certs/secrets/service-network-serving-certkey/tls.crt" + - flag: "/etc/kubernetes/static-pod-certs/secrets/service-network-serving-certkey/tls.key" + remediation: | + None. By default, OpenShift uses X.509 certificates to provide secure connections between the API server and + node/kubelet. OpenShift does not use values assigned to the tls-cert-file or tls-private-key-file flags. + You may optionally set a custom default certificate to be used by the API server when serving content in + order to enable clients to access the API server at a different host name or without the need to distribute + the cluster-managed certificate authority (CA) certificates to the clients. + Follow the directions in the OpenShift documentation + https://docs.openshift.com/container-platform/4.15/security/certificates/api-server.html + scored: false + + - id: 1.2.29 + text: "Ensure that the --client-ca-file argument is set as appropriate (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["client-ca-file"][]' + tests: + test_items: + - flag: "/etc/kubernetes/static-pod-certs/configmaps/client-ca/ca-bundle.crt" + remediation: | + None required. By default, OpenShift configures the client-ca-file and automatically manages the certificate. + It does not use the value assigned to the client-ca-file flag. + You may optionally set a custom default certificate to be used by the API server when serving content in + order to enable clients to access the API server at a different host name or without the need to distribute + the cluster-managed certificate authority (CA) certificates to the clients. + Please follow the OpenShift documentation for providing certificates for OpenShift to use. + https://docs.openshift.com/container-platform/4.15/security/certificate_types_descriptions/user-provided-certificates-for-api-server.html#location + scored: false + + - id: 1.2.30 + text: "Ensure that the --etcd-cafile argument is set as appropriate (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments["etcd-cafile"][]' + tests: + test_items: + - flag: "/etc/kubernetes/static-pod-resources/configmaps/etcd-serving-ca/ca-bundle.crt" + remediation: | + None required. By default, OpenShift uses X.509 certificates to provide secure communication to etcd. + OpenShift does not use values assigned to etcd-cafile. OpenShift generates the etcd-cafile and sets the + arguments appropriately in the API server. Communication with etcd is secured by the etcd serving CA. + scored: false + + - id: 1.2.31 + text: "Ensure that encryption providers are appropriately configured (Manual)" + audit: | + # encrypt the etcd datastore + oc get openshiftapiserver -o=jsonpath='{range.items[0].status.conditions[?(@.type=="Encrypted")]}{.reason}{"\n"}{.message}{"\n"}' + tests: + test_items: + - flag: "EncryptionCompleted" + remediation: | + Follow the OpenShift documentation for encrypting etcd data. + https://docs.openshift.com/container-platform/4.15/security/encrypting-etcd.html + scored: false + + - id: 1.2.32 + text: "Ensure that the API Server only makes use of Strong Cryptographic Ciphers (Manual)" + type: manual + audit: | + # verify cipher suites + oc get cm -n openshift-authentication v4-0-config-system-cliconfig -o jsonpath='{.data.v4\-0\-config\-system\-cliconfig}' | jq .servingInfo + oc get kubeapiservers.operator.openshift.io cluster -o json | jq .spec.observedConfig.servingInfo + oc get openshiftapiservers.operator.openshift.io cluster -o json | jq .spec.observedConfig.servingInfo + oc get -n openshift-ingress-operator ingresscontroller/default -o json | jq .status.tlsProfile + remediation: | + None required. By default, OpenShift uses the Intermediate TLS profile, which requires a minimum of TLS 1.2. + You can configure TLS security profiles by following the OpenShift TLS documentation. + https://docs.openshift.com/container-platform/4.15/security/tls-security-profiles.html + Note: The HAProxy Ingress controller image does not support TLS 1.3 and because the Modern profile requires + TLS 1.3, it is not supported. The Ingress Operator converts the Modern profile to Intermediate. + The Ingress Operator also converts the TLS 1.0 of an Old or Custom profile to 1.1, and TLS 1.3 of a Custom + profile to 1.2. + scored: false + + - id: 1.2.33 + text: "Ensure unsupported configuration overrides are not used (Manual)" + audit: | + oc get kubeapiserver/cluster -o jsonpath='{.spec.unsupportedConfigOverrides}' + tests: + test_items: + - flag: "null" + remediation: | + None required. By default, OpenShift sets this value to null and doesn't support overriding configuration + with unsupported features. + scored: false + + - id: 1.3 + text: "Controller Manager" + checks: + - id: 1.3.1 + text: "Ensure that controller manager healthz endpoints are protected by RBAC (Manual)" + type: manual + audit: | + # Verify configuration for ports, livenessProbe, readinessProbe, healthz + oc -n openshift-kube-controller-manager get cm kube-controller-manager-pod -o json | jq -r '.data."pod.yaml"' | jq '.spec.containers[].livenessProbe' + oc -n openshift-kube-controller-manager get cm kube-controller-manager-pod -o json | jq -r '.data."pod.yaml"' | jq '.spec.containers[].readinessProbe' + # Verify endpoints + oc -n openshift-kube-controller-manager describe endpoints + # Test to validate RBAC enabled on the controller endpoint; check with non-admin role + POD=$(oc get pods -n openshift-kube-controller-manager -l app=kube-controller-manager -o jsonpath='{.items[0].metadata.name}') + PORT=$(oc get pods -n openshift-kube-controller-manager -l app=kube-controller-manager -o jsonpath='{.items[0].spec.containers[0].ports[0].hostPort}') + # Following should return 403 Forbidden + oc rsh -n openshift-kube-controller-manager ${POD} curl https://localhost:${PORT}/metrics -k + # Create a service account to test RBAC + oc create -n openshift-kube-controller-manager sa permission-test-sa + # Should return 403 Forbidden + export SA_TOKEN=$(oc create token -n openshift-kube-controller-manager permission-test-sa) + oc rsh -n openshift-kube-controller-manager ${POD} curl https://localhost:${PORT}/metrics -H "Authorization: Bearer $SA_TOKEN" -k + # As cluster admin, should succeed + CLUSTER_ADMIN_TOKEN=$(oc whoami -t) + oc rsh -n openshift-kube-controller-manager ${POD} curl https://localhost:${PORT}/metrics -H "Authorization: Bearer $CLUSTER_ADMIN_TOKEN" -k + # Cleanup + unset CLUSTER_ADMIN_TOKEN POD PORT SA_TOKEN + oc delete -n openshift-kube-controller-manager sa permission-test-sa + remediation: | + None required. By default, the operator exposes metrics via metrics service. The metrics are collected + from the OpenShift Controller Manager and the Kubernetes Controller Manager and protected by RBAC. + scored: false + + - id: 1.3.2 + text: "Ensure that the --use-service-account-credentials argument is set to true (Manual)" + audit: | + echo use-service-account-credentials=`oc get configmaps config -n openshift-kube-controller-manager -ojson | jq -r '.data["config.yaml"]' | jq -r '.extendedArguments["use-service-account-credentials"][]'` + tests: + test_items: + - flag: "use-service-account-credentials" + compare: + op: eq + value: true + remediation: | + The OpenShift Controller Manager operator manages and updates the OpenShift Controller Manager. + The Kubernetes Controller Manager operator manages and updates the Kubernetes Controller Manager deployed on top of OpenShift. + This operator is configured via KubeControllerManager custom resource. + scored: false + + - id: 1.3.3 + text: "Ensure that the --service-account-private-key-file argument is set as appropriate (Manual)" + audit: | + oc get configmaps config -n openshift-kube-controller-manager -ojson | jq -r '.data["config.yaml"]' | jq -r '.extendedArguments["service-account-private-key-file"][]' + tests: + test_items: + - flag: "/etc/kubernetes/static-pod-resources/secrets/service-account-private-key/service-account.key" + remediation: | + None required. + OpenShift manages the service account credentials for the scheduler automatically. + scored: false + + - id: 1.3.4 + text: "Ensure that the --root-ca-file argument is set as appropriate (Manual)" + audit: | + oc get configmaps config -n openshift-kube-controller-manager -ojson | jq -r '.data["config.yaml"]' | jq -r '.extendedArguments["root-ca-file"][]' + tests: + test_items: + - flag: "/etc/kubernetes/static-pod-resources/configmaps/serviceaccount-ca/ca-bundle.crt" + remediation: | + None required. + Certificates for OpenShift platform components are automatically created and rotated by the OpenShift Container Platform. + scored: false + + - id: 1.3.5 + text: "Ensure that the --bind-address argument is set to 127.0.0.1 (Manual)" + audit: | + echo secure-port=`oc get configmaps config -n openshift-kube-controller-manager -ojson | jq -r '.data["config.yaml"]' | jq '.extendedArguments["secure-port"][]'` + #Following should fail with a http code 403 + POD=$(oc get pods -n openshift-kube-controller-manager -l app=kube-controller-manager -o jsonpath='{.items[0].metadata.name}') + oc rsh -n openshift-kube-controller-manager -c kube-controller-manager $POD curl https://localhost:10257/metrics -k + # Cleanup + unset POD + tests: + bin_op: and + test_items: + - flag: "secure-port" + compare: + op: eq + value: "\"10257\"" + - flag: "\"code\": 403" + remediation: | + Edit the Controller Manager pod specification file $controllermanagerconf + on the master node and ensure the correct value for the --bind-address parameter. + scored: false + + - id: 1.4 + text: "Scheduler" + checks: + - id: 1.4.1 + text: "Ensure that the healthz endpoints for the scheduler are protected by RBAC (Manual)" + type: manual + audit: | + # check configuration for ports, livenessProbe, readinessProbe, healthz + oc -n openshift-kube-scheduler get cm kube-scheduler-pod -o json | jq -r '.data."pod.yaml"' | jq '.spec.containers[].livenessProbe' + oc -n openshift-kube-scheduler get cm kube-scheduler-pod -o json | jq -r '.data."pod.yaml"' | jq '.spec.containers[].readinessProbe' + # Test to verify endpoints + oc -n openshift-kube-scheduler describe endpoints + # Test to validate RBAC enabled on the scheduler endpoint; check with non-admin role + # oc project openshift-kube-scheduler + export POD=$(oc get pods -n openshift-kube-scheduler -l app=openshift-kube-scheduler -o jsonpath='{.items[0].metadata.name}') + export PORT=$(oc get pod -n openshift-kube-scheduler $POD -o jsonpath='{.spec.containers[0].livenessProbe.httpGet.port}') + # Should return 403 Forbidden + oc rsh -n openshift-kube-scheduler ${POD} curl https://localhost:${PORT}/metrics -k + # Create a service account to test RBAC + oc create sa -n openshift-kube-scheduler permission-test-sa + # Should return 403 Forbidden + export SA_TOKEN=$(oc create token -n openshift-kube-scheduler permission-test-sa) + oc rsh -n openshift-kube-scheduler ${POD} curl https://localhost:${PORT}/metrics -H "Authorization: Bearer $SA_TOKEN" -k + # As cluster admin, should succeed + export CLUSTER_ADMIN_TOKEN=$(oc whoami -t) + oc rsh -n openshift-kube-scheduler ${POD} curl https://localhost:${PORT}/metrics -H "Authorization: Bearer $CLUSTER_ADMIN_TOKEN" -k + # Cleanup + unset CLUSTER_ADMIN_TOKEN POD PORT SA_TOKEN + oc delete sa -n openshift-kube-scheduler permission-test-sa + remediation: | + None required. By default, profiling is enabled and protected by RBAC. + scored: false + + - id: 1.4.2 + text: "Verify that the scheduler API service is protected by RBAC (Manual)" + type: manual + audit: | + # To verify endpoints + oc -n openshift-kube-scheduler describe endpoints + # To verify that bind-adress is not used in the configuration and that port is set to 0 + oc -n openshift-kube-scheduler get cm kube-scheduler-pod -o json | jq -r '.data."pod.yaml"' | jq '.spec.containers[]|select(.name=="kube-scheduler")|.args' + # To test for RBAC: + # oc project openshift-kube-scheduler + export POD=$(oc get pods -n openshift-kube-scheduler -l app=openshift-kube-scheduler -o jsonpath='{.items[0].metadata.name}') + export POD_IP=$(oc get pods -n openshift-kube-scheduler -l app=openshift-kube-scheduler -o jsonpath='{.items[0].status.podIP}') + export PORT=$(oc get pod -n openshift-kube-scheduler $POD -o jsonpath='{.spec.containers[0].livenessProbe.httpGet.port}') + # Should return a 403 + oc rsh -n openshift-kube-scheduler ${POD} curl https://${POD_IP}:${PORT}/metrics + # Create a service account to test RBAC + oc create sa -n openshift-kube-scheduler permission-test-sa + # Should return 403 Forbidden + export SA_TOKEN=$(oc create token -n openshift-kube-scheduler permission-test-sa) + oc rsh -n openshift-kube-scheduler ${POD} curl http://localhost:${PORT}/metrics -H "Authorization: Bearer $SA_TOKEN" -k + # As cluster admin, should succeed + export CLUSTER_ADMIN_TOKEN=$(oc whoami -t) + oc rsh ${POD} curl http://localhost:${PORT}/metrics -H "Authorization: Bearer $CLUSTER_ADMIN_TOKEN" -k + # Cleanup + unset CLUSTER_ADMIN_TOKEN POD PORT SA_TOKEN + oc delete sa -n openshift-kube-scheduler permission-test-sa + remediation: | + By default, the --bind-address argument is not used and the metrics endpoint is protected by RBAC when using the pod IP address. + scored: false diff --git a/cfg/rh-1.6/node.yaml b/cfg/rh-1.6/node.yaml new file mode 100644 index 000000000..f7ac9ca6c --- /dev/null +++ b/cfg/rh-1.6/node.yaml @@ -0,0 +1,384 @@ +--- +controls: +version: rh-1.6 +id: 4 +text: "Worker Node Security Configuration" +type: "node" +groups: + - id: 4.1 + text: "Worker Node Configuration Files" + checks: + - id: 4.1.1 + text: "Ensure that the kubelet service file permissions are set to 644 or more restrictive (Automated)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME %n permissions=%a" /etc/systemd/system/kubelet.service 2> /dev/null + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "644" + remediation: | + By default, the kubelet service file has permissions of 644. + scored: true + + - id: 4.1.2 + text: "Ensure that the kubelet service file ownership is set to root:root (Automated)" + audit: | + # Should return root:root for each node + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME %n %U:%G" /etc/systemd/system/kubelet.service 2> /dev/null + tests: + test_items: + - flag: root:root + remediation: | + By default, the kubelet service file has ownership of root:root. + scored: true + + - id: 4.1.3 + text: "If proxy kube proxy configuration file exists ensure permissions are set to 644 or more restrictive (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + # Get the pod name in the openshift-sdn namespace + POD_NAME=$(oc get pods -n openshift-sdn -l app=sdn --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-sdn "$POD_NAME" - stat -Lc "$i %n permissions=%a" /config/kube-proxy-config.yaml 2>/dev/null + fi + tests: + bin_op: or + test_items: + - flag: "permissions" + set: true + compare: + op: bitmask + value: "644" + remediation: | + None needed. + scored: false + + - id: 4.1.4 + text: "If proxy kubeconfig file exists ensure ownership is set to root:root (Manual)" + audit: | + # Get the node name where the pod is running + NODE_NAME=$(oc get pod "$HOSTNAME" -o=jsonpath='{.spec.nodeName}') + # Get the pod name in the openshift-sdn namespace + POD_NAME=$(oc get pods -n openshift-sdn -l app=sdn --field-selector spec.nodeName="$NODE_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + + if [ -z "$POD_NAME" ]; then + echo "No matching pods found on the current node." + else + # Execute the stat command + oc exec -n openshift-sdn "$POD_NAME" -- stat -Lc "$i %n %U:%G" /config/kube-proxy-config.yaml 2>/dev/null + fi + use_multiple_values: true + tests: + bin_op: or + test_items: + - flag: root:root + remediation: | + None required. The configuration is managed by OpenShift operators. + scored: false + + - id: 4.1.5 + text: "Ensure that the --kubeconfig kubelet.conf file permissions are set to 644 or more restrictive (Automated)" + audit: | + # Check permissions + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME %n permissions=%a" /etc/kubernetes/kubelet.conf 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "644" + remediation: | + None required. + scored: true + + - id: 4.1.6 + text: "Ensure that the --kubeconfig kubelet.conf file ownership is set to root:root (Automated)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME %n %U:%G" /etc/kubernetes/kubelet.conf 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: root:root + remediation: | + None required. + scored: true + + - id: 4.1.7 + text: "Ensure that the certificate authorities file permissions are set to 644 or more restrictive (Automated)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME %n permissions=%a" /etc/kubernetes/kubelet-ca.crt 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "644" + remediation: | + None required. + scored: true + + - id: 4.1.8 + text: "Ensure that the client certificate authorities file ownership is set to root:root (Automated)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME %n %U:%G" /etc/kubernetes/kubelet-ca.crt 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: root:root + remediation: | + None required. + scored: true + + - id: 4.1.9 + text: "Ensure that the kubelet --config configuration file has permissions set to 600 or more restrictive (Automated)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + # default setups have the file present at /var/lib/kubelet only. Custom setup is present at /var/data/kubelet/config.json. + oc debug node/$NODE_NAME -- /bin/sh -c ' + if [ -f /var/data/kubelet/config.json ]; then + chroot /host stat -c "$NODE_NAME %n permissions=%a" /var/data/kubelet/config.json; + else + chroot /host stat -c "$NODE_NAME %n permissions=%a" /var/lib/kubelet/config.json; + fi' 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "600" + remediation: | + None required. + scored: true + + - id: 4.1.10 + text: "Ensure that the kubelet configuration file ownership is set to root:root (Automated)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + # default setups have the file present at /var/lib/kubelet only. Custom setup is present at /var/data/kubelet/config.json. + oc debug node/$NODE_NAME -- /bin/sh -c ' + if [ -f /var/data/kubelet/config.json ]; then + chroot /host stat -c "$NODE_NAME %n %U:%G" /var/data/kubelet/config.json; + else + chroot /host stat -c "$NODE_NAME %n %U:%G" /var/lib/kubelet/config.json; + fi' 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: root:root + remediation: | + None required. + scored: true + + - id: 4.2 + text: "Kubelet" + checks: + - id: 4.2.1 + text: "Activate Garbage collection in OpenShift Container Platform 4, as appropriate (Manual)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc debug node/$NODE_NAME -- chroot /host grep -B4 -A1 anonymous /etc/kubernetes/kubelet.conf 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: "enabled: true" + set: false + remediation: | + To configure, follow the directions in Garbage Collection Remediation https://docs.openshift.com/container-platform/latest/nodes/nodes/nodes-nodes-garbage-collection.html. + scored: true + + - id: 4.2.2 + text: "Ensure that the --anonymous-auth argument is set to false (Automated)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.authentication.anonymous.enabled' 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: "true" + set: false + remediation: | + Create a kubeletconfig to explicitly disable anonymous authentication. Examples of how + to do this can be found in the OpenShift documentation. + scored: true + + - id: 4.2.3 + text: "Ensure that the --authorization-mode argument is not set to AlwaysAllow (Automated)" + type: manual + # Takes a lot of time for connection to fail and + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.authorization' 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: mode + compare: + op: noteq + value: AlwaysAllow + remediation: | + None required. Unauthenticated/Unauthorized users have no access to OpenShift nodes. + scored: true + + - id: 4.2.4 + text: "Ensure that the --client-ca-file argument is set as appropriate (Automated)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq -r '.kubeletconfig.authentication.x509.clientCAFile' 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: '/etc/kubernetes/kubelet-ca.crt' + remediation: | + None required. Changing the clientCAFile value is unsupported. + scored: true + + - id: 4.2.5 + text: "Verify that the read only port is not used or is set to 0 (Automated)" + audit: | + oc -n openshift-kube-apiserver get cm config -o json | jq -r '.data."config.yaml"' | jq -r '.apiServerArguments."kubelet-read-only-port"[]' 2> /dev/null + tests: + test_items: + - flag: '0' + remediation: | + In earlier versions of OpenShift 4, the read-only-port argument is not used. + Follow the instructions in the documentation https://docs.openshift.com/container-platform/latest/post_installation_configuration/machine-configuration-tasks.html#create-a-kubeletconfig-crd-to-edit-kubelet-parameters_post-install-machine-configuration-tasks + to create a kubeletconfig CRD and set the kubelet-read-only-port is set to 0. + scored: true + + - id: 4.2.6 + text: "Ensure that the --streaming-connection-idle-timeout argument is not set to 0 (Automated)" + audit: | + # Should return 1 for node + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + echo streamingConnectionIdleTimeout=$(oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.streamingConnectionIdleTimeout' 2> /dev/null) + use_multiple_values: true + tests: + test_items: + - flag: streamingConnectionIdleTimeout + compare: + op: noteq + value: 0s + remediation: | + Follow the instructions https://docs.openshift.com/container-platform/latest/post_installation_configuration/machine-configuration-tasks.html#create-a-kubeletconfig-crd-to-edit-kubelet-parameters_post-install-machine-configuration-tasks in the documentation to create a kubeletconfig CRD and set + the streamingConnectionIdleTimeout to the desired value. Do not set the value to 0. + scored: true + + - id: 4.2.7 + text: "Ensure that the --make-iptables-util-chains argument is set to true (Manual)" + audit: | + # Should return 1 for node + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.makeIPTablesUtilChains' 2> /dev/null + use_multiple_values: true + tests: + test_items: + - flag: "true" + remediation: | + None required. The makeIPTablesUtilChains argument is set to true by default. + scored: false + + - id: 4.2.8 + text: "Ensure that the kubeAPIQPS [--event-qps] argument is set to 0 or a level which ensures appropriate event capture (Manual)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + echo kubeAPIQPS=$(oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.kubeAPIQPS' 2> /dev/null) + tests: + test_items: + - flag: kubeAPIQPS + compare: + op: gte + value: 0 + remediation: | + None required by default. Follow the documentation to edit kubeletconfig parameters + https://docs.openshift.com/container-platform/4.15/post_installation_configuration/machine-configuration-tasks.html#create-a-kubeletconfig-crd-to-edit-kubelet-parameters_post-install-machine-configuration-tasks + scored: false + + - id: 4.2.9 + text: "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate (Manual)" + audit: | + oc get configmap config -n openshift-kube-apiserver -ojson | \ + jq -r '.data["config.yaml"]' | \ + jq -r '.apiServerArguments | ."kubelet-client-certificate"[0], ."kubelet-client-key"[0]' 2> /dev/null + tests: + bin_op: and + test_items: + - flag: "/etc/kubernetes/static-pod-certs/secrets/kubelet-client/tls.crt" + - flag: "/etc/kubernetes/static-pod-certs/secrets/kubelet-client/tls.key" + remediation: | + OpenShift automatically manages TLS authentication for the API server communication with the node/kublet. + This is not configurable. + scored: true + + - id: 4.2.10 + text: "Ensure that the --rotate-certificates argument is not set to false (Manual)" + audit: | + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + echo rotateCertificates=$(oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq -r '.kubeletconfig.rotateCertificates' 2> /dev/null) + use_multiple_values: true + tests: + test_items: + - flag: rotateCertificates + compare: + op: eq + value: true + remediation: | + None required. + scored: false + + - id: 4.2.11 + text: "Verify that the RotateKubeletServerCertificate argument is set to true (Manual)" + audit: | + #Verify the rotateKubeletServerCertificate feature gate is on + NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') + echo RotateKubeletServerCertificate=$(oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.featureGates.RotateKubeletServerCertificate' 2> /dev/null) + # Verify the rotateCertificates argument is set to true + echo rotateCertificates=$(oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq -r '.kubeletconfig.rotateCertificates' 2> /dev/null) + tests: + bin_op: and + test_items: + - flag: RotateKubeletServerCertificate + compare: + op: eq + value: true + - flag: rotateCertificates + compare: + op: eq + value: true + remediation: | + None required. By default, kubelet server certificate rotation is enabled. + scored: false + + - id: 4.2.12 + text: "Ensure that the Kubelet only makes use of Strong Cryptographic Ciphers (Manual)" + audit: | + # needs verification + # verify cipher suites + oc get --namespace=openshift-ingress-operator ingresscontroller/default -o json | jq '.status.tlsProfile.ciphers' 2> /dev/null + oc get kubeapiservers.operator.openshift.io cluster -o json | jq '.spec.observedConfig.servingInfo.cipherSuites' 2> /dev/null + oc get openshiftapiservers.operator.openshift.io cluster -o json | jq '.spec.observedConfig.servingInfo.cipherSuites' 2> /dev/null + oc get cm -n openshift-authentication v4-0-config-system-cliconfig -o jsonpath='{.data.v4\-0\-config\-system\-cliconfig}' | jq '.servingInfo.cipherSuites' 2> /dev/null + #check value for tlsSecurityProfile; null is returned if default is used + oc get kubeapiservers.operator.openshift.io cluster -o json |jq .spec.tlsSecurityProfile 2> /dev/null + type: manual + remediation: | + Follow the directions above and in the OpenShift documentation to configure the tlsSecurityProfile. + Configuring Ingress. https://docs.openshift.com/container-platform/4.15/networking/ingress-operator.html#nw-ingress-controller-configuration-parameters_configuring-ingress + Please reference the OpenShift TLS security profile documentation for more detail on each profile. + https://docs.openshift.com/container-platform/4.15/security/tls-security-profiles.html + scored: false diff --git a/cfg/rh-1.6/policies.yaml b/cfg/rh-1.6/policies.yaml new file mode 100644 index 000000000..51e2b5d7f --- /dev/null +++ b/cfg/rh-1.6/policies.yaml @@ -0,0 +1,347 @@ +--- +controls: +version: rh-1.6 +id: 5 +text: "Kubernetes Policies" +type: "policies" +groups: + - id: 5.1 + text: "RBAC and Service Accounts" + checks: + - id: 5.1.1 + text: "Ensure that the cluster-admin role is only used where required (Manual)" + type: "manual" + audit: | + #To get a list of users and service accounts with the cluster-admin role + oc get clusterrolebindings -o=custom-columns="NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].kind" | grep cluster-admin + #To verity that kbueadmin is removed, no results should be returned + oc get secrets kubeadmin -n kube-system + remediation: | + Identify all clusterrolebindings to the cluster-admin role. Check if they are used and + if they need this role or if they could use a role with fewer privileges. + Where possible, first bind users to a lower privileged role and then remove the + clusterrolebinding to the cluster-admin role : + oc delete clusterrolebinding [name] + scored: false + + - id: 5.1.2 + text: "Minimize access to secrets (Manual)" + type: "manual" + remediation: | + Where possible, remove get, list and watch access to secret objects in the cluster. + scored: false + + - id: 5.1.3 + text: "Minimize wildcard use in Roles and ClusterRoles (Manual)" + type: "manual" + audit: | + # needs verification + # Run the command below to describe each cluster role and inspect it for wildcard usage + oc describe clusterrole + # Run the command below to describe each role and inspect it for wildcard usage + oc describe role -A + remediation: | + Where possible replace any use of wildcards in clusterroles and roles with specific + objects or actions. + scored: false + + - id: 5.1.4 + text: "Minimize access to create pods (Manual)" + type: "manual" + audit: | + # needs verification + # Review the users who have create access to pod objects in the Kubernetes API + oc adm policy who-can create pod + remediation: | + Where possible, remove create access to pod objects in the cluster. + scored: false + + - id: 5.1.5 + text: "Ensure that default service accounts are not actively used. (Manual)" + type: "manual" + remediation: | + None required. + scored: false + + - id: 5.1.6 + text: "Ensure that Service Account Tokens are only mounted where necessary (Manual)" + type: "manual" + audit: | + # needs verification + # Find all pods that automatically mount service account tokens + oc get pods -A -o json | jq '.items[] | select(.spec.automountServiceAccountToken) | .metadata.name' + # Find all service accounts that automatically mount service tokens + oc get serviceaccounts -A -o json | jq '.items[] | select(.automountServiceAccountToken) | .metadata.name' + remediation: | + Modify the definition of pods and service accounts which do not need to mount service + account tokens to disable it. + scored: false + + - id: 5.2 + text: "Pod Security Policies" + checks: + - id: 5.2.1 + text: "Minimize the admission of privileged containers (Manual)" + audit: | + # needs verification + oc get scc -o=custom-columns=NAME:.metadata.name,allowPrivilegedContainer:.allowPrivilegedContainer + tests: + test_items: + - flag: "false" + remediation: | + Create an SCC that sets allowPrivilegedContainer to false and take it into use by + assigning it to applicable users and groups. + scored: false + + - id: 5.2.2 + text: "Minimize the admission of containers wishing to share the host process ID namespace (Manual)" + audit: | + oc get scc -o=custom-columns=NAME:.metadata.name,allowHostPID:.allowHostPID + tests: + test_items: + - flag: "false" + remediation: | + Create an SCC that sets allowHostPID to false and take it into use by assigning it to + applicable users and groups. + scored: false + + - id: 5.2.3 + text: "Minimize the admission of containers wishing to share the host IPC namespace (Manual)" + audit: | + oc get scc -o=custom-columns=NAME:.metadata.name,allowHostIPC:.allowHostIPC + tests: + test_items: + - flag: "false" + remediation: | + Create an SCC that sets allowHostIPC to false and take it into use by assigning it to + applicable users and groups. + scored: false + + - id: 5.2.4 + text: "Minimize the admission of containers wishing to share the host network namespace (Manual)" + audit: | + oc get scc -o=custom-columns=NAME:.metadata.name,allowHostNetwork:.allowHostNetwork + tests: + test_items: + - flag: "false" + remediation: | + Create an SCC that sets allowHostNetwork to false and take it into use by assigning + it to applicable users and groups. + scored: false + + - id: 5.2.5 + text: "Minimize the admission of containers with allowPrivilegeEscalation (Manual)" + audit: | + oc get scc -o=custom-columns=NAME:.metadata.name,allowPrivilegeEscalation:.allowPrivilegeEscalation + tests: + test_items: + - flag: "false" + remediation: | + Create an SCC that sets allowPrivilegeEscalation to false and take it into use by + assigning it to applicable users and groups. + scored: false + + - id: 5.2.6 + text: "Minimize the admission of root containers (Manual)" + audit: | + # needs verification + oc get scc -o=custom-columns=NAME:.metadata.name,runAsUser:.runAsUser.type + #For SCCs with MustRunAs verify that the range of UIDs does not include 0 + oc get scc -o=custom-columns=NAME:.metadata.name,uidRangeMin:.runAsUser.uidRangeMin,uidRangeMax:.runAsUser.uidRangeMax + tests: + bin_op: or + test_items: + - flag: "MustRunAsNonRoot" + - flag: "MustRunAs" + compare: + op: nothave + value: 0 + remediation: | + None required. By default, OpenShift includes the nonroot and nonroot-v2 SCCs that + restrict the ability to run as nonroot. If additional SCCs are appropriate, follow the + OpenShift documentation to create custom SCCs. + scored: false + + - id: 5.2.7 + text: "Minimize the admission of containers with the NET_RAW capability (Manual)" + audit: | + # needs verification + oc get scc -o=custom-columns=NAME:.metadata.name,requiredDropCapabilities:.requiredDropCapabilities + tests: + bin_op: or + test_items: + - flag: "ALL" + - flag: "NET_RAW" + remediation: | + Create an SCC that sets requiredDropCapabilities to include ALL or at least + NET_RAW and take it into use by assigning it to applicable users and groups. + scored: false + + - id: 5.2.8 + text: "Minimize the admission of containers with added capabilities (Manual)" + type: "manual" + audit: | + # needs verification + # List all SCCs that prohibit users from defining container capabilities + oc get scc -A -o json | jq '.items[] | select(.allowedCapabilities==null) | .metadata.name' + # List all SCCs that do not set default container capabilities + oc get scc -A -o json | jq '.items[] | select(.defaultAddCapabilities==null) | .metadata.name' + tests: + test_items: + - flag: "false" + remediation: | + Utilize the restricted-v2 SCC or create an SCC that sets allowedCapabilities and + defaultAddCapabilities to an empty list and take it into use by assigning it to + applicable users and groups. + scored: false + + - id: 5.2.9 + text: "Minimize the admission of containers with capabilities assigned (Manual)" + type: "manual" + audit: | + # needs verification + # List all SCCs that drop all capabilities from containers + oc get scc -A -o json | jq '.items[] | select(.requiredDropCapabilities[]?|any(. == "ALL"; .)) | .metadata.name' + tests: + test_items: + - flag: "false" + remediation: | + Review the use of capabilities in applications running on your cluster. Where a namespace + contains applications which do not require any Linux capabilities to operate, consider + adding a SCC which forbids the admission of containers which do not drop all capabilities. + scored: false + + - id: 5.2.10 + text: "Minimize access to privileged Security Context Constraints (Manual)" + type: "manual" + audit: | + # needs verification + # All users and groups with access to SCCs that include privileged or elevated capabilities. + oc get scc -ojson | jq '.items[]|select(.allowHostIPC or .allowHostPID or .allowHostPorts + or .allowHostNetwork or .allowHostDirVolumePlugin + or .allowPrivilegedContainer or .runAsUser.type != "MustRunAsRange") | + .metadata.name,{"Group:":.groups},{"User":.users}' + tests: + test_items: + - flag: "false" + remediation: | + Remove any users and groups who do not need access to an SCC, following the principle of least privilege. + You can remove users and groups from an SCC using the oc edit scc $NAME command. + Additionally, you can create your own SCCs that contain the container functionality you + need for a particular use case and assign that SCC to users and groups if the default + SCCs are not appropriate for your use case. + scored: false + + - id: 5.3 + text: "Network Policies and CNI" + checks: + - id: 5.3.1 + text: "Ensure that the CNI in use supports Network Policies (Manual)" + type: "manual" + remediation: | + None required. This will depend on the CNI plugin in use. + scored: false + + - id: 5.3.2 + text: "Ensure that all Namespaces have Network Policies defined (Manual)" + type: "manual" + audit: | + #Run the following command and review the NetworkPolicy objects created in the cluster. + oc -n all get networkpolicy + remediation: | + Follow the documentation and create NetworkPolicy objects as you need them. + scored: false + + - id: 5.4 + text: "Secrets Management" + checks: + - id: 5.4.1 + text: "Prefer using secrets as files over secrets as environment variables (Manual)" + type: "manual" + audit: | + #Run the following command to find references to objects which use environment variables defined from secrets. + oc get all -o jsonpath='{range .items[?(@..secretKeyRef)]} {.kind} {.metadata.name} {"\n"}{end}' -A + remediation: | + If possible, rewrite application code to read secrets from mounted secret files, rather than + from environment variables. + scored: false + + - id: 5.4.2 + text: "Consider external secret storage (Manual)" + type: "manual" + remediation: | + Refer to the secrets management options offered by your cloud provider or a third-party + secrets management solution. + scored: false + + - id: 5.5 + text: "Extensible Admission Control" + checks: + - id: 5.5.1 + text: "Configure Image Provenance using ImagePolicyWebhook admission controller (Manual)" + type: "manual" + audit: | + # needs verification + oc get image.config.openshift.io/cluster -o json | jq .spec.registrySources + remediation: | + Follow the OpenShift documentation for Image Configuration resources: https://docs.openshift.com/container-platform/4.15/openshift_images/image-configuration.html + scored: false + + - id: 5.7 + text: "General Policies" + checks: + - id: 5.7.1 + text: "Create administrative boundaries between resources using namespaces (Manual)" + type: "manual" + audit: | + #Run the following command and review the namespaces created in the cluster. + oc get namespaces + #Ensure that these namespaces are the ones you need and are adequately administered as per your requirements. + oc get namespaces -o json | jq '.items[] | select(.metadata.name|test("(?!default|kube-.|openshift|openshift-.)^.*")) | .metadata.name' + remediation: | + Follow the documentation and create namespaces for objects in your deployment as you need them. + scored: false + + - id: 5.7.2 + text: "Ensure that the seccomp profile is set to docker/default in your pod definitions (Manual)" + type: "manual" + audit: | + oc get pods -A -o json | jq '.items[] | select( (.metadata.namespace | test("^kube*|^openshift*") | not) + and .spec.securityContext.seccompProfile.type==null) | + (.metadata.namespace + "/" + .metadata.name)' + remediation: | + For any non-privileged pods or containers that do not have seccomp profiles, consider + using the RuntimeDefault or creating a custom seccomp profile specifically for the workload. + Please refer to the OpenShift documentation for working with custom seccomp profiles. + https://docs.openshift.com/container-platform/4.15/security/seccomp-profiles.html + scored: false + + - id: 5.7.3 + text: "Apply Security Context to Your Pods and Containers (Manual)" + type: "manual" + audit: | + # needs verification + # obtain a list of pods that are using privileged security context constraints + oc get pods -A -o json | jq '.items[] | select(.metadata.annotations."openshift.io/scc"|test("privileged"?)) | .metadata.name' + # obtain a list of pods that are not using security context constraints at all + oc get pods -A -o json | jq '.items[] | select(.metadata.annotations."openshift.io/scc" == null) | .metadata.name' + remediation: | + Follow the Kubernetes documentation and apply security contexts to your pods. For a + suggested list of security contexts, you may refer to the CIS Security Benchmark for Docker + Containers. + scored: false + + - id: 5.7.4 + text: "The default namespace should not be used (Manual)" + type: "manual" + audit: | + # Run the following command to list all resources in the default namespace, besides the kubernetes and + # openshift services, which are expected to be in the default namespace + oc get all -n default -o json | jq '.items[] | select((.kind|test("Service")) + and (.metadata.name|test("openshift|kubernetes"))? | not) | + (.kind + "/" + .metadata.name)' + #The only entries there should be system managed resources such as the kubernetes and openshift service + remediation: | + Ensure that namespaces are created to allow for appropriate segregation of Kubernetes + resources and that all new resources are created in a specific namespace. + scored: false diff --git a/cmd/common_test.go b/cmd/common_test.go index 53793a000..e02e73224 100644 --- a/cmd/common_test.go +++ b/cmd/common_test.go @@ -248,6 +248,7 @@ func TestMapToCISVersion(t *testing.T) { {kubeVersion: "gke-1.2.0", succeed: true, exp: "gke-1.2.0"}, {kubeVersion: "ocp-3.10", succeed: true, exp: "rh-0.7"}, {kubeVersion: "ocp-3.11", succeed: true, exp: "rh-0.7"}, + {kubeVersion: "ocp-4.15", succeed: true, exp: "rh-1.6"}, {kubeVersion: "unknown", succeed: false, exp: "", expErr: "unable to find a matching Benchmark Version match for kubernetes version: unknown"}, } for _, c := range cases { diff --git a/cmd/util.go b/cmd/util.go index 275de2326..e73b8d84c 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -507,6 +507,8 @@ func getPlatformBenchmarkVersion(platform Platform) string { return "rh-0.7" case "4.1": return "rh-1.0" + case "4.15": + return "rh-1.6" } case "vmware": return "tkgi-1.2.53" @@ -581,7 +583,7 @@ func getOcpValidVersion(ocpVer string) (string, error) { for !isEmpty(ocpVer) { glog.V(3).Info(fmt.Sprintf("getOcpBenchmarkVersion check for ocp: %q \n", ocpVer)) - if ocpVer == "3.10" || ocpVer == "4.1" { + if ocpVer == "4.15" || ocpVer == "4.1" || ocpVer == "3.10" { glog.V(1).Info(fmt.Sprintf("getOcpBenchmarkVersion found valid version for ocp: %q \n", ocpVer)) return ocpVer, nil } diff --git a/cmd/util_test.go b/cmd/util_test.go index 2c24a7a95..0d0477b44 100644 --- a/cmd/util_test.go +++ b/cmd/util_test.go @@ -708,6 +708,13 @@ func Test_getPlatformBenchmarkVersion(t *testing.T) { }, want: "rh-1.0", }, + { + name: "openshift4_15", + args: args{ + platform: Platform{Name: "ocp", Version: "4.15"}, + }, + want: "rh-1.6", + }, { name: "k3s", args: args{ @@ -751,6 +758,7 @@ func Test_getOcpValidVersion(t *testing.T) { {openShiftVersion: "4.1", succeed: true, exp: "4.1"}, {openShiftVersion: "4.5", succeed: true, exp: "4.1"}, {openShiftVersion: "4.6", succeed: true, exp: "4.1"}, + {openShiftVersion: "4.16", succeed: true, exp: "4.15"}, {openShiftVersion: "invalid", succeed: false, exp: ""}, } for _, c := range cases { diff --git a/docs/architecture.md b/docs/architecture.md index c65978f0a..9d4684a5b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -13,28 +13,29 @@ Check the contents of the benchmark directory under `cfg` to see which targets a The following table shows the valid targets based on the CIS Benchmark version. -| CIS Benchmark | Targets | -|----------------------|---------| -| cis-1.5 | master, controlplane, node, etcd, policies | -| cis-1.6 | master, controlplane, node, etcd, policies | -| cis-1.20 | master, controlplane, node, etcd, policies | -| cis-1.23 | master, controlplane, node, etcd, policies | -| cis-1.24 | master, controlplane, node, etcd, policies | -| cis-1.7 | master, controlplane, node, etcd, policies | -| cis-1.8 | master, controlplane, node, etcd, policies | -| cis-1.9 | master, controlplane, node, etcd, policies | -| gke-1.0 | master, controlplane, node, etcd, policies, managedservices | -| gke-1.2.0 | controlplane, node, policies, managedservices | -| gke-1.6.0 | controlplane, node, policies, managedservices | -| eks-1.0.1 | controlplane, node, policies, managedservices | -| eks-1.1.0 | controlplane, node, policies, managedservices | -| eks-1.2.0 | controlplane, node, policies, managedservices | -| ack-1.0 | master, controlplane, node, etcd, policies, managedservices | -| aks-1.0 | controlplane, node, policies, managedservices | -| rh-0.7 | master,node| -| rh-1.0 | master, controlplane, node, etcd, policies | -| cis-1.6-k3s | master, controlplane, node, etcd, policies | -| cis-1.24-microk8s | master, controlplane, node, etcd, policies | +| CIS Benchmark | Targets | +|-------------------|---------| +| cis-1.5 | master, controlplane, node, etcd, policies | +| cis-1.6 | master, controlplane, node, etcd, policies | +| cis-1.20 | master, controlplane, node, etcd, policies | +| cis-1.23 | master, controlplane, node, etcd, policies | +| cis-1.24 | master, controlplane, node, etcd, policies | +| cis-1.7 | master, controlplane, node, etcd, policies | +| cis-1.8 | master, controlplane, node, etcd, policies | +| cis-1.9 | master, controlplane, node, etcd, policies | +| gke-1.0 | master, controlplane, node, etcd, policies, managedservices | +| gke-1.2.0 | controlplane, node, policies, managedservices | +| gke-1.6.0 | controlplane, node, policies, managedservices | +| eks-1.0.1 | controlplane, node, policies, managedservices | +| eks-1.1.0 | controlplane, node, policies, managedservices | +| eks-1.2.0 | controlplane, node, policies, managedservices | +| ack-1.0 | master, controlplane, node, etcd, policies, managedservices | +| aks-1.0 | controlplane, node, policies, managedservices | +| rh-0.7 | master,node| +| rh-1.0 | master, controlplane, node, etcd, policies | +| rh-1.6 | master, controlplane, node, etcd, policies | +| cis-1.6-k3s | master, controlplane, node, etcd, policies | +| cis-1.24-microk8s | master, controlplane, node, etcd, policies | The following table shows the valid DISA STIG versions diff --git a/docs/platforms.md b/docs/platforms.md index d6fbcf712..543ed3f7d 100644 --- a/docs/platforms.md +++ b/docs/platforms.md @@ -17,7 +17,7 @@ Some defined by other hardenening guides. | CIS | [1.24](https://workbench.cisecurity.org/benchmarks/10873) | cis-1.24 | 1.24 | | CIS | [1.7](https://workbench.cisecurity.org/benchmarks/11107) | cis-1.7 | 1.25 | | CIS | [1.8](https://workbench.cisecurity.org/benchmarks/12958) | cis-1.8 | 1.26 | -| CIS | [1.9](https://workbench.cisecurity.org/benchmarks/16828) | cis-1.9 | 1.27-1.29 | +| CIS | [1.9](https://workbench.cisecurity.org/benchmarks/16828) | cis-1.9 | 1.27-1.29 | | CIS | [GKE 1.0.0](https://workbench.cisecurity.org/benchmarks/4536) | gke-1.0 | GKE | | CIS | [GKE 1.2.0](https://workbench.cisecurity.org/benchmarks/7534) | gke-1.2.0 | GKE | | CIS | [GKE 1.6.0](https://workbench.cisecurity.org/benchmarks/16093) | gke-1.6.0 | GKE | @@ -27,7 +27,8 @@ Some defined by other hardenening guides. | CIS | [ACK 1.0.0](https://workbench.cisecurity.org/benchmarks/6467) | ack-1.0 | ACK | | CIS | [AKS 1.0.0](https://workbench.cisecurity.org/benchmarks/6347) | aks-1.0 | AKS | | RHEL | RedHat OpenShift hardening guide | rh-0.7 | OCP 3.10-3.11 | -| CIS | [OCP4 1.1.0](https://workbench.cisecurity.org/benchmarks/6778) | rh-1.0 | OCP 4.1- | +| CIS | [OCP4 1.1.0](https://workbench.cisecurity.org/benchmarks/6778) | rh-1.0 | OCP 4.1-4.14 | +| CIS | [OCP4 1.6.0](https://workbench.cisecurity.org/benchmarks/16094) | rh-1.6 | OCP 4.15- | | CIS | [1.6.0-k3s](https://docs.rancher.cn/docs/k3s/security/self-assessment/_index) | cis-1.6-k3s | k3s v1.16-v1.24 | | DISA | [Kubernetes Ver 1, Rel 6](https://dl.dod.cyber.mil/wp-content/uploads/stigs/zip/U_Kubernetes_V1R6_STIG.zip) | eks-stig-kubernetes-v1r6 | EKS | | CIS | [TKGI 1.2.53](https://network.pivotal.io/products/p-compliance-scanner#/releases/1248397) | tkgi-1.2.53 | vmware | diff --git a/docs/running.md b/docs/running.md index c482a78b6..102ea1fd4 100644 --- a/docs/running.md +++ b/docs/running.md @@ -132,9 +132,10 @@ docker push .dkr.ecr..amazonaws.com/k8s/kube-bench: ### Running on OpenShift | OpenShift Hardening Guide | kube-bench config | -| ------------------------- | ----------------- | +|---------------------------|-------------------| | ocp-3.10 + | rh-0.7 | -| ocp-4.1 + | rh-1.0 | +| ocp-4.1-4.14 | rh-1.0 | +| ocp-4.15 + | rh-1.6 | kube-bench includes a set of test files for Red Hat's OpenShift hardening guide for OCP 3.10 and 4.1. To run this you will need to specify `--benchmark rh-07`, or `--version ocp-3.10` or,`--version ocp-4.5` or `--benchmark rh-1.0` diff --git a/job-ocp.yaml b/job-ocp.yaml new file mode 100644 index 000000000..cd05ecccc --- /dev/null +++ b/job-ocp.yaml @@ -0,0 +1,106 @@ +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: kube-bench +spec: + template: + metadata: + labels: + app: kube-bench + spec: + serviceAccountName: kube-bench + automountServiceAccountToken: true + containers: + - command: ["kube-bench"] + image: docker.io/aquasec/kube-bench:latest + name: kube-bench + volumeMounts: + - name: var-lib-cni + mountPath: /var/lib/cni + readOnly: true + - mountPath: /var/lib/etcd + name: var-lib-etcd + readOnly: true + - mountPath: /var/lib/kubelet + name: var-lib-kubelet + readOnly: true + - mountPath: /var/lib/kube-scheduler + name: var-lib-kube-scheduler + readOnly: true + - mountPath: /var/lib/kube-controller-manager + name: var-lib-kube-controller-manager + readOnly: true + - mountPath: /etc/systemd + name: etc-systemd + readOnly: true + - mountPath: /lib/systemd/ + name: lib-systemd + readOnly: true + - mountPath: /srv/kubernetes/ + name: srv-kubernetes + readOnly: true + - mountPath: /etc/kubernetes + name: etc-kubernetes + readOnly: true + - mountPath: /usr/local/mount-from-host/bin + name: usr-bin + readOnly: true + - mountPath: /etc/cni/net.d/ + name: etc-cni-netd + readOnly: true + - mountPath: /opt/cni/bin/ + name: opt-cni-bin + readOnly: true + - name: etc-passwd + mountPath: /etc/passwd + readOnly: true + - name: etc-group + mountPath: /etc/group + readOnly: true + + hostPID: true + restartPolicy: Never + volumes: + - name: var-lib-cni + hostPath: + path: /var/lib/cni + - hostPath: + path: /var/lib/etcd + name: var-lib-etcd + - hostPath: + path: /var/lib/kubelet + name: var-lib-kubelet + - hostPath: + path: /var/lib/kube-scheduler + name: var-lib-kube-scheduler + - hostPath: + path: /var/lib/kube-controller-manager + name: var-lib-kube-controller-manager + - hostPath: + path: /etc/systemd + name: etc-systemd + - hostPath: + path: /lib/systemd + name: lib-systemd + - hostPath: + path: /srv/kubernetes + name: srv-kubernetes + - hostPath: + path: /etc/kubernetes + name: etc-kubernetes + - hostPath: + path: /usr/bin + name: usr-bin + - hostPath: + path: /etc/cni/net.d/ + name: etc-cni-netd + - hostPath: + path: /opt/cni/bin/ + name: opt-cni-bin + - hostPath: + path: "/etc/passwd" + name: etc-passwd + - hostPath: + path: "/etc/group" + name: etc-group