diff --git a/cfg/cis-1.11/master.yaml b/cfg/cis-1.11/master.yaml index 39fab0bf3..7eeaff3b3 100644 --- a/cfg/cis-1.11/master.yaml +++ b/cfg/cis-1.11/master.yaml @@ -752,7 +752,7 @@ groups: value: "aescbc,kms,secretbox" remediation: | Follow the Kubernetes documentation and configure a EncryptionConfig file. - In this file, choose aescbc, kms or secretbox as the encryption provider. + In this file, choose aescbc, kms, or secretbox as the encryption provider. scored: false - id: 1.2.29 diff --git a/cfg/cis-1.11/node.yaml b/cfg/cis-1.11/node.yaml index 1b05cc525..dfea33ab2 100644 --- a/cfg/cis-1.11/node.yaml +++ b/cfg/cis-1.11/node.yaml @@ -466,6 +466,7 @@ groups: Set the parameter, either via the --seccomp-default command line parameter or the seccompDefault configuration file setting. By default the seccomp profile is not enabled. + https://kubernetes.io/docs/tutorials/security/seccomp/#enable-the-use-of-runtimedefault-as-the-default-seccomp-profile-for-all-workloads scored: false - id: 4.2.15 diff --git a/cfg/eks-1.7.0/controlplane.yaml b/cfg/eks-1.7.0/controlplane.yaml index dad66c24b..480ff744d 100644 --- a/cfg/eks-1.7.0/controlplane.yaml +++ b/cfg/eks-1.7.0/controlplane.yaml @@ -9,7 +9,7 @@ groups: text: "Logging" checks: - id: 2.1.1 - text: "Enable audit Logs (Manual)" + text: "Enable audit Logs (Automated)" type: manual remediation: | From Console: @@ -34,36 +34,37 @@ groups: - id: 2.1.2 text: "Ensure audit logs are collected and managed (Manual)" - type: manual + type: "manual" remediation: | - Create or update the audit-policy.yaml to specify the audit logging configuration: - apiVersion: audit.k8s.io/v1 - kind: Policy - rules: - - level: Metadata - resources: - - group: "" - resources: ["pods"] - Apply the audit policy configuration to the cluster: - kubectl apply -f .yaml - Ensure audit logs are forwarded to a centralized logging system like CloudWatch, Elasticsearch, or another log management solution: - kubectl create configmap cluster-audit-policy --from-file=audit-policy.yaml -n kube-system - kubectl apply -f - <.yaml + 3. Ensure audit logs are forwarded to a centralized logging system like CloudWatch, + Elasticsearch, or another log management solution: + kubectl create configmap cluster-audit-policy --from-file=audit-policy.yaml -n kube-system + kubectl apply -f - < \ - --flatten="bindings[].members" \ - --format='table(bindings.members,bindings.role)' \ - --filter="bindings.role:roles/storage.admin OR bindings.role:roles/storage.objectAdmin OR bindings.role:roles/storage.objectCreator OR bindings.role:roles/storage.legacyBucketOwner OR bindings.role:roles/storage.legacyBucketWriter OR bindings.role:roles/storage.legacyObjectOwner" type: "manual" remediation: | For Images Hosted in AR: @@ -73,11 +68,6 @@ groups: - id: 5.1.3 text: "Minimize cluster access to read-only for Container Image repositories (Manual)" - audit: | - gcloud projects get-iam-policy \ - --flatten="bindings[].members" \ - --format='table(bindings.members,bindings.role)' \ - --filter="bindings.role:roles/storage.admin OR bindings.role:roles/storage.objectAdmin OR bindings.role:roles/storage.objectCreator OR bindings.role:roles/storage.legacyBucketOwner OR bindings.role:roles/storage.legacyBucketWriter OR bindings.role:roles/storage.legacyObjectOwner" type: "manual" remediation: | For Images Hosted in AR: @@ -123,14 +113,30 @@ groups: - id: 5.1.4 text: "Ensure only trusted container images are used (Manual)" - audit: | - gcloud container clusters describe $CLUSTER_NAME --zone $COMPUTE_ZONE --format json | jq .binaryAuthorization type: "manual" remediation: | + Using Google Cloud Console: + 1. Go to Binary Authorization by visiting: + https://console.cloud.google.com/security/binary-authorization. + 2. Enable the Binary Authorization API (if disabled). + 3. Create an appropriate policy for use with the cluster. See + https://cloud.google.com/binary-authorization/docs/policy-yaml-reference for + guidance. + 4. Go to Kubernetes Engine by visiting: + https://console.cloud.google.com/kubernetes/list. + 5. Select the cluster for which Binary Authorization is disabled. + 6. Under the details pane, within the Security section, click on the pencil icon + named Edit Binary Authorization. + 7. Check the box next to Enable Binary Authorization. + 8. Choose Enforce policy and provide a directory for the policy to be used. + 9. Click SAVE CHANGES. + Using Command Line: Update the cluster to enable Binary Authorization: - gcloud container cluster update --enable-binauthz + gcloud container cluster update --zone --binauthz-evaluation-mode= + + See: https://cloud.google.com/sdk/gcloud/reference/container/clusters/update#--binauthz-evaluation-mode for more details around the evaluation modes available. Create a Binary Authorization Policy using the Binary Authorization Policy Reference: https://cloud.google.com/binary-authorization/docs/policy-yaml-reference for guidance. @@ -144,7 +150,7 @@ groups: text: "Identity and Access Management (IAM)" checks: - id: 5.2.1 - text: "Ensure GKE clusters are not running using the Compute Engine default service account (Automated))" + text: "Ensure GKE clusters are not running using the Compute Engine default service account (Automated)" audit: | gcloud container node-pools describe $NODE_POOL --cluster $CLUSTER_NAME --zone $COMPUTE_ZONE --format json | jq '.config.serviceAccount' type: "manual" @@ -180,8 +186,6 @@ groups: - id: 5.2.2 text: "Prefer using dedicated GCP Service Accounts and Workload Identity (Manual)" - audit: | - gcloud container clusters describe $CLUSTER_NAME --zone $COMPUTE_ZONE --format json | jq .workloadIdentityConfig type: "manual" remediation: | Using Command Line: @@ -406,6 +410,11 @@ groups: gcloud container clusters create --zone \ --enable-ip-alias + + If using Autopilot configuration mode: + + gcloud container clusters create-auto \ + --zone scored: false - id: 5.6.3 @@ -604,6 +613,8 @@ groups: Create a new cluster without a Client Certificate: gcloud container clusters create [CLUSTER_NAME] \ --no-issue-client-certificate + In addition it's important to restrict access to the CSR API in Kubernetes to prevent + users from using it to issue new client certificate credentials. scored: false - id: 5.8.2 @@ -709,10 +720,10 @@ groups: --cluster --image-type=cos_containerd --sandbox="type=gvisor" scored: false - - id: 5.10.5 + - id: 5.10.4 text: "Enable Security Posture (Manual)" - audit: "gcloud container clusters --location describe" type: "manual" + audit: "gcloud container clusters --location describe" remediation: | Enable security posture via the UI, gCloud or API. https://cloud.google.com/kubernetes-engine/docs/how-to/protect-workload-configuration diff --git a/cfg/gke-1.8.0/node.yaml b/cfg/gke-1.8.0/node.yaml index 4ccc1837f..014684c2f 100644 --- a/cfg/gke-1.8.0/node.yaml +++ b/cfg/gke-1.8.0/node.yaml @@ -10,7 +10,7 @@ groups: checks: - id: 3.1.1 text: "Ensure that the kubeconfig file permissions are set to 644 or more restrictive (Automated)" - audit: '/bin/sh -c ''if test -e $proxykubeconfig; then stat -c permissions=%a $proxykubeconfig; fi'' ' + audit: '/bin/sh -c ''if test -e $kubeletkubeconfig; then stat -c permissions=%a $kubeletkubeconfig; fi'' ' tests: test_items: - flag: "permissions" @@ -21,12 +21,12 @@ groups: Run the below command (based on the file location on your system) on each worker node. For example, - chmod 644 $proxykubeconfig + chmod 644 $kubeletkubeconfig scored: true - id: 3.1.2 text: "Ensure that the kubelet kubeconfig file ownership is set to root:root (Automated)" - audit: '/bin/sh -c ''if test -e $proxykubeconfig; then stat -c %U:%G $proxykubeconfig; fi'' ' + audit: '/bin/sh -c ''if test -e $kubeletkubeconfig; then stat -c %U:%G $kubeletkubeconfig; fi'' ' tests: test_items: - flag: root:root @@ -34,7 +34,7 @@ groups: Run the below command (based on the file location on your system) on each worker node. For example: - chown root:root $proxykubeconfig + chown root:root $kubeletkubeconfig scored: true - id: 3.1.3 diff --git a/cfg/gke-1.8.0/policies.yaml b/cfg/gke-1.8.0/policies.yaml index 7f1856d0a..43938dc86 100644 --- a/cfg/gke-1.8.0/policies.yaml +++ b/cfg/gke-1.8.0/policies.yaml @@ -418,7 +418,7 @@ groups: type: "manual" remediation: | Follow the Kubernetes documentation and setup image provenance. - Also see recommendation 5.10.4. + Also see recommendation 5.1.4. scored: false - id: 4.6 diff --git a/cfg/rh-1.8/controlplane.yaml b/cfg/rh-1.8/controlplane.yaml index 0d5def4d0..3e12ed34d 100644 --- a/cfg/rh-1.8/controlplane.yaml +++ b/cfg/rh-1.8/controlplane.yaml @@ -14,7 +14,7 @@ groups: # To verify user authentication is enabled oc describe authentication # To verify that an identity provider is configured - oc get identity + 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 @@ -23,7 +23,7 @@ groups: remediation: | Configure an identity provider for the OpenShift cluster. Understanding identity provider configuration | Authentication | OpenShift - Container Platform 4.5. Once an identity provider has been defined, + 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. @@ -35,14 +35,20 @@ groups: - 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/ - #To verify kube apiserver audit config - oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.auditConfig[]?' - #To verify openshift apiserver audit config - oc get configmap config -n openshift-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.auditConfig[]?' type: manual remediation: | No remediation required. @@ -52,11 +58,10 @@ groups: text: "Ensure that the audit policy covers key security concerns (Manual)" audit: | #To verify openshift apiserver audit config - oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.auditConfig.policyConfiguration.rules[]?' + 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 config -n openshift-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.auditConfig.policyConfiguration.rules[]?' + oc get configmap -n openshift-apiserver audit -o json | jq -r '.data."policy.yaml"' type: manual remediation: | - In OpenShift 4.6 and higher, if appropriate for your needs, - modify the audit policy. + Update the audit log policy profile to use WriteRequestBodies. scored: false diff --git a/cfg/rh-1.8/etcd.yaml b/cfg/rh-1.8/etcd.yaml index 8e4aa0db5..fea121539 100644 --- a/cfg/rh-1.8/etcd.yaml +++ b/cfg/rh-1.8/etcd.yaml @@ -6,7 +6,7 @@ text: "Etcd" type: "etcd" groups: - id: 2 - text: "Etcd" + text: "Etcd Node Configuration Files" checks: - id: 2.1 text: "Ensure that the --cert-file and --key-file arguments are set as appropriate (Manual)" @@ -16,11 +16,11 @@ groups: # 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." + 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/' + # 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: @@ -28,7 +28,8 @@ groups: - flag: "file" compare: op: regex - value: '\/etc\/kubernetes\/static-pod-certs\/secrets\/etcd-all-(serving|certs)\/etcd-serving-.*\.(?:crt|key)' + # 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. @@ -42,10 +43,10 @@ groups: # 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." + 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/' + # 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: @@ -67,10 +68,10 @@ groups: # 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." + 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=$? + # 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: @@ -91,11 +92,11 @@ groups: # 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." + 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/' + # 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: @@ -103,7 +104,8 @@ groups: - flag: "file" compare: op: regex - value: '\/etc\/kubernetes\/static-pod-certs\/secrets\/etcd-all-(peer|certs)\/etcd-peer-.*\.(?:crt|key)' + # 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: true @@ -116,10 +118,10 @@ groups: # 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." + 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/' + # 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: @@ -141,10 +143,10 @@ groups: # 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." + 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=$? + # 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: @@ -165,11 +167,11 @@ groups: # 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." + 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/' + # 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: @@ -177,7 +179,7 @@ groups: - flag: "file" compare: op: regex - value: '\/etc\/kubernetes\/static-pod-certs\/configmaps\/(?:etcd-(?:serving|peer-client)-ca\/ca-bundle\.crt|etcd-all-bundles\/server-ca-bundle\.crt)' + value: '\/etc\/kubernetes\/static-pod-certs\/configmaps\/etcd-(?:serving|peer-client)-ca\/ca-bundle\.crt' remediation: | None required. Certificates for etcd are managed by the OpenShift cluster etcd operator. scored: true diff --git a/cfg/rh-1.8/master.yaml b/cfg/rh-1.8/master.yaml index 25a3ac004..fa456f7d9 100644 --- a/cfg/rh-1.8/master.yaml +++ b/cfg/rh-1.8/master.yaml @@ -2,7 +2,7 @@ controls: version: rh-1.8 id: 1 -text: "Control Plane Components" +text: "Master Node Security Configuration" type: "master" groups: - id: 1.1 @@ -31,8 +31,10 @@ groups: op: bitmask value: "600" remediation: | - No remediation required; file permissions are managed by the operator. - scored: true + 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)" @@ -55,7 +57,7 @@ groups: - flag: "root:root" remediation: | No remediation required; file permissions are managed by the operator. - scored: true + scored: false - id: 1.1.3 text: "Ensure that the controller manager pod specification file permissions are set to 600 or more restrictive (Manual)" @@ -80,8 +82,10 @@ groups: op: bitmask value: "600" remediation: | - No remediation required; file permissions are managed by the operator. - scored: true + 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)" @@ -129,11 +133,13 @@ groups: op: bitmask value: "600" remediation: | - No remediation required; file permissions are managed by the operator. + 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: true - id: 1.1.6 - text: "Ensure that the scheduler pod specification file ownership is set to root:root (Manual))" + 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}') @@ -156,7 +162,7 @@ groups: scored: true - id: 1.1.7 - text: "Ensure that the etcd pod specification file permissions are set to 600 or more restrictive (Manual))" + 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}') @@ -178,7 +184,9 @@ groups: op: bitmask value: "600" remediation: | - No remediation required; file permissions are managed by the operator. + 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: true - id: 1.1.8 @@ -212,35 +220,23 @@ groups: # 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 [ -z "$POD_NAME" ]; then - echo "No matching pods found on the current node." - else - # 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 [ -z "$POD_NAME" ]; then - echo "No matching pods found on the current node." + echo "No matching pods found on the current node." else - # 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 + # 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 [ -z "$POD_NAME" ]; then - echo "No matching pods found on the current node." + echo "No matching pods found on the current node." else - # 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 + # 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: @@ -263,37 +259,39 @@ groups: 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 [ -z "$POD_NAME" ]; then - echo "No matching pods found on the current node." + echo "No matching pods found on the current node." else - # 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 + # Execute the stat command + oc exec -n openshift-multus "$POD_NAME" -- /bin/bash -c "stat -c \"$POD_NAME %n %U:%G\" /host/etc/cni/net.d/*.conf" 2>/dev/null + oc exec -n openshift-multus "$POD_NAME" -- /bin/bash -c "stat -c \"$POD_NAME %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 [ -z "$POD_NAME" ]; then - echo "No matching pods found on the current node." + echo "No matching pods found on the current node." else - # 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 + # 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 [ -z "$POD_NAME" ]; then - echo "No matching pods found on the current node." + echo "No matching pods found on the current node." else - # 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 + # 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: true @@ -321,7 +319,7 @@ groups: op: bitmask value: "700" remediation: | - No remediation required; file permissions are managed by the operator. + No remediation required; file permissions are managed by the etcd operator. scored: true - id: 1.1.12 @@ -360,7 +358,9 @@ groups: op: bitmask value: "600" remediation: | - No remediation required; file permissions are managed by the operator. + 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: true - id: 1.1.14 @@ -377,7 +377,7 @@ groups: scored: true - id: 1.1.15 - text: "Ensure that the scheduler kubeconfig file permissions are set to 600 or more restrictive (Manual)" + 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}') @@ -386,10 +386,10 @@ groups: 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." + 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 + # 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: @@ -399,7 +399,9 @@ groups: op: bitmask value: "600" remediation: | - No remediation required; file permissions are managed by the operator. + 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: true - id: 1.1.16 @@ -412,10 +414,10 @@ groups: 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." + 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 + # 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: @@ -435,10 +437,10 @@ groups: 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." + 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 + # 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: @@ -461,10 +463,10 @@ groups: 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." + 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 + # 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: @@ -485,14 +487,14 @@ groups: 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." + 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" {} \; + # 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: @@ -560,193 +562,107 @@ groups: - id: 1.2.1 text: "Ensure that anonymous requests are authorized (Manual)" audit: | - found=0 - - echo "# ClusterRoleBindings granting permissions to system:unauthenticated" - crb_out="$(oc get clusterrolebindings -o json 2>/dev/null \ - | jq -r '.items[] - | select(.subjects[]? | select(.kind=="Group" and .name=="system:unauthenticated")) - | .metadata.name + " -> " + .roleRef.kind + "/" + .roleRef.name' \ - | sort -u)" - if [ -n "$crb_out" ]; then - echo "$crb_out" - found=1 - else - echo "(none)" - fi - - echo - echo "# Namespaced RoleBindings granting permissions to system:unauthenticated" - rb_out="$(oc get rolebindings -A -o json 2>/dev/null \ - | jq -r '.items[] - | select(.subjects[]? | select(.kind=="Group" and .name=="system:unauthenticated")) - | (.metadata.namespace + "/" + .metadata.name) + " -> " + .roleRef.kind + "/" + .roleRef.name' \ - | sort -u)" - if [ -n "$rb_out" ]; then - echo "$rb_out" - found=1 - else - echo "(none)" - fi - - # Provide a simple flag for the test harness - if [ $found -eq 1 ]; then - echo "unauthenticated_bindings_present" - else - echo "unauthenticated_bindings_missing" - fi + # 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: "unauthenticated_bindings_present" - set: true + - 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: true + scored: false - id: 1.2.2 text: "Use HTTPS for kubelet connections (Manual)" audit: | - CFG=$(oc -n openshift-kube-apiserver get cm config -o jsonpath='{.data.config\.yaml}') - - # Extract kubelet client cert/key paths (support both layouts) - CERT_FILE=$(printf '%s\n' "$CFG" \ - | grep -Eo '/etc/kubernetes/static-pod-(resources/kube-apiserver-certs|certs)/secrets/kubelet-client/tls\.crt' \ - | head -n1) - - KEY_FILE=$(printf '%s\n' "$CFG" \ - | grep -Eo '/etc/kubernetes/static-pod-(resources/kube-apiserver-certs|certs)/secrets/kubelet-client/tls\.key' \ - | head -n1) - - # 1) pass/fail on presence of both files - if [ -n "$CERT_FILE" ] && [ -n "$KEY_FILE" ]; then - echo "pass" - else - echo "fail" - fi - KUBELET_HTTPS=$(printf '%s\n' "$CFG" \ - | grep -Eo '(^|[[:space:]])kubelet-https:[[:space:]]*(true|false)' \ - | awk -F: '{print $2}' \ - | tr -d '[:space:]' \ - | head -n1) - - if [ "$KUBELET_HTTPS" = "false" ]; then - echo "false" - else - echo "true" - fi - - oc -n openshift-apiserver describe secret serving-cert | grep -E 'tls\.crt|tls\.key|Type:' + 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: "pass" - - flag: "true" - - flag: "kubernetes.io/tls" - - flag: "tls.crt" - - flag: "tls.key" + - 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: | - OpenShift does not use the legacy --kubelet-https flag; TLS is enforced via - kubelet client cert/key arguments and cluster CAs. Ensure: - - apiServerArguments.kubelet-client-certificate[0] points to a real file - - apiServerArguments.kubelet-client-key[0] points to a real file - - The openshift-apiserver 'serving-cert' secret is type kubernetes.io/tls and contains tls.crt and tls.key + 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.3 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["kubelet-client-certificate"]' - oc get configmap config -n openshift-kube-apiserver -ojson | jq -r'.data["config.yaml"]' | jq '.apiServerArguments["kubelet-client-key"]' + 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" - - flag: "kubernetes.io/tls" remediation: | No remediation is required. - OpenShift automatically manages kubelet authentication using X.509 certificates issued by the internal platform CA. - Manual modification of these certificates is not supported and can disrupt platform components. - scored: true + 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.4 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["kubelet-certificate-authority"]' + 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 uses internal X.509 certificates and platform-managed CAs to verify kubelet server identities. - This is not user-configurable and should not be modified. - scored: true + 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 --authorization-mode argument is not set to AlwaysAllow (Manual)" audit: | - oc get configmap config -n openshift-kube-apiserver -o json \ - | jq -r '.data["config.yaml"]' \ - | jq '.apiServerArguments["authorization-mode"]' - audit_config: | - oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.apiServerArguments' + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments."authorization-mode"[]' tests: - bin_op: or test_items: - - path: "{.authorization-mode}" - compare: - op: nothave - value: "AlwaysAllow" - - path: "{.authorization-mode}" - flag: "authorization-mode" + - flag: "AlwaysAllow" set: false remediation: | - No remediation required. - OpenShift does not support the 'AlwaysAllow' authorization mode. - The API server is bootstrapped with secure authorization mechanisms including RBAC and Node by default. - scored: true + 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.6 text: "Verify that RBAC is enabled (Manual)" audit: | - oc get configmap config -n openshift-kube-apiserver -o json \ - | jq -r '.data["config.yaml"]' \ - | jq '.apiServerArguments["authorization-mode"]' - audit_config: | - oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq '.apiServerArguments' + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r '.apiServerArguments."authorization-mode"[]' tests: - bin_op: or test_items: - - path: "{.authorization-mode}" - compare: - op: has - value: "RBAC" - - path: "{.authorization-mode}" - flag: "authorization-mode" - set: false + - flag: "RBAC" remediation: | - No remediation is required. - OpenShift is configured at bootstrap time to use Role-Based Access Control (RBAC) as the default authorization mode. - RBAC is always enabled, and cannot be disabled through configuration. - scored: true - + None. It is not possible to disable RBAC. + scored: false - id: 1.2.7 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 - scored: true + No remediation is required. By default, the OpenShift kubelet has been fixed to send fewer requests. + scored: false - id: 1.2.8 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: @@ -754,11 +670,12 @@ groups: set: false remediation: | No remediation is required. The AlwaysAdmit admission controller cannot be enabled in OpenShift. - scored: true + scored: false - id: 1.2.9 text: "Ensure that the admission control plugin AlwaysPullImages is 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: @@ -766,7 +683,7 @@ groups: set: false remediation: | None required. - scored: true + scored: false - id: 1.2.10 text: "Ensure that the admission control plugin ServiceAccount is set (Manual)" @@ -777,111 +694,119 @@ groups: - flag: "ServiceAccount" set: true remediation: | - None required. OpenShift is configured to use service accounts by default. - scored: true + None required. By default, OpenShift configures the ServiceAccount admission controller. + scored: false - id: 1.2.11 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"' - output=$(oc -n openshift-kube-apiserver get configmap config -o json | jq -r '.data."config.yaml"' | jq '.apiServerArguments."enable-admission-plugins"') - [ "$output" == "null" ] && echo "ocp 4.5 has NamespaceLifecycle compiled" || echo $output tests: test_items: - flag: "NamespaceLifecycle" remediation: | - Ensure that the --disable-admission-plugins parameter does not include NamespaceLifecycle. - scored: true + None required. OpenShift configures NamespaceLifecycle admission controller by default. + scored: false - id: 1.2.12 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"' - output=$(oc -n openshift-kube-apiserver get configmap config -o json | jq -r '.data."config.yaml"' | jq '.apiServerArguments."enable-admission-plugins"') - [ "$output" == "null" ] && echo "ocp 4.5 has SecurityContextConstraint compiled" || echo $output tests: test_items: - flag: "security.openshift.io/SecurityContextConstraint" remediation: | - None required. Security Context Constraints are enabled by default in OpenShift and cannot be disabled. - scored: true + None required. By default, the SecurityContextConstraints admission controller is configured and cannot be disabled. + scored: false - id: 1.2.13 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"' - output=$(oc -n openshift-kube-apiserver get configmap config -o json | jq -r '.data."config.yaml"' | jq '.apiServerArguments."enable-admission-plugins"') - [ "$output" == "null" ] && echo "ocp 4.5 has NodeRestriction compiled" || echo $output tests: test_items: - flag: "NodeRestriction" remediation: | - The NodeRestriction plugin cannot be disabled. - scored: true + None required. In OpenShift, the NodeRestriction admission plugin is enabled by default and cannot be disabled. + scored: false - id: 1.2.14 - text: "Ensure that the --insecure-bind-address argument is not set (manual)" + text: "Ensure that the --insecure-bind-address argument is not set (Manual)" audit: | - # Get the insecure-bind-address value - insecure_bind_address=$(oc get kubeapiservers.operator.openshift.io cluster -ojson \ - | jq -r '.spec.observedConfig.apiServerArguments["insecure-bind-address"][]?') - - # Get port from openshift-kube-apiserver - kube_api_port=$(oc -n openshift-kube-apiserver get endpoints -o jsonpath='{.items[*].subsets[*].ports[*].port}') - - # Get port from openshift-apiserver - openshift_api_port=$(oc -n openshift-apiserver get endpoints -o jsonpath='{.items[*].subsets[*].ports[*].port}') - - # Evaluate logic - [[ -z "$insecure_bind_address" ]] && \ - [[ "$kube_api_port" == *"6443"* ]] && \ - [[ "$openshift_api_port" == *"8443"* ]] && echo "pass" || echo "fail" + # 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: "pass" + - flag: "InsecureBindAddress=true" + set: false + - flag: 6443 + - flag: 8443 remediation: | - No remediation is required. - By default, OpenShift uses secure HTTPS ports (6443 and 8443) for all API communications. - The API servers are not configured to expose insecure ports and are isolated within the pod network. - scored: true - + None required. By default, the openshift-kube-apiserver is served over HTTPS with authentication and authorization. + scored: false - id: 1.2.15 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. The configuration is managed by the API server operator. - scored: true + None required. By default, the openshift-kube-server is served over HTTPS with authentication and authorization. + scored: false - id: 1.2.16 text: "Ensure that the --secure-port argument is not set to 0 (Manual)" audit: | - BIND_ADDR=$(oc get kubeapiservers.operator.openshift.io cluster -o json \ - | jq -r '.spec.observedConfig.servingInfo.bindAddress') - - PORTS=$(oc get pods -n openshift-kube-apiserver -l app=openshift-kube-apiserver \ - -o jsonpath='{.items[*].spec.containers[?(@.name=="kube-apiserver")].ports[*].containerPort}') - - if [ "$BIND_ADDR" = "0.0.0.0:6443" ] && echo "$PORTS" | grep -q '\b6443\b'; then - echo "pass" - else - echo "fail" - fi + 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: "pass" + - flag: 'bindAddress' + compare: + op: eq + value: '"0.0.0.0:6443"' + - flag: "ports" + compare: + op: eq + value: '6443' remediation: | - None required. OpenShift serves the API securely over port 6443 with TLS, authentication, and authorization. - The insecure API port is not exposed or configurable by default. - scored: true + 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.17 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 @@ -889,48 +814,39 @@ groups: - id: 1.2.18 text: "Ensure that the --audit-log-path argument is set (Manual)" audit: | - # Get kube-apiserver audit log path - kube_path=$(oc get configmap config -n openshift-kube-apiserver -ojson \ - | jq -r '.data["config.yaml"]' \ - | jq -r '.apiServerArguments["audit-log-path"][]?') - - # Get OpenShift apiserver audit log path - os_path=$(oc get configmap config -n openshift-apiserver -ojson \ - | jq -r '.data["config.yaml"]' \ - | jq -r '.apiServerArguments["audit-log-path"][]?') - - # Check if log file exists in kube-apiserver pod - kube_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 $kube_pod ls "$kube_path" >/dev/null 2>&1 - kube_exists=$? - - # Check if log file exists in openshift-apiserver pod - os_pod=$(oc get pods -n openshift-apiserver -l apiserver=true -o jsonpath='{.items[0].metadata.name}') - oc rsh -n openshift-apiserver $os_pod ls "$os_path" >/dev/null 2>&1 - os_exists=$? - - # Evaluate all conditions - [[ "$kube_path" == "/var/log/kube-apiserver/audit.log" ]] && \ - [[ "$os_path" == "/var/log/openshift-apiserver/audit.log" ]] && \ - [[ $kube_exists -eq 0 ]] && \ - [[ $os_exists -eq 0 ]] && echo "pass" || echo "fail" + # 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: "pass" + - 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: | - No remediation is required. - OpenShift manages audit logging automatically via the apiserver configuration. - By default, the audit log paths are: - - /var/log/kube-apiserver/audit.log - - /var/log/openshift-apiserver/audit.log - scored: true + None required. This is managed by the cluster apiserver operator. By default, auditing is enabled. + scored: false - id: 1.2.19 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.5/logging/cluster-logging-external.html + https://docs.openshift.com/container-platform/4.15/observability/logging/log_collection_forwarding/configuring-log-forwarding.html scored: false - id: 1.2.20 @@ -946,8 +862,8 @@ groups: echo "fail (current=$VALUE)" fi tests: - test_items: - - flag: "pass" + test_items: + - flag: "pass" remediation: | No remediation required. By default, OpenShift retains 10 audit log backup files. @@ -957,53 +873,43 @@ groups: - id: 1.2.21 text: "Configure Kubernetes API Server Maximum Audit Log Size (Manual)" audit: | - VALUE=$(oc get configmap config -n openshift-kube-apiserver -ojson \ - | jq -r '.data["config.yaml"]' \ - | jq -r '.apiServerArguments["audit-log-maxsize"][0] // empty') - - if [ -n "$VALUE" ] && [ "$VALUE" -ge 100 ]; then - echo "pass (current=$VALUE)" - else - echo "fail (current=$VALUE)" - fi + 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: "pass" + - flag: "audit-log-maxsize" + compare: + op: gte + value: 100 remediation: | - Set the audit-log-maxsize parameter to 100 or as an appropriate number. - maximumFileSizeMegabytes: 100 - scored: true + None. The audit-log-maxsize parameter is by default set to 100 and not supported to change. + scored: false - id: 1.2.22 text: "Ensure that the --request-timeout argument is set (Manual)" audit: | - VALUE=$(oc get configmap config -n openshift-kube-apiserver -ojson \ - | jq -r '.data["config.yaml"]' \ - | jq -r '.apiServerArguments["min-request-timeout"][0] // empty') - - if [ -n "$VALUE" ] && [ "$VALUE" -eq 3600 ]; then - echo "pass (current=$VALUE)" - else - echo "fail (current=$VALUE)" - fi + 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: "pass" + - flag: "requestTimeoutSeconds" + compare: + op: gte + value: 100 remediation: | - TBD - scored: true + None required. By default, min-request-timeout is set to 3600 seconds in OpenShift. + scored: false - id: 1.2.23 text: "Ensure that the --service-account-lookup argument is set to true (Manual)" audit: | - oc get configmap config -n openshift-kube-apiserver -ojson | jq -r - '.data["config.yaml"]' | jq '.apiServerArguments."service-account-lookup"[]' + 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: "true" + - flag: "service-account-lookup=true" remediation: | - TBD - scored: true + None required. Service account lookup is enabled by default. + scored: false - id: 1.2.24 text: "Ensure that the --service-account-key-file argument is set as appropriate (Manual)" @@ -1018,112 +924,103 @@ groups: 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: true + scored: false - id: 1.2.25 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"][]?' + 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: | - No remediation is required. - OpenShift automatically manages X.509 client certificates and TLS encryption for secure communication with etcd. - These settings are handled by the platform and should not be manually modified. - scored: true + OpenShift automatically manages TLS and client certificate authentication for etcd. + This is not configurable. + scored: false - id: 1.2.26 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"][]?' - + 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: | - No remediation is required. OpenShift automatically configures the API server with valid X.509 certificates and TLS keys. - These are used to encrypt traffic between the API server and clients, including kubelets and users. - Certificate rotation and lifecycle management are handled by the OpenShift platform. - scored: true - + 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.27 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 .servingInfo.clientCA + oc get configmap config -n openshift-kube-apiserver -ojson | jq -r '.data["config.yaml"]' | jq -r .servingInfo.clientCA tests: test_items: - flag: "/etc/kubernetes/static-pod-certs/configmaps/client-ca/ca-bundle.crt" remediation: | - OpenShift automatically manages TLS authentication for the API server communication with the node/kublet. - This is not configurable. 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. - - User-provided certificates must be provided in a kubernetes.io/tls type Secret in the openshift-config namespace. - Update the API server cluster configuration, - the apiserver/cluster resource, to enable the use of the user-provided certificate. - scored: true + 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.28 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"]' + 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. 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: true + 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.29 text: "Ensure that encryption providers are appropriately configured (Manual)" audit: | - oc get openshiftapiserver -o=jsonpath='{range .items[0].status.conditions[?(@.type=="Encrypted")]}{.reason}{"\n"}{.message}{"\n"}' + # 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 Kubernetes documentation and configure a EncryptionConfig file. - In this file, choose aescbc, kms or secretbox as the encryption provider. - scored: true + 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.30 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 describe --namespace=openshift-ingress-operator ingresscontroller/default + 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: | - Verify that the tlsSecurityProfile is set to the value you chose. - 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. + 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.31 @@ -1134,10 +1031,9 @@ groups: test_items: - flag: "null" remediation: | - No remediation is required. - OpenShift has deprecated and disabled unsupportedConfigOverrides. - This field should remain null and must not be used in any supported configuration. - scored: true + 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" @@ -1148,11 +1044,11 @@ groups: 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' + 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 - oc project openshift-kube-controller-manage 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 @@ -1160,15 +1056,17 @@ groups: # Create a service account to test RBAC oc create -n openshift-kube-controller-manager sa permission-test-sa # Should return 403 Forbidden - SA_TOKEN=$(oc sa -n openshift-kube-controller-manager get-token permission-test-sa) + 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 - # Cleanup - oc delete -n openshift-kube-controller-manager sa permission-test-sa # 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; profiling is protected by RBAC. + 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 @@ -1203,16 +1101,14 @@ groups: - 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"][]' + 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: true + scored: false - id: 1.4 @@ -1223,63 +1119,57 @@ groups: 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' + 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 - POD=$(oc get pods -l app=openshift-kube-scheduler -o jsonpath='{.items[0].metadata.name}') - PORT=$(oc get pod $POD -o jsonpath='{.spec.containers[0].livenessProbe.httpGet.port}') + # 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 ${POD} curl http://localhost:${PORT}/metrics -k + oc rsh -n openshift-kube-scheduler ${POD} curl https://localhost:${PORT}/metrics -k # Create a service account to test RBAC - oc create sa permission-test-sa + oc create sa -n openshift-kube-scheduler permission-test-sa # Should return 403 Forbidden - SA_TOKEN=$(oc sa get-token permission-test-sa) - oc rsh ${POD} curl http://localhost:${PORT}/metrics -H "Authorization: Bearer $SA_TOKEN" -k - # Cleanup - oc delete sa permission-test-sa + 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 - CLUSTER_ADMIN_TOKEN=$(oc whoami -t) - oc rsh ${POD} curl http://localhost:${PORT}/metrics -H "Authorization: Bearer $CLUSTER_ADMIN_TOKEN" -k + 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: | - A fix to this issue: https://bugzilla.redhat.com/show_bug.cgi?id=1889488 None required. - Profiling is protected by RBAC and cannot be disabled. + 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: | - echo "Describing kube-scheduler endpoints..." + # To verify endpoints oc -n openshift-kube-scheduler describe endpoints - - echo "Checking pod configuration for kube-scheduler to confirm no --bind-address or insecure arguments..." - 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' - - echo "Testing access to metrics endpoint as unauthenticated user..." - oc project openshift-kube-scheduler - export POD=$(oc get pods -l app=openshift-kube-scheduler -o jsonpath='{.items[0].metadata.name}') - export POD_IP=$(oc get pods -l app=openshift-kube-scheduler -o jsonpath='{.items[0].status.podIP}') - export PORT=$(oc get pod $POD -o jsonpath='{.spec.containers[0].livenessProbe.httpGet.port}') - oc rsh $POD curl -k -s -o /dev/null -w "%{http_code}" https://$POD_IP:$PORT/metrics - - echo "Testing access with unprivileged service account..." - oc create sa permission-test-sa - export SA_TOKEN=$(oc create token permission-test-sa) - oc rsh $POD curl -k -s -o /dev/null -w "%{http_code}" https://$POD_IP:$PORT/metrics -H "Authorization: Bearer $SA_TOKEN" - - echo "Testing access with cluster-admin..." + # 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 -k -s -o /dev/null -w "%{http_code}" https://$POD_IP:$PORT/metrics -H "Authorization: Bearer $CLUSTER_ADMIN_TOKEN" - + oc rsh ${POD} curl http://localhost:${PORT}/metrics -H "Authorization: Bearer $CLUSTER_ADMIN_TOKEN" -k # Cleanup - unset CLUSTER_ADMIN_TOKEN POD PORT SA_TOKEN POD_IP - oc delete sa permission-test-sa + 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 present, - the readinessProbe and livenessProbe arguments are set to 10251 and the port argument is set to 0. - Check the status of this issue: https://bugzilla.redhat.com/show_bug.cgi?id=1889488 + 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.8/node.yaml b/cfg/rh-1.8/node.yaml index 6327efbec..4642901c8 100644 --- a/cfg/rh-1.8/node.yaml +++ b/cfg/rh-1.8/node.yaml @@ -48,9 +48,10 @@ groups: 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 + 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 @@ -59,7 +60,7 @@ groups: value: "644" remediation: | None needed. - scored: true + scored: false - id: 4.1.4 text: "If proxy kubeconfig file exists ensure ownership is set to root:root (Manual)" @@ -77,14 +78,15 @@ groups: 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: true + scored: false - id: 4.1.5 - text: "Ensure that the --kubeconfig kubelet.conf file permissions are set to 644 or more restrictive (Manual)" + 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}') @@ -101,7 +103,7 @@ groups: scored: true - id: 4.1.6 - text: "Ensure that the --kubeconfig kubelet.conf file ownership is set to root:root (Manual)" + 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 @@ -114,21 +116,19 @@ groups: scored: true - id: 4.1.7 - text: "Ensure that the certificate authorities file permissions are set to 644 or more restrictive" + 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 get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.authentication.x509.clientCAFile' - oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME permissions=%a" /etc/kubernetes/kubelet-ca.crt 2> /dev/null + 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: "/etc/kubernetes/kubelet-ca.crt" - flag: "permissions" compare: op: bitmask value: "644" remediation: | - No remediation required. OpenShift sets /etc/kubernetes/kubelet-ca.crt to 644 by default. - If permissions are more permissive than 644, update with: chmod 644 /etc/kubernetes/kubelet-ca.crt + None required. scored: true @@ -149,7 +149,13 @@ groups: 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}') - oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME %n permissions=%a" /var/data/kubelet/config.json 2> /dev/null + # 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: @@ -165,7 +171,13 @@ groups: 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}') - oc debug node/$NODE_NAME -- chroot /host stat -c "$NODE_NAME %n %U:%G" /var/data/kubelet/config.json 2> /dev/null + # 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: @@ -243,31 +255,30 @@ groups: echo "Checking kubelet authorization mode on the current node..." NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') - oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.authorization.mode' + oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.authorization' 2> /dev/null + use_multiple_values: true tests: test_items: - - flag: AlwaysAllow - set: false + - flag: mode + compare: + op: noteq + value: AlwaysAllow remediation: | - No remediation required. By default, OpenShift uses secure authorization modes such as 'Webhook' and does not allow AlwaysAllow. - If AlwaysAllow is found, the node must be reconfigured using a KubeletConfig applied through the appropriate MachineConfigPool. + 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: | - echo "Checking Kubelet 'clientCAFile' setting on current node..." - NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') - oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz \ - | jq '.kubeletconfig.authentication.x509.clientCAFile' + 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" + - flag: '/etc/kubernetes/kubelet-ca.crt' remediation: | - No remediation required. OpenShift sets the clientCAFile by default to /etc/kubernetes/kubelet-ca.crt. - Manual modification is unsupported and unnecessary as OpenShift manages Kubelet certificate authentication via the Machine Config Operator. + None required. Changing the clientCAFile value is unsupported. scored: true @@ -275,13 +286,10 @@ groups: text: "Verify that the read only port is not used or is set to 0 (Automated)" audit: | echo "Checking 'kubelet-read-only-port' argument in openshift-kube-apiserver config..." - - oc -n openshift-kube-apiserver get configmap config -o json \ - | jq -r '.data["config.yaml"]' \ - | yq '.apiServerArguments."kubelet-read-only-port"[0]' + 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" + - flag: '0' remediation: | No remediation is required if the read-only port is set to 0. If this value is not set to 0 (or the argument is missing), create a KubeletConfig object and apply it to the appropriate MachineConfigPool to disable the read-only port. @@ -299,15 +307,13 @@ groups: - id: 4.2.6 - text: "Ensure that the --streaming-connection-idle-timeout argument is not set to 0 (automated)" + text: "Ensure that the --streaming-connection-idle-timeout argument is not set to 0 (Automated)" audit: | NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') - oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz \ - | jq '.kubeletconfig' + oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.streamingConnectionIdleTimeout' 2> /dev/null tests: test_items: - - path: ".streamingConnectionIdleTimeout" - compare: + - compare: op: noteq value: "0s" remediation: | @@ -326,25 +332,24 @@ groups: scored: true - id: 4.2.7 - text: "Ensure that the --make-iptables-util-chains argument is set to true (manual)" + text: "Ensure that the --make-iptables-util-chains argument is set to true (Manual)" audit: | - echo "Checking 'makeIPTablesUtilChains' setting in Kubelet config on current node..." - - NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') - oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz \ - | jq '.kubeletconfig' + # CIS Kubernetes Benchmark 1.4.0 (2.1.7): verify makeIPTablesUtilChains is true for each node. + fail=0 + for node in $(oc get nodes -ojsonpath='{.items[*].metadata.name}'); do + value=$(oc get --raw /api/v1/nodes/$node/proxy/configz | jq -r '.kubeletconfig.makeIPTablesUtilChains' 2>/dev/null) + echo "$node makeIPTablesUtilChains=$value" + [ "$value" = "true" ] || fail=1 + done + [ "$fail" -eq 0 ] && echo "pass" || echo "fail" tests: test_items: - - path: ".makeIPTablesUtilChains" - compare: - op: eq - value: true + - flag: "pass" remediation: | No remediation is required. By default, OpenShift sets makeIPTablesUtilChains to true. This allows Kubelet to manage iptables rules and keep them in sync with the dynamic pod network configuration. - scored: true - + 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)" @@ -378,15 +383,11 @@ groups: - id: 4.2.9 - text: "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate (manual)" + 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"][]?' - - oc get configmap config -n openshift-kube-apiserver -ojson \ - | jq -r '.data["config.yaml"]' \ - | jq -r '.apiServerArguments["kubelet-client-key"][]?' + 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: @@ -400,14 +401,15 @@ groups: - id: 4.2.10 - text: "Ensure that the --rotate-certificates argument is not set to false (manual)" + text: "Ensure that the --rotate-certificates argument is not set to false (Manual)" audit: | + echo "Checking 'rotateCertificates' setting in Kubelet config on current node..." NODE_NAME=$(oc get pod $HOSTNAME -o=jsonpath='{.spec.nodeName}') - oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz \ - | jq '.kubeletconfig' + oc get --raw /api/v1/nodes/$NODE_NAME/proxy/configz | jq '.kubeletconfig.rotateCertificates' + use_multiple_values: true tests: test_items: - - path: ".rotateCertificates" + - flag: ".rotateCertificates" compare: op: eq value: true @@ -467,19 +469,21 @@ groups: RotateKubeletServerCertificate: true scored: true - - id: 4.2.13 + - id: 4.2.12 text: "Ensure that the Kubelet only makes use of Strong Cryptographic Ciphers (Manual)" audit: | # needs verification # verify cipher suites - oc describe --namespace=openshift-ingress-operator ingresscontroller/default - 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 cm -n openshift-authentication v4-0-config-system-cliconfig -o jsonpath='{.data.v4\-0\-config\-system\-cliconfig}' | jq .servingInfo + 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 + 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 + 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.8/policies.yaml b/cfg/rh-1.8/policies.yaml index 506a9c00a..4dc4df25f 100644 --- a/cfg/rh-1.8/policies.yaml +++ b/cfg/rh-1.8/policies.yaml @@ -13,14 +13,15 @@ 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 | - grep cluster-admin - #To verity that kbueadmin is removed, no results should be returned + oc get clusterrolebindings -o=custom-columns="NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].kind" | grep cluster-admin + #To verity that kubeadmin 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] + 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 @@ -33,6 +34,11 @@ groups: - id: 5.1.3 text: "Minimize wildcard use in Roles and ClusterRoles (Manual)" type: "manual" + audit: | + # 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. @@ -41,6 +47,9 @@ groups: - id: 5.1.4 text: "Minimize access to create pods (Manual)" type: "manual" + audit: | + # 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 @@ -55,6 +64,11 @@ groups: - id: 5.1.6 text: "Ensure that Service Account Tokens are only mounted where necessary (Manual)" type: "manual" + audit: | + # 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. @@ -160,7 +174,7 @@ groups: scored: true - id: 5.2.4 - text: "Minimize the admission of containers wishing to share the host network namespace (manual)" + text: "Minimize the admission of containers wishing to share the host network namespace (Manual)" audit: | oc get scc -o json \ | jq -r '[.items[] | select(.allowHostNetwork==false) | .metadata.name] @@ -191,7 +205,7 @@ groups: scored: true - id: 5.2.5 - text: "Minimize the admission of containers with allowPrivilegeEscalation (manual)" + text: "Minimize the admission of containers with allowPrivilegeEscalation (Manual)" audit: | oc get scc -o json \ | jq -r '[.items[] | select(.allowPrivilegeEscalation==false) | .metadata.name] @@ -224,10 +238,12 @@ groups: - id: 5.2.6 - text: "Minimize the admission of root containers (manual)" + text: "Minimize the admission of root containers (Manual)" audit: | - sccs=$(oc get scc -o json | jq -r '.items[] | select(.runAsUser.type == "MustRunAsNonRoot") | .metadata.name') - if [[ -n "$sccs" ]]; then + # Check if at least one SCC enforces MustRunAsNonRoot + result=$(oc get scc -A -o json | jq -r '.items[] | select(.runAsUser["type"] == "MustRunAsNonRoot") | .metadata.name') + + if [[ -n "$result" ]]; then echo "pass" else echo "fail" @@ -235,30 +251,20 @@ groups: tests: test_items: - flag: "pass" + compare: + op: eq + value: "pass" remediation: | - If no SCC is found with `runAsUser.type: MustRunAsNonRoot`, create one as follows: - - --- - apiVersion: security.openshift.io/v1 - kind: SecurityContextConstraints - metadata: - name: restricted-nonroot - allowPrivilegeEscalation: false - runAsUser: - type: MustRunAsNonRoot - seLinuxContext: - type: MustRunAs - users: [] - groups: - - system:authenticated - --- + Ensure that at least one SCC enforces: + runAsUser: + type: MustRunAsNonRoot - Assign this SCC only to workloads that must not run as root. - If an SCC allows `RunAsAny`, audit and restrict access using RBAC to prevent misuse. + If root containers are required, define a separate SCC and restrict access + using RBAC controls to only approved service accounts and users. scored: true - id: 5.2.7 - text: "Minimize the admission of containers with the NET_RAW capability (manual)" + text: "Minimize the admission of containers with the NET_RAW capability (Manual)" audit: | oc get scc -o json \ | jq -r '[.items[] @@ -295,7 +301,7 @@ groups: - id: 5.2.8 - text: "Minimize the admission of containers with added capabilities (manual)" + text: "Minimize the admission of containers with added capabilities (Manual)" audit: | oc get scc -o json \ | jq -r '[.items[] @@ -338,7 +344,7 @@ groups: scored: true - id: 5.2.9 - text: "Minimize the admission of containers with capabilities assigned (manual)" + text: "Minimize the admission of containers with capabilities assigned (Manual)" audit: | oc get scc -o json \ | jq -r '[.items[] @@ -376,11 +382,15 @@ groups: - id: 5.2.10 text: "Minimize access to privileged Security Context Constraints (Manual)" type: "manual" + audit: | + # 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}' 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. + 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. @@ -394,7 +404,7 @@ groups: text: "Ensure that the CNI in use supports Network Policies (Manual)" type: "manual" remediation: | - None required. + None required. This will depend on the CNI plugin in use. scored: false - id: 5.3.2 @@ -415,8 +425,7 @@ groups: 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 + 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. @@ -436,8 +445,10 @@ groups: - id: 5.5.1 text: "Configure Image Provenance using image controller configuration parameters (Manual)" type: "manual" + audit: | + oc get image.config.openshift.io/cluster -o json | jq .spec.registrySources remediation: | - Follow the OpenShift documentation: [Image configuration resources](https://docs.openshift.com/container-platform/4.5/openshift_images/image-configuration.html + 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 @@ -450,22 +461,33 @@ groups: #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. + 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: | - To enable the default seccomp profile, use the reserved value /runtime/default that will - make sure that the pod uses the default policy available on the host. + 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: | + # 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 @@ -476,9 +498,11 @@ groups: text: "The default namespace should not be used (Manual)" type: "manual" audit: | - #Run this command to list objects in default namespace - oc project default - oc get all + # 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