diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index acdcde358..343db3601 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v8 with: - version: v2.5.0 + version: v2.11.4 args: --verbose --timeout 2m unit: name: Unit tests diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4e13515f2..8f402b27e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -47,10 +47,11 @@ jobs: images: ${{ env.REP }} tag-semver: | {{version}} - - name: Extract variables from makefile (kubectl) + - name: Extract variables from makefile id: extract_vars run: | echo "KUBECTL_VERSION=$(grep -oP '^KUBECTL_VERSION\s*\?=\s*\K.*' makefile)" >> $GITHUB_ENV + echo "K8S_PKGS_VERSION=$(grep -oP '^K8S_PKGS_VERSION\s*\?=\s*\K.*' makefile)" >> $GITHUB_ENV - name: Build and push - Docker/ECR id: docker_build uses: docker/build-push-action@v7 @@ -81,7 +82,7 @@ jobs: file: Dockerfile.ubi build-args: | KUBEBENCH_VERSION=${{ steps.get_version.outputs.version }} - KUBECTL_VERSION=${{ env.KUBECTL_VERSION }} + K8S_PKGS_VERSION=${{ env.K8S_PKGS_VERSION }} tags: | ${{ env.DOCKERHUB_ALIAS }}/${{ env.REP }}:${{ steps.get_version.outputs.version }}-ubi public.ecr.aws/${{ env.ALIAS }}/${{ env.REP }}:${{ steps.get_version.outputs.version }}-ubi @@ -101,7 +102,7 @@ jobs: file: Dockerfile.fips.ubi build-args: | KUBEBENCH_VERSION=${{ steps.get_version.outputs.version }} - KUBECTL_VERSION=${{ env.KUBECTL_VERSION }} + K8S_PKGS_VERSION=${{ env.K8S_PKGS_VERSION }} tags: | ${{ env.DOCKERHUB_ALIAS }}/${{ env.REP }}:${{ steps.get_version.outputs.version }}-ubi-fips public.ecr.aws/${{ env.ALIAS }}/${{ env.REP }}:${{ steps.get_version.outputs.version }}-ubi-fips diff --git a/Dockerfile b/Dockerfile index 061573adb..a62b5fd2a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.26.1 AS build +FROM golang:1.26.2 AS build WORKDIR /go/src/github.com/aquasecurity/kube-bench/ COPY makefile makefile COPY go.mod go.sum ./ @@ -9,16 +9,6 @@ COPY internal/ internal/ ARG KUBEBENCH_VERSION RUN make build && cp kube-bench /go/bin/kube-bench -# Add kubectl to run policies checks -ARG KUBECTL_VERSION TARGETARCH -RUN wget -O /usr/local/bin/kubectl "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" -RUN wget -O kubectl.sha256 "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl.sha256" - -# Verify kubectl sha256sum -RUN /bin/bash -c 'echo "$( /etc/yum.repos.d/kubernetes.repo \ + && yum install -y kubectl \ && yum clean all \ && microdnf remove yum || rpm -e -v yum \ - && microdnf clean all + && microdnf clean all \ + && /usr/bin/kubectl version --client WORKDIR /opt/kube-bench/ -ENV PATH=$PATH:/usr/local/mount-from-host/bin +ENV PATH=$PATH:/usr/local/mount-from-host/bin COPY LICENSE /licenses/LICENSE COPY --from=build /go/bin/kube-bench /usr/local/bin/kube-bench -COPY --from=build /usr/local/bin/kubectl /usr/local/bin/kubectl COPY entrypoint.sh . COPY cfg/ cfg/ COPY helper_scripts/check_files_owner_in_dir.sh /go/bin diff --git a/Dockerfile.ubi b/Dockerfile.ubi index f3c4439fb..c06d5ed2d 100644 --- a/Dockerfile.ubi +++ b/Dockerfile.ubi @@ -1,4 +1,4 @@ -FROM golang:1.26.1 AS build +FROM golang:1.26.2 AS build WORKDIR /go/src/github.com/aquasecurity/kube-bench/ COPY makefile makefile COPY go.mod go.sum ./ @@ -9,17 +9,9 @@ COPY internal/ internal/ ARG KUBEBENCH_VERSION RUN make build && cp kube-bench /go/bin/kube-bench -# Add kubectl to run policies checks -ARG KUBECTL_VERSION TARGETARCH -RUN wget -O /usr/local/bin/kubectl "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" -RUN wget -O kubectl.sha256 "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl.sha256" -# Verify kubectl sha256sum -RUN /bin/bash -c 'echo "$( /etc/yum.repos.d/kubernetes.repo \ + && yum install -y kubectl \ && yum clean all \ && microdnf remove yum || rpm -e -v yum \ - && microdnf clean all + && microdnf clean all \ + && /usr/bin/kubectl version --client WORKDIR /opt/kube-bench/ @@ -39,7 +38,6 @@ ENV PATH=$PATH:/usr/local/mount-from-host/bin COPY LICENSE /licenses/LICENSE COPY --from=build /go/bin/kube-bench /usr/local/bin/kube-bench -COPY --from=build /usr/local/bin/kubectl /usr/local/bin/kubectl COPY entrypoint.sh . COPY cfg/ cfg/ COPY helper_scripts/check_files_owner_in_dir.sh /go/bin diff --git a/cfg/config.yaml b/cfg/config.yaml index 74f0f359e..7d3a231c5 100644 --- a/cfg/config.yaml +++ b/cfg/config.yaml @@ -308,6 +308,7 @@ version_mapping: "gke-1.2.0": "gke-1.2.0" "gke-1.6.0": "gke-1.6.0" "gke-1.8.0": "gke-1.8.0" + "gke-1.9.0": "gke-1.9.0" "ocp-3.10": "rh-0.7" "ocp-3.11": "rh-0.7" "ocp-4.0": "rh-1.0" @@ -435,6 +436,12 @@ target_mapping: - "controlplane" - "policies" - "managedservices" + "gke-1.9.0": + - "master" + - "node" + - "controlplane" + - "policies" + - "managedservices" "eks-1.0.1": - "master" - "node" diff --git a/cfg/gke-1.9.0/config.yaml b/cfg/gke-1.9.0/config.yaml new file mode 100644 index 000000000..d9ab6f274 --- /dev/null +++ b/cfg/gke-1.9.0/config.yaml @@ -0,0 +1,9 @@ +--- +## Version-specific settings that override the values in cfg/config.yaml + +node: + proxy: + defaultkubeconfig: "/var/lib/kubelet/kubeconfig" + + kubelet: + defaultconf: "/etc/kubernetes/kubelet-config.yaml" diff --git a/cfg/gke-1.9.0/controlplane.yaml b/cfg/gke-1.9.0/controlplane.yaml new file mode 100644 index 000000000..9c06acc32 --- /dev/null +++ b/cfg/gke-1.9.0/controlplane.yaml @@ -0,0 +1,6 @@ +--- +controls: +version: "gke-1.9.0" +id: 2 +text: "Control Plane Configuration" +type: "controlplane" diff --git a/cfg/gke-1.9.0/managedservices.yaml b/cfg/gke-1.9.0/managedservices.yaml new file mode 100644 index 000000000..eb6b2a2ab --- /dev/null +++ b/cfg/gke-1.9.0/managedservices.yaml @@ -0,0 +1,751 @@ +--- +controls: +version: "gke-1.9.0" +id: 5 +text: "Managed Services" +type: "managedservices" +groups: + - id: 5.1 + text: "Image Registry and Image Scanning" + checks: + - id: 5.1.1 + text: "Ensure Image Vulnerability Scanning is enabled (Automated)" + audit: "/bin/sh -c 'if gcloud services list --enabled | grep -q containerscanning.googleapis.com; then echo enabled; else echo disabled; fi'" + tests: + test_items: + - flag: enabled + remediation: | + For Images Hosted in GCR: + Using Google Cloud Console + Go to GCR by visiting: https://console.cloud.google.com/gcr + Select Settings and, under the Vulnerability Scanning heading, click the TURN ON button. + Using Command Line + gcloud services enable containeranalysis.googleapis.com + For Images Hosted in AR: + Using Google Cloud Console + Go to GCR by visiting: https://console.cloud.google.com/artifacts + Select Settings and, under the Vulnerability Scanning heading, click the ENABLE button. + Using Command Line + gcloud services enable containerscanning.googleapis.com + scored: true + + - id: 5.1.2 + text: "Minimize user access to 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: + Using Command Line: + + gcloud artifacts repositories set-iam-policy \ + --location + + To learn how to configure policy files see: https://cloud.google.com/artifact-registry/docs/access-control#grant + + For Images Hosted in GCR: + Using Command Line: + To change roles at the GCR bucket level: + Firstly, run the following if read permissions are required: + + gsutil iam ch ::objectViewer gs://artifacts..appspot.com + + Then remove the excessively privileged role (Storage Admin / Storage Object + Admin / Storage Object Creator) using: + + gsutil iam ch -d :: gs://artifacts..appspot.com + + where: + can be one of the following: + user, if the is a Google account. + serviceAccount, if specifies a Service account. + can be one of the following: + a Google account (for example, someone@example.com). + a Cloud IAM service account. + + To modify roles defined at the project level and subsequently inherited within the GCR + bucket, or the Service Account User role, extract the IAM policy file, modify it + accordingly and apply it using: + + gcloud projects set-iam-policy + scored: false + + - 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: + Using Command Line: + Add artifactregistry.reader role + + gcloud artifacts repositories add-iam-policy-binding \ + --location= \ + --member='serviceAccount:' \ + --role='roles/artifactregistry.reader' + + Remove any roles other than artifactregistry.reader + + gcloud artifacts repositories remove-iam-policy-binding \ + --location \ + --member='serviceAccount:' \ + --role='' + + For Images Hosted in GCR: + For an account explicitly granted to the bucket: + Firstly add read access to the Kubernetes Service Account: + + gsutil iam ch ::objectViewer gs://artifacts..appspot.com + + where: + can be one of the following: + user, if the is a Google account. + serviceAccount, if specifies a Service account. + can be one of the following: + a Google account (for example, someone@example.com). + a Cloud IAM service account. + + Then remove the excessively privileged role (Storage Admin / Storage Object + Admin / Storage Object Creator) using: + + gsutil iam ch -d :: gs://artifacts..appspot.com + + For an account that inherits access to the GCR Bucket through Project level + permissions, modify the Projects IAM policy file accordingly, then upload it using: + + gcloud projects set-iam-policy + scored: false + + - id: 5.1.4 + text: "Ensure only trusted container images are used (Manual)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq .binaryAuthorization + type: "manual" + remediation: | + Using Command Line: + Update the cluster to enable Binary Authorization: + + gcloud container cluster update --location --project --binauthz-evaluation-mode= + scored: false + + - id: 5.2 + 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)" + audit: | + /bin/bash -c " + # Check for required variables + if [[ -z \$PROJECT_ID ]] || [[ -z \$CLUSTER_NAME ]] || [[ -z \$LOCATION ]]; then + echo 'MISSING_REQUIRED_VARIABLES' + exit 1 + fi + + # Get NODE_POOL if not already set + if [[ -z \$NODE_POOL ]]; then + NODE_POOL=\$(gcloud container clusters describe \$CLUSTER_NAME --location \$LOCATION --project \$PROJECT_ID --format='value(nodePools[0].name)' 2>/dev/null) + [[ -z \$NODE_POOL ]] && { echo 'FAILED_NODE_POOL'; exit 1; } + fi + + # Get service account from node pool + SA=\$(gcloud container node-pools describe \$NODE_POOL --cluster \$CLUSTER_NAME --location \$LOCATION --project \$PROJECT_ID --format='value(config.serviceAccount)' 2>/dev/null) + [[ -z \$SA ]] && { echo 'FAILED_SERVICE_ACCOUNT'; exit 1; } + + # Check if service account is 'default' + [[ \"\$SA\" == \"default\" ]] && echo 'DEFAULT_SA_IN_USE' || echo 'DEFAULT_SA_NOT_IN_USE' + " + tests: + test_items: + - flag: "DEFAULT_SA_NOT_IN_USE" + set: true + compare: + op: eq + value: "DEFAULT_SA_NOT_IN_USE" + remediation: | + Using Command Line: + To create a minimally privileged service account: + + gcloud iam service-accounts create \ + --display-name "GKE Node Service Account" + export NODE_SA_EMAIL=gcloud iam service-accounts list \ + --format='value(email)' --filter='displayName:GKE Node Service Account' + + Grant the following roles to the service account: + + export PROJECT_ID=gcloud config get-value project + gcloud projects add-iam-policy-binding --member \ + serviceAccount: --role roles/monitoring.metricWriter + gcloud projects add-iam-policy-binding --member \ + serviceAccount: --role roles/monitoring.viewer + gcloud projects add-iam-policy-binding --member \ + serviceAccount: --role roles/logging.logWriter + + To create a new Node pool using the Service account, run the following command: + + gcloud container node-pools create \ + --service-account=@.iam.gserviceaccount.com \ + --cluster= --zone + + Note: The workloads will need to be migrated to the new Node pool, and the old node + pools that use the default service account should be deleted to complete the + remediation. + scored: true + + - id: 5.2.2 + text: "Prefer using dedicated GCP Service Accounts and Workload Identity (Manual)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq .workloadIdentityConfig + type: "manual" + remediation: | + Using Command Line: + + gcloud container clusters update --location \ + --workload-pool .svc.id.goog + + Note that existing Node pools are unaffected. New Node pools default to --workload- + metadata-from-node=GKE_METADATA_SERVER. + + Then, modify existing Node pools to enable GKE_METADATA_SERVER: + + gcloud container node-pools update --cluster \ + --location --workload-metadata=GKE_METADATA + + Workloads may need to be modified in order for them to use Workload Identity as + described within: https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity. + Also consider the effects on the availability of hosted workloads as Node pools + are updated. It may be more appropriate to create new Node Pools. + scored: false + + - id: 5.3 + text: "Cloud Key Management Service (Cloud KMS)" + checks: + - id: 5.3.1 + text: "Ensure Kubernetes Secrets are encrypted using keys managed in Cloud KMS (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.databaseEncryption' + type: "manual" + remediation: | + To create a key: + Create a key ring: + + gcloud kms keyrings create --location --project \ + + + Create a key: + + gcloud kms keys create --location --keyring \ + --purpose encryption --project + + Grant the Kubernetes Engine Service Agent service account the Cloud KMS + CryptoKey Encrypter/Decrypter role: + + gcloud kms keys add-iam-policy-binding --location \ + --keyring --member serviceAccount: \ + --role roles/cloudkms.cryptoKeyEncrypterDecrypter --project + + To create a new cluster with Application-layer Secrets Encryption: + + gcloud container clusters create --cluster-version=latest \ + --zone \ + --database-encryption-key projects//locations//keyRings//cryptoKeys/ \ + --project + + To enable on an existing cluster: + + gcloud container clusters update --zone \ + --database-encryption-key projects//locations//keyRings//cryptoKeys/ \ + --project + scored: false + + - id: 5.4 + text: "Node Metadata" + checks: + - id: 5.4.1 + text: "Ensure the GKE Metadata Server is Enabled (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --format json | jq '.nodePools[].config.workloadMetadataConfig' + type: "manual" + remediation: | + Using Command Line: + + gcloud container clusters update --identity-namespace=.svc.id.goog + + Note that existing Node pools are unaffected. New Node pools default to --workload- + metadata-from-node=GKE_METADATA_SERVER. + + To modify an existing Node pool to enable GKE Metadata Server: + + gcloud container node-pools update --cluster= \ + --workload-metadata-from-node=GKE_METADATA_SERVER + + Workloads may need modification in order for them to use Workload Identity as + described within: https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity. + scored: false + + - id: 5.5 + text: "Node Configuration and Maintenance" + checks: + - id: 5.5.1 + text: "Ensure Container-Optimized OS (cos_containerd) is used for GKE node images (Automated)" + audit: | + gcloud container node-pools describe $NODE_POOL --cluster $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.config.imageType' + type: "manual" + remediation: | + Using Command Line: + To set the node image to cos for an existing cluster's Node pool: + + gcloud container clusters upgrade --image-type cos_containerd \ + --location --node-pool + scored: false + + - id: 5.5.2 + text: "Ensure Node Auto-Repair is enabled for GKE nodes (Automated)" + audit: | + gcloud container node-pools describe $POOL_NAME --cluster $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.management' + type: "manual" + remediation: | + Using Command Line: + To enable node auto-repair for an existing cluster's Node pool: + + gcloud container node-pools update --cluster \ + --location --enable-autorepair + scored: false + + - id: 5.5.3 + text: "Ensure Node Auto-Upgrade is enabled for GKE nodes (Automated)" + audit: | + gcloud container node-pools describe $POOL_NAME --cluster $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.management' + type: "manual" + remediation: | + Using Command Line: + To enable node auto-upgrade for an existing cluster's Node pool, run the following + command: + + gcloud container node-pools update --cluster \ + --location --enable-autoupgrade + scored: false + + - id: 5.5.4 + text: "When creating New Clusters - Automate GKE version management using Release Channels (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq .releaseChannel.channel + type: "manual" + remediation: | + Using Command Line: + Create a new cluster by running the following command: + + gcloud container clusters create --location --release-channel + + where is stable or regular, according to requirements. + scored: false + + - id: 5.5.5 + text: "Ensure Shielded GKE Nodes are Enabled (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.shieldedNodes' + type: "manual" + remediation: | + Using Command Line: + To migrate an existing cluster, the flag --enable-shielded-nodes needs to be + specified in the cluster update command: + + gcloud container clusters update --location --enable-shielded-nodes + scored: false + + - id: 5.5.6 + text: "Ensure Integrity Monitoring for Shielded GKE Nodes is Enabled (Automated)" + audit: | + gcloud container node-pools describe $POOL_NAME --cluster $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq .config.shieldedInstanceConfig + type: "manual" + remediation: | + Using Command Line: + To create a Node pool within the cluster with Integrity Monitoring enabled, run the + following command: + + gcloud container node-pools create --cluster --location --shielded-integrity-monitoring + + Workloads from existing non-conforming Node pools will need to be migrated to the + newly created Node pool, then delete non-conforming Node pools to complete the + remediation + scored: false + + - id: 5.5.7 + text: "Ensure Secure Boot for Shielded GKE Nodes is Enabled (Automated)" + audit: | + gcloud container node-pools describe $POOL_NAME --cluster $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq .config.shieldedInstanceConfig + type: "manual" + remediation: | + Using Command Line: + To create a Node pool within the cluster with Secure Boot enabled, run the following + command: + + gcloud container node-pools create --cluster --location --shielded-secure-boot + + + Workloads will need to be migrated from existing non-conforming Node pools to the + newly created Node pool, then delete the non-conforming pools. + scored: false + + - id: 5.6 + text: "Cluster Networking" + checks: + - id: 5.6.1 + text: "Enable VPC Flow Logs and Intranode Visibility (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.networkConfig.enableIntraNodeVisibility' + type: "manual" + remediation: | + Using Command Line: + 1. Find the subnetwork name associated with the cluster. + + gcloud container clusters describe \ + --location --format json | jq '.subnetwork' + + 2. Update the subnetwork to enable VPC Flow Logs. + gcloud compute networks subnets update --enable-flow-logs + scored: false + + - id: 5.6.2 + text: "Ensure use of VPC-native clusters (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.ipAllocationPolicy.useIpAliases' + type: "manual" + remediation: | + Using Command Line: + To enable Alias IP on a new cluster, run the following command: + + gcloud container clusters create --location \ + --enable-ip-alias + scored: false + + - id: 5.6.3 + text: "Ensure Control Plane Authorized Networks is Enabled (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.masterAuthorizedNetworksConfig' + type: "manual" + remediation: | + Using Command Line: + To enable Control Plane Authorized Networks for an existing cluster, run the following + command: + + gcloud container clusters update --location \ + --enable-master-authorized-networks + + Along with this, you can list authorized networks using the --master-authorized-networks + flag which contains a list of up to 20 external networks that are allowed to + connect to your cluster's control plane through HTTPS. You provide these networks as + a comma-separated list of addresses in CIDR notation (such as 90.90.100.0/24). + scored: false + + - id: 5.6.4 + text: "Ensure clusters are created with Private Endpoint Enabled and Public Access Disabled (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.privateClusterConfig.enablePrivateEndpoint' + type: "manual" + remediation: | + Using Command Line: + Create a cluster with a Private Endpoint enabled and Public Access disabled by including + the --enable-private-endpoint flag within the cluster create command: + + gcloud container clusters create --enable-private-endpoint + + Setting this flag also requires the setting of --enable-private-nodes, --enable-ip-alias + and --master-ipv4-cidr=. + scored: false + + - id: 5.6.5 + text: "Ensure clusters are created with Private Nodes (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.privateClusterConfig.enablePrivateNodes' + type: "manual" + remediation: | + Using Command Line: + To create a cluster with Private Nodes enabled, include the --enable-private-nodes + flag within the cluster create command: + + gcloud container clusters create --enable-private-nodes + + Setting this flag also requires the setting of --enable-ip-alias and + --master-ipv4-cidr=. + scored: false + + - id: 5.6.6 + text: "Consider firewalling GKE worker nodes (Manual)" + audit: | + gcloud compute instances describe $CLUSTER_NAME --zone $LOCATION --project $PROJECT_ID --format json | \ + jq '{tags: .tags.items[], serviceaccount:.serviceAccounts[].email, network: .networkInterfaces[].network}' + type: "manual" + remediation: | + Using Command Line: + Use the following command to generate firewall rules, setting the variables as + appropriate: + + gcloud compute firewall-rules create \ + --network --priority --direction \ + --action --target-tags \ + --target-service-accounts \ + --source-ranges --source-tags \ + --source-service-accounts \ + --destination-ranges --rules + scored: false + + - id: 5.6.7 + text: "Ensure use of Google-managed SSL Certificates (Automated)" + audit: | + svc_json="$(kubectl get svc -A -o json 2>/dev/null || echo '{"items":[],"__err":"SVC_FORBIDDEN"}')" + ing_json="$(kubectl get ingress -A -o json 2>/dev/null || echo '{"items":[],"__err":"INGRESS_FORBIDDEN"}')" + mc_json="$(kubectl get managedcertificates -A -o json 2>/dev/null || echo '{"items":[],"__err":"MC_FORBIDDEN"}')" + + printf '%s\n%s\n%s\n' "$svc_json" "$ing_json" "$mc_json" \ + | jq -rs ' + (.[0] // {}) as $svcsRaw | + (.[1] // {}) as $ingsRaw | + (.[2] // {}) as $mcsRaw | + + # If any list failed, surface an error and DO NOT print the success string + if ($svcsRaw.__err or $ingsRaw.__err or $mcsRaw.__err) then + "ERROR_KUBECTL_LIST:" + + ([ + ($svcsRaw.__err // empty), + ($ingsRaw.__err // empty), + ($mcsRaw.__err // empty) + ] | join(",")) + else + ($svcsRaw.items // []) as $svcs | + ($ingsRaw.items // []) as $ings | + ($mcsRaw.items // []) as $mcs | + + def trim: gsub("^\\s+|\\s+$";""); + def hasmc($ns;$name): any($mcs[]?; .metadata.namespace==$ns and .metadata.name==$name); + + ([ + # Public Services (not eligible for managed certs) + $svcs[]? | select(.spec.type=="LoadBalancer") + | "FOUND_PUBLIC_LB_SERVICE:\(.metadata.namespace // "default"):\(.metadata.name)" + ] + [ + # Ingresses missing managed-certs annotation + $ings[]? as $i + | ($i.metadata.annotations."networking.gke.io/managed-certificates" // "") as $ann + | select($ann=="") + | "FOUND_INGRESS_WITHOUT_MANAGED_CERT:\($i.metadata.namespace // "default"):\($i.metadata.name)" + ] + [ + # Ingresses referencing non-existent ManagedCertificate(s) + $ings[]? as $i + | ($i.metadata.annotations."networking.gke.io/managed-certificates" // "") as $ann + | select($ann!="") + | ($i.metadata.namespace // "default") as $ns + | ($ann | split(",") | map(trim) | map(select(length>0)) | .[]) as $mc + | select(hasmc($ns;$mc) | not) + | "FOUND_MISSING_MANAGED_CERT_RESOURCE:\($ns):\($i.metadata.name):cert=\($mc)" + ]) as $f + | if ($f|length)>0 + then $f[] + else "ALL_INGRESSES_USE_MANAGED_CERTS_AND_NO_PUBLIC_LB_SERVICES" + end + end + ' + tests: + test_items: + - flag: "ALL_INGRESSES_USE_MANAGED_CERTS_AND_NO_PUBLIC_LB_SERVICES" + set: true + compare: + op: eq + value: "ALL_INGRESSES_USE_MANAGED_CERTS_AND_NO_PUBLIC_LB_SERVICES" + remediation: | + If services of type:LoadBalancer are discovered, consider replacing the Service with + an Ingress. + + To configure the Ingress and use Google-managed SSL certificates, follow the + instructions as listed at: https://cloud.google.com/kubernetes-engine/docs/how- + to/managed-certs. + scored: true + + - id: 5.7 + text: "Logging" + checks: + - id: 5.7.1 + text: "Ensure Logging and Cloud Monitoring is Enabled (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | \ + jq '{loggingService: .loggingService, monitoringService: .monitoringService}' + type: "manual" + remediation: | + To enable Logging for an existing cluster, run the following command: + gcloud container clusters update --location \ + --logging= + + See https://cloud.google.com/sdk/gcloud/reference/container/clusters/update#--logging + for a list of available components for logging. + + To enable Cloud Monitoring for an existing cluster, run the following command: + gcloud container clusters update --location \ + --monitoring= + + See https://cloud.google.com/sdk/gcloud/reference/container/clusters/update#-- + monitoring for a list of available components for Cloud Monitoring. + scored: false + + - id: 5.7.2 + text: "Enable Linux auditd logging (Manual)" + audit: | + kubectl get daemonsets -A -o json | jq '.items[] | select (.spec.template.spec.containers[].image | contains ("gcr.io/stackdriver-agents/stackdriver-logging-agent"))'| jq '{name: .metadata.name, annotations: .metadata.annotations."kubernetes.io/description", namespace: .metadata.namespace, status: .status}' + type: "manual" + remediation: | + Using Command Line: + Download the example manifests: + curl https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-node-tools/master/os-audit/cos-auditd-logging.yaml > cos-auditd-logging.yaml + + Edit the example manifests if needed. Then, deploy them: + kubectl apply -f cos-auditd-logging.yaml + + Verify that the logging Pods have started. If a different Namespace was defined in the + manifests, replace cos-auditd with the name of the namespace being used: + kubectl get pods --namespace=cos-auditd + scored: false + + - id: 5.8 + text: "Authentication and Authorization" + checks: + - id: 5.8.1 + text: "Ensure authentication using Client Certificates is Disabled (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.masterAuth.clientKey' + type: "manual" + remediation: | + Using Command Line: + Create a new cluster without a Client Certificate: + gcloud container clusters create [CLUSTER_NAME] \ + --no-issue-client-certificate + scored: false + + - id: 5.8.2 + text: "Manage Kubernetes RBAC users with Google Groups for GKE (Manual)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '{Enabled: .authenticatorGroupsConfig.enabled, "Security Group": .authenticatorGroupsConfig.securityGroup}' + type: "manual" + remediation: | + Using Command Line: + Follow the G Suite Groups instructions at: https://cloud.google.com/kubernetes- + engine/docs/how-to/role-based-access-control#google-groups-for-gke. + + Command line statement to create a new cluster: + gcloud container clusters create CLUSTER_NAME \ + --location=CONTROL_PLANE_LOCATION \ + --security-group="gke-security-groups@DOMAIN" + + Command line statement to update an existing cluster: + gcloud container clusters update CLUSTER_NAME \ + --location=CONTROL_PLANE_LOCATION \ + --security-group="gke-security-groups@DOMAIN" + + Finally create Roles, ClusterRoles, RoleBindings, and ClusterRoleBindings that + reference the G Suite Groups. + scored: false + + - id: 5.8.3 + text: "Ensure Legacy Authorization (ABAC) is Disabled (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.legacyAbac' + type: "manual" + remediation: | + Using Command Line: + To disable Legacy Authorization for an existing cluster, run the following command: + gcloud container clusters update --location --no-enable-legacy-authorization + scored: false + + - id: 5.9 + text: "Storage" + checks: + - id: 5.9.1 + text: "Enable Customer-Managed Encryption Keys (CMEK) for GKE Persistent Disks (PD) (Manual)" + audit: | + gcloud compute disks describe $PV_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.diskEncryptionKey.kmsKeyName' + type: "manual" + remediation: | + Using Command Line: + Follow the instructions detailed at: https://cloud.google.com/kubernetes-engine/docs/how-to/using-cmek. + scored: false + + - id: 5.9.2 + text: "Enable Customer-Managed Encryption Keys (CMEK) for Boot Disks (Automated)" + audit: | + gcloud container node-pools describe $NODE_POOL --cluster $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format="table(name, config.diskType, config.bootDiskKmsKey)" + type: "manual" + remediation: | + Using Command Line: + Create a new node pool using customer-managed encryption keys for the node boot + disk, of either pd-standard or pd-ssd: + gcloud container node-pools create --disk-type \ + --boot-disk-kms-key projects//locations//keyRings//cryptoKeys/ + + Create a cluster using customer-managed encryption keys for the node boot disk, of + either pd-standard or pd-ssd: + gcloud container clusters create --disk-type \ + --boot-disk-kms-key projects//locations//keyRings//cryptoKeys/ + scored: false + + - id: 5.10 + text: "Other Cluster Configurations" + checks: + - id: 5.10.1 + text: "Ensure Kubernetes Web UI is Disabled (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.addonsConfig.kubernetesDashboard + type: "manual" + remediation: | + Using Command Line: + To disable the Kubernetes Dashboard on an existing cluster, run the following + command: + gcloud container clusters update --location --update-addons=KubernetesDashboard=DISABLED + scored: false + + - id: 5.10.2 + text: "Ensure that Alpha clusters are not used for production workloads (Automated)" + audit: | + gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.enableKubernetesAlpha' + type: "manual" + remediation: | + Using Command Line: + Upon creating a new cluster + gcloud container clusters create --location + + Do not use the --enable-kubernetes-alpha argument. + scored: false + + - id: 5.10.3 + text: "Consider GKE Sandbox for running untrusted workloads (Automated)" + audit: | + gcloud container node-pools describe $NODE_POOL --cluster $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.config.sandboxConfig' + type: "manual" + remediation: | + Using Command Line: + To enable GKE Sandbox on an existing cluster, a new Node pool must be created, + which can be done using: + gcloud container node-pools create --location --cluster --image-type=cos_containerd --sandbox="type=gvisor + scored: false + + - id: 5.10.4 + text: "Enable Security Posture (Automated)" + audit: "gcloud container clusters describe $CLUSTER_NAME --location $LOCATION --project $PROJECT_ID --format json | jq '.securityPostureConfig'" + type: "manual" + remediation: | + Using Command Line: + To enable Security Posture on an existing cluster, run the following command: + gcloud container clusters update CLUSTER_NAME \ + --location=CONTROL_PLANE_LOCATION \ + --security-posture=standard + scored: false diff --git a/cfg/gke-1.9.0/master.yaml b/cfg/gke-1.9.0/master.yaml new file mode 100644 index 000000000..f83172f5d --- /dev/null +++ b/cfg/gke-1.9.0/master.yaml @@ -0,0 +1,6 @@ +--- +controls: +version: "gke-1.9.0" +id: 1 +text: "Control Plane Components" +type: "master" diff --git a/cfg/gke-1.9.0/node.yaml b/cfg/gke-1.9.0/node.yaml new file mode 100644 index 000000000..d62809b9a --- /dev/null +++ b/cfg/gke-1.9.0/node.yaml @@ -0,0 +1,65 @@ +--- +controls: +version: "gke-1.9.0" +id: 3 +text: "Worker Nodes" +type: "node" +groups: + - id: 3.1 + text: "Worker Node Configuration Files" + 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'' ' + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "644" + remediation: | + Run the below command (based on the file location on your system) on each worker node. + For example, + + chmod 644 $proxykubeconfig + 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'' ' + tests: + test_items: + - flag: root:root + remediation: | + Run the below command (based on the file location on your system) on each worker node. + For example: + + chown root:root $proxykubeconfig + scored: true + + - id: 3.1.3 + text: "Ensure that the kubelet configuration file has permissions set to 644 (Automated)" + audit: '/bin/sh -c ''if test -e $kubeletconf; then stat -c permissions=%a $kubeletconf; fi'' ' + tests: + test_items: + - flag: "permissions" + compare: + op: bitmask + value: "644" + remediation: | + Run the following command (using the kubelet config file location) + + chmod 644 $kubeletconf + scored: true + + - id: 3.1.4 + text: "Ensure that the kubelet configuration file ownership is set to root:root (Automated)" + audit: '/bin/sh -c ''if test -e $kubeletconf; then stat -c %U:%G $kubeletconf; fi'' ' + tests: + test_items: + - flag: root:root + remediation: | + Run the following command (using the config file location identied in the Audit step) + + chown root:root $kubeletconf + scored: true diff --git a/cfg/gke-1.9.0/policies.yaml b/cfg/gke-1.9.0/policies.yaml new file mode 100644 index 000000000..f96318efd --- /dev/null +++ b/cfg/gke-1.9.0/policies.yaml @@ -0,0 +1,511 @@ +--- +controls: +version: "gke-1.9.0" +id: 4 +text: "Kubernetes Policies" +type: "policies" +groups: + - id: 4.1 + text: "RBAC and Service Accounts" + checks: + - id: 4.1.1 + text: "Ensure that the cluster-admin role is only used where required (Automated)" + audit: | + kubectl get clusterrolebindings -o json | jq -r ' + [ + .items[] + | select(.roleRef.name == "cluster-admin") + | .subjects[]? + | select(.kind != "Group" or .name != "system:masters") + ] + | if length == 0 + then "NO_CLUSTER_ADMIN_BINDINGS" + else "FOUND_CLUSTER_ADMIN_BINDING" + end + ' + tests: + test_items: + - flag: "NO_CLUSTER_ADMIN_BINDINGS" + set: true + compare: + op: eq + value: "NO_CLUSTER_ADMIN_BINDINGS" + remediation: | + Identify all ClusterRoleBindings to the "cluster-admin" role and review their subjects: + + kubectl get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECTS:.subjects[*].name | grep cluster-admin + + If non-system principals (users, groups, or service accounts) do not strictly require cluster-admin, + rebind them to a least-privileged (Cluster)Role and then remove the excessive binding: + + kubectl delete clusterrolebinding + + Notes: + - Do not modify bindings with the "system:" prefix that are required for core components. + - Prefer assigning narrowly scoped Roles/ClusterRoles that grant only the permissions needed. + scored: true + + - id: 4.1.2 + text: "Minimize access to secrets (Automated)" + audit: | + count=$(kubectl get roles --all-namespaces -o json | jq ' + .items[] + | select(.rules[]? + | (.resources[]? == "secrets") + and ((.verbs[]? == "get") or (.verbs[]? == "list") or (.verbs[]? == "watch")) + )' | wc -l) + + if [ "$count" -gt 0 ]; then + echo "SECRETS_ACCESS_FOUND" + fi + tests: + test_items: + - flag: "SECRETS_ACCESS_FOUND" + set: false + remediation: | + Where possible, remove get, list and watch access to Secret objects in the cluster. + scored: true + + - id: 4.1.3 + text: "Minimize wildcard use in Roles and ClusterRoles (Automated)" + audit: | + wildcards=$(kubectl get roles --all-namespaces -o json | jq ' + .items[] | select( + .rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*") + )' | wc -l) + + wildcards_clusterroles=$(kubectl get clusterroles -o json | jq ' + .items[] | select( + .rules[]? | (.verbs[]? == "*" or .resources[]? == "*" or .apiGroups[]? == "*") + )' | wc -l) + + total=$((wildcards + wildcards_clusterroles)) + + if [ "$total" -gt 0 ]; then + echo "wildcards_present" + fi + tests: + test_items: + - flag: wildcards_present + set: false + remediation: | + Where possible replace any use of wildcards in clusterroles and roles with specific + objects or actions. + scored: true + + - id: 4.1.4 + text: "Ensure that default service accounts are not actively used (Automated)" + audit: | + echo "šŸ”¹ Default Service Accounts with automountServiceAccountToken enabled:" + default_sa_count=$(kubectl get serviceaccounts --all-namespaces -o json | jq ' + [.items[] | select(.metadata.name == "default" and (.automountServiceAccountToken != false))] | length') + if [ "$default_sa_count" -gt 0 ]; then + echo "default_sa_not_auto_mounted" + fi + + echo "\nšŸ”¹ Pods using default ServiceAccount:" + pods_using_default_sa=$(kubectl get pods --all-namespaces -o json | jq ' + [.items[] | select(.spec.serviceAccountName == "default")] | length') + if [ "$pods_using_default_sa" -gt 0 ]; then + echo "default_sa_used_in_pods" + fi + tests: + test_items: + - flag: default_sa_not_auto_mounted + set: false + - flag: default_sa_used_in_pods + set: false + remediation: | + Create explicit service accounts wherever a Kubernetes workload requires specific + access to the Kubernetes API server. + + Modify the configuration of each default service account to include this value + + automountServiceAccountToken: false + scored: true + + - id: 4.1.5 + text: "Ensure that Service Account Tokens are only mounted where necessary (Automated)" + audit: | + echo "šŸ”¹ Pods with automountServiceAccountToken enabled:" + pods_with_token_mount=$(kubectl get pods --all-namespaces -o json | jq ' + [.items[] | select(.spec.automountServiceAccountToken != false)] | length') + + if [ "$pods_with_token_mount" -gt 0 ]; then + echo "automountServiceAccountToken" + fi + tests: + test_items: + - flag: automountServiceAccountToken + set: false + remediation: | + Modify the definition of pods and service accounts which do not need to mount service + account tokens to disable it. + scored: true + + - id: 4.1.6 + text: "Avoid use of system:masters group (Automated)" + audit: | + found=0 + for csr in $(kubectl get csr -o name 2>/dev/null | sed 's|^.*/||'); do + req=$(kubectl get csr "$csr" -o jsonpath='{.spec.request}' 2>/dev/null) + [ -z "$req" ] && continue + if echo "$req" | base64 -d 2>/dev/null | openssl req -noout -text 2>/dev/null | grep -q 'O = system:masters'; then + conds=$(kubectl get csr "$csr" -o json | jq -r '[.status.conditions[]?.type] | join(",")') + echo "FOUND_SYSTEM_MASTERS_CSR:${csr}:${conds:-NONE}" + found=1 + fi + done + if [ "$found" -eq 0 ]; then + echo "NO_SYSTEM_MASTERS_CREDENTIALS_FOUND" + fi + tests: + test_items: + - flag: "NO_SYSTEM_MASTERS_CREDENTIALS_FOUND" + set: true + compare: + op: eq + value: "NO_SYSTEM_MASTERS_CREDENTIALS_FOUND" + remediation: | + Remove the system:masters group from all users in the cluster. + scored: true + + - id: 4.1.7 + text: "Limit use of the Bind, Impersonate and Escalate permissions in the Kubernetes cluster (Manual)" + type: "manual" + audit: | + kubectl get clusterrole,role -A -o json | jq -r ' + def risky_verbs: ["bind","impersonate","escalate"]; + + .items[] + | . as $r + | [ $r.rules[]? + | select( + (.verbs // [] | any(. as $v | $v == "bind" or $v == "impersonate" or $v == "escalate")) + ) + | { + resources: ((.resources // ["*"]) | join(",")), + verbs: ((.verbs // []) | map(select(IN("bind","impersonate","escalate"))) | join(",")) + } + ] as $matches + | select($matches | length > 0) + | [ + $r.kind, + $r.metadata.name, + ($r.metadata.namespace // "cluster-wide"), + ($matches | map(.resources) | unique | join(",")), + ($matches | map(.verbs) | unique | join(",")) + ] | @tsv + ' | awk -F'\t' 'BEGIN{ + printf "%-15s %-40s %-20s %-40s %-30s\n","KIND","NAME","NAMESPACE","RESOURCES","VERBS" + print "--------------- ---------------------------------------- -------------------- ---------------------------------------- ------------------------------" + }{ + printf "%-15s %-40s %-20s %-40s %-30s\n",$1,$2,$3,$4,$5 + }' + remediation: | + Where possible, remove the impersonate, bind and escalate rights from subjects. + scored: false + + - id: 4.1.8 + text: "Avoid bindings to system:anonymous (Automated)" + audit: | + # Flags any ClusterRoleBinding/RoleBinding that targets the user "system:anonymous". + # Prints "NO_ANONYMOUS_BINDINGS" when none are found. + ( + kubectl get clusterrolebindings -o json | jq -r ' + .items[] + | select((.subjects | length) > 0) + | select(any(.subjects[]?; + .kind=="User" and .name=="system:anonymous" + )) + | "FOUND_ANONYMOUS:ClusterRoleBinding:\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)" + '; + kubectl get rolebindings -A -o json | jq -r ' + .items[] + | select((.subjects | length) > 0) + | select(any(.subjects[]?; + .kind=="User" and .name=="system:anonymous" + )) + | "FOUND_ANONYMOUS:RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)" + ' + ) | (grep -q '^FOUND_ANONYMOUS:' && cat || echo 'NO_ANONYMOUS_BINDINGS') + tests: + test_items: + - flag: "NO_ANONYMOUS_BINDINGS" + set: true + compare: + op: eq + value: "NO_ANONYMOUS_BINDINGS" + remediation: | + Identify all clusterrolebindings and rolebindings to the user system:anonymous. + Check if they are used and review the permissions associated with the binding using the + commands in the Audit section above or refer to GKE documentation + (https://cloud.google.com/kubernetes-engine/docs/best-practices/rbac#detect-prevent-default). + + Strongly consider replacing unsafe bindings with an authenticated, user-defined group. + Where possible, bind to non-default, user-defined groups with least-privilege roles. + + If there are any unsafe bindings to the user system:anonymous, proceed to delete them + after consideration for cluster operations with only necessary, safer bindings. + + kubectl delete clusterrolebinding [CLUSTER_ROLE_BINDING_NAME] + kubectl delete rolebinding [ROLE_BINDING_NAME] --namespace [ROLE_BINDING_NAMESPACE] + scored: true + + - id: 4.1.9 + text: "Avoid non-default bindings to system:unauthenticated (Automated)" + audit: | + # Flags any non-default binding to the group "system:unauthenticated". + # Prints "NO_NON_DEFAULT_UNAUTH_BINDINGS" when none are found. + ( + kubectl get clusterrolebindings -o json | jq -r ' + .items[] + | select(.metadata.name != "system:public-info-viewer") + | select((.subjects | length) > 0) + | select(any(.subjects[]?; + .kind=="Group" and .name=="system:unauthenticated" + )) + | "FOUND_UNAUTH:ClusterRoleBinding:\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)" + '; + kubectl get rolebindings -A -o json | jq -r ' + .items[] + | select((.subjects | length) > 0) + | select(any(.subjects[]?; + .kind=="Group" and .name=="system:unauthenticated" + )) + | "FOUND_UNAUTH:RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)" + ' + ) | (grep -q "^FOUND_UNAUTH:" && cat || echo "NO_NON_DEFAULT_UNAUTH_BINDINGS") + tests: + test_items: + - flag: "NO_NON_DEFAULT_UNAUTH_BINDINGS" + set: true + compare: + op: eq + value: "NO_NON_DEFAULT_UNAUTH_BINDINGS" + remediation: | + Identify all non-default clusterrolebindings and rolebindings to the group + system:unauthenticated. Check if they are used and review the permissions + associated with the binding using the commands in the Audit section above or refer to + GKE documentation (https://cloud.google.com/kubernetes-engine/docs/best-practices/rbac#detect-prevent-default). + + Strongly consider replacing non-default, unsafe bindings with an authenticated, user- + defined group. Where possible, bind to non-default, user-defined groups with least- + privilege roles. + + If there are any non-default, unsafe bindings to the group system:unauthenticated, + proceed to delete them after consideration for cluster operations with only necessary, + safer bindings. + + kubectl delete clusterrolebinding [CLUSTER_ROLE_BINDING_NAME] + kubectl delete rolebinding [ROLE_BINDING_NAME] --namespace [ROLE_BINDING_NAMESPACE] + scored: true + + - id: 4.1.10 + text: "Avoid non-default bindings to system:authenticated (Automated)" + audit: | + # Flags any non-default binding to the group "system:authenticated". + # Allowed defaults (CRB): system:basic-user, system:discovery + # Prints "NO_NON_DEFAULT_AUTH_BINDINGS" when none are found. + ( + kubectl get clusterrolebindings -o json | jq -r ' + .items[] + | select((.metadata.name != "system:basic-user") and (.metadata.name != "system:discovery")) + | select((.subjects | length) > 0) + | select(any(.subjects[]?; + .kind=="Group" and .name=="system:authenticated" + )) + | "FOUND_AUTH:ClusterRoleBinding:\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)" + '; + kubectl get rolebindings -A -o json | jq -r ' + .items[] + | select((.subjects | length) > 0) + | select(any(.subjects[]?; + .kind=="Group" and .name=="system:authenticated" + )) + | "FOUND_AUTH:RoleBinding:\(.metadata.namespace):\(.metadata.name):ROLE=\(.roleRef.kind)/\(.roleRef.name)" + ' + ) | (grep -q "^FOUND_AUTH:" && cat || echo "NO_NON_DEFAULT_AUTH_BINDINGS") + tests: + test_items: + - flag: "NO_NON_DEFAULT_AUTH_BINDINGS" + set: true + compare: + op: eq + value: "NO_NON_DEFAULT_AUTH_BINDINGS" + remediation: | + Identify all non-default clusterrolebindings and rolebindings to the group + system:authenticated. Check if they are used and review the permissions associated + with the binding using the commands in the Audit section above or refer to GKE + documentation. + + Strongly consider replacing non-default, unsafe bindings with an authenticated, user- + defined group. Where possible, bind to non-default, user-defined groups with least- + privilege roles. + + If there are any non-default, unsafe bindings to the group system:authenticated, + proceed to delete them after consideration for cluster operations with only necessary, + safer bindings. + + kubectl delete clusterrolebinding [CLUSTER_ROLE_BINDING_NAME] + kubectl delete rolebinding [ROLE_BINDING_NAME] --namespace [ROLE_BINDING_NAMESPACE] + scored: true + + - id: 4.2 + text: "Pod Security Standards" + checks: + - id: 4.2.1 + text: "Ensure that the cluster enforces Pod Security Standard Baseline profile or stricter for all namespaces. (Manual)" + audit: | + diff \ + <(kubectl get namespace -l pod-security.kubernetes.io/enforce=baseline -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}') \ + <(kubectl get namespace -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}') + type: "manual" + remediation: | + Ensure that Pod Security Admission is in place for every namespace which contains + user workloads. + Run the following command to enforce the Baseline profile in a namespace: + + kubectl label namespace pod-security.kubernetes.io/enforce=baseline + scored: false + + - id: 4.3 + text: "Network Policies and CNI" + checks: + - id: 4.3.1 + text: "Ensure that the CNI in use supports Network Policies (Manual)" + type: "manual" + remediation: | + To use a CNI plugin with Network Policy, enable Network Policy in GKE, and the CNI plugin + will be updated. See Recommendation 5.6.7. + scored: false + + - id: 4.3.2 + text: "Ensure that all Namespaces have Network Policies defined (Automated)" + audit: | + (kubectl get ns -o json; kubectl get networkpolicy -A -o json) \ + | jq -rs ' + (.[0].items | map(.metadata.name) + | map(select(.!="kube-system" and .!="kube-public" and .!="kube-node-lease"))) as $ns + | + ( (.[1].items // []) + | sort_by(.metadata.namespace) + | group_by(.metadata.namespace) + | map({key: .[0].metadata.namespace, value: length}) + | from_entries + ) as $np + | + [ $ns[] | select( ($np[.] // 0) == 0 ) ] as $missing + | + if ($missing|length)>0 + then ($missing[] | "FOUND_NAMESPACE_WITHOUT_NETWORKPOLICY:"+.) + else "ALL_NAMESPACES_HAVE_NETWORK_POLICIES" + end + ' + tests: + test_items: + - flag: "ALL_NAMESPACES_HAVE_NETWORK_POLICIES" + set: true + compare: + op: eq + value: "ALL_NAMESPACES_HAVE_NETWORK_POLICIES" + remediation: | + Follow the documentation and create NetworkPolicy objects as needed. + See: https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy#creating_a_network_policy + for more information. + scored: true + + - id: 4.4 + text: "Secrets Management" + checks: + - id: 4.4.1 + text: "Prefer using secrets as files over secrets as environment variables (Automated)" + audit: | + output=$(kubectl get all --all-namespaces -o jsonpath='{range .items[?(@..secretKeyRef)]} {.kind} {.metadata.name} {"\n"}{end}') + if [ -z "$output" ]; then echo "NO_ENV_SECRET_REFERENCES"; else echo "ENV_SECRET_REFERENCES_FOUND"; fi + tests: + test_items: + - flag: "NO_ENV_SECRET_REFERENCES" + set: true + compare: + op: eq + value: "NO_ENV_SECRET_REFERENCES" + remediation: | + If possible, rewrite application code to read secrets from mounted secret files, rather than + from environment variables. + scored: true + + - id: 4.4.2 + text: "Consider external secret storage (Manual)" + type: "manual" + remediation: | + Refer to the secrets management options offered by your cloud provider or a third-party + secrets management solution. + scored: false + + - id: 4.5 + text: "Extensible Admission Control" + checks: + - id: 4.5.1 + text: "Configure Image Provenance using ImagePolicyWebhook admission controller (Manual)" + type: "manual" + remediation: | + Follow the Kubernetes documentation and setup image provenance. + Also see recommendation 5.1.4. + scored: false + + - id: 4.6 + text: "General Policies" + checks: + - id: 4.6.1 + text: "Create administrative boundaries between resources using namespaces (Manual)" + type: "manual" + audit: | + kubectl get namespaces + remediation: | + Follow the documentation and create namespaces for objects in your deployment as you need + them. + scored: false + + - id: 4.6.2 + text: "Ensure that the seccomp profile is set to RuntimeDefault in your pod definitions (Automated)" + type: "manual" + audit: | + kubectl get pods --all-namespaces -o json |\ + jq -r '.items[] | select(.metadata.annotations."seccomp.security.alpha.kubernetes.io/pod" == "runtime/default" or .spec.securityContext.seccompProfile.type == "RuntimeDefault") | {namespace: .metadata.namespace, name: .metadata.name, seccompProfile: .spec.securityContext.seccompProfile.type}' + remediation: | + Use security context to enable the RuntimeDefault seccomp profile in your pod + definitions. An example is as below: + + { + "namespace": "kube-system", + "name": "metrics-server-v0.7.0-dbcc8ddf6-gz7d4", + "seccompProfile": "RuntimeDefault" + } + scored: false + + - id: 4.6.3 + text: "Apply Security Context to Your Pods and Containers (Manual)" + type: "manual" + 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 Google Container- + Optimized OS Benchmark. + scored: false + + - id: 4.6.4 + text: "The default namespace should not be used (Automated)" + audit: | + output=$(kubectl get all -n default --no-headers 2>/dev/null | grep -v '^service\s\+kubernetes\s' || true) + if [ -z "$output" ]; then echo "DEFAULT_NAMESPACE_UNUSED"; else echo "DEFAULT_NAMESPACE_IN_USE"; fi + tests: + test_items: + - flag: "DEFAULT_NAMESPACE_UNUSED" + set: true + compare: + op: eq + value: "DEFAULT_NAMESPACE_UNUSED" + remediation: | + Ensure that namespaces are created to allow for appropriate segregation of Kubernetes + resources and that all new resources are created in a specific namespace. + scored: true diff --git a/cmd/util.go b/cmd/util.go index 092b17ff8..10afdf3a9 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -576,7 +576,7 @@ func gkeBenchmark(version string) string { case "1.28", "1.29", "1.30": return "gke-1.6.0" case "1.31", "1.32", "1.33", "1.34": - return "gke-1.8.0" + return "gke-1.9.0" default: return "gke-1.2.0" } @@ -606,7 +606,7 @@ func k3sBenchmark(version string) string { case "1.25", "1.26", "1.27": return "k3s-cis-1.7" default: - return "" + return "k3s-cis-1.8" } } diff --git a/cmd/util_test.go b/cmd/util_test.go index 9f87865d8..2e179dcd0 100644 --- a/cmd/util_test.go +++ b/cmd/util_test.go @@ -776,6 +776,27 @@ func Test_getPlatformBenchmarkVersion(t *testing.T) { }, want: "k3s-cis-1.7", }, + { + name: "k3s 1.28", + args: args{ + platform: Platform{Name: "k3s", Version: "1.28"}, + }, + want: "k3s-cis-1.8", + }, + { + name: "k3s 1.34", + args: args{ + platform: Platform{Name: "k3s", Version: "1.34"}, + }, + want: "k3s-cis-1.8", + }, + { + name: "k3s future", + args: args{ + platform: Platform{Name: "k3s", Version: "1.40"}, + }, + want: "k3s-cis-1.8", + }, { name: "rancher1", args: args{ diff --git a/docs/architecture.md b/docs/architecture.md index 8622a155f..16d294d17 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -29,6 +29,8 @@ The following table shows the valid targets based on the CIS Benchmark version. | gke-1.0 | master, controlplane, node, etcd, policies, managedservices | | gke-1.2.0 | controlplane, node, policies, managedservices | | gke-1.6.0 | controlplane, node, policies, managedservices | +| gke-1.8.0 | node, policies, managedservices | +| gke-1.9.0 | node, policies, managedservices | | eks-1.0.1 | controlplane, node, policies, managedservices | | eks-1.1.0 | controlplane, node, policies, managedservices | | eks-1.2.0 | controlplane, node, policies, managedservices | diff --git a/docs/platforms.md b/docs/platforms.md index 7cf8d1002..3cd2491ff 100644 --- a/docs/platforms.md +++ b/docs/platforms.md @@ -24,6 +24,8 @@ Other benchmarks are defined by hardening guides. | CIS | [GKE 1.0.0](https://workbench.cisecurity.org/benchmarks/4536) | gke-1.0 | GKE | | CIS | [GKE 1.2.0](https://workbench.cisecurity.org/benchmarks/7534) | gke-1.2.0 | GKE | | CIS | [GKE 1.6.0](https://workbench.cisecurity.org/benchmarks/16093) | gke-1.6.0 | GKE | +| CIS | [GKE 1.8.0](https://workbench.cisecurity.org/benchmarks/20260) | gke-1.8.0 | GKE | +| CIS | [GKE 1.9.0](https://workbench.cisecurity.org/benchmarks/24112) | gke-1.9.0 | GKE | | CIS | [EKS 1.0.1](https://workbench.cisecurity.org/benchmarks/6041) | eks-1.0.1 | EKS | | CIS | [EKS 1.1.0](https://workbench.cisecurity.org/benchmarks/6248) | eks-1.1.0 | EKS | | CIS | [EKS 1.2.0](https://workbench.cisecurity.org/benchmarks/9681) | eks-1.2.0 | EKS | diff --git a/go.mod b/go.mod index 56406aa9b..2dfd627a7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/aquasecurity/kube-bench -go 1.25.0 +go 1.26.2 require ( github.com/aws/aws-sdk-go-v2 v1.41.5 @@ -49,7 +49,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/pgx/v5 v5.9.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -73,7 +73,6 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.18.0 // indirect diff --git a/go.sum b/go.sum index 246852ef1..742cee475 100644 --- a/go.sum +++ b/go.sum @@ -92,8 +92,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -191,8 +191,6 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/job.yaml b/job.yaml index 7a968f229..5e3fc0ab5 100644 --- a/job.yaml +++ b/job.yaml @@ -11,7 +11,7 @@ spec: spec: containers: - command: ["kube-bench"] - image: docker.io/aquasec/kube-bench:v0.15.0 + image: docker.io/aquasec/kube-bench:v0.15.1 name: kube-bench volumeMounts: - name: var-lib-cni diff --git a/makefile b/makefile index 3acb8c333..9bc644750 100644 --- a/makefile +++ b/makefile @@ -11,8 +11,11 @@ uname := $(shell uname -s) BUILDX_PLATFORM ?= linux/amd64,linux/arm64,linux/arm,linux/ppc64le,linux/s390x DOCKER_ORGS ?= aquasec public.ecr.aws/aquasecurity GOARCH ?= $@ +# Kubernetes stable RPM repo minor for UBI images (pkgs.k8s.io .../v${K8S_PKGS_VERSION}/rpm/). Override: make build-docker-ubi K8S_PKGS_VERSION=1.33 +K8S_PKGS_VERSION ?= 1.34 + KUBECTL_VERSION ?= 1.36.0-alpha.1 -ARCH ?= $(shell go env GOARCH) +ARCH ?= $(shell go env GOARCH 2>/dev/null || echo amd64) ifneq ($(findstring Microsoft,$(shell uname -r)),) BUILD_OS := windows @@ -33,7 +36,10 @@ docker: set -xe; \ for org in $(DOCKER_ORGS); do \ docker buildx build --tag $${org}/kube-bench:${VERSION} \ - --platform $(BUILDX_PLATFORM) --push . ; \ + --platform $(BUILDX_PLATFORM) --push . \ + --build-arg KUBEBENCH_VERSION=$(KUBEBENCH_VERSION) \ + --build-arg KUBECTL_VERSION=$(KUBECTL_VERSION) \ + --build-arg K8S_PKGS_VERSION=$(K8S_PKGS_VERSION) ; \ done build: $(BINARY) @@ -47,19 +53,20 @@ build-fips: # builds the current dev docker version build-docker: docker build --build-arg BUILD_DATE=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") \ - --build-arg VCS_REF=$(VERSION) \ - --build-arg KUBEBENCH_VERSION=$(KUBEBENCH_VERSION) \ - --build-arg KUBECTL_VERSION=$(KUBECTL_VERSION) \ + --build-arg VCS_REF=$(VERSION) \ + --build-arg KUBEBENCH_VERSION=$(KUBEBENCH_VERSION) \ + --build-arg KUBECTL_VERSION=$(KUBECTL_VERSION) \ + --build-arg K8S_PKGS_VERSION=$(K8S_PKGS_VERSION) \ --build-arg TARGETARCH=$(ARCH) \ - -t $(IMAGE_NAME) . + -t $(IMAGE_NAME) . build-docker-ubi: docker build -f Dockerfile.ubi --build-arg BUILD_DATE=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") \ --build-arg VCS_REF=$(VERSION) \ - --build-arg KUBEBENCH_VERSION=$(KUBEBENCH_VERSION) \ - --build-arg KUBECTL_VERSION=$(KUBECTL_VERSION) \ + --build-arg KUBEBENCH_VERSION=$(KUBEBENCH_VERSION) \ + --build-arg K8S_PKGS_VERSION=$(K8S_PKGS_VERSION) \ --build-arg TARGETARCH=$(ARCH) \ - -t $(IMAGE_NAME_UBI) . + -t $(IMAGE_NAME_UBI) . # unit tests tests: