Kubernetes Node Scanner
Section 4.1 of the CIS Benchmark (version 1.6.0) for Kubernetes includes a set of security checks for Kubernetes clusters that requires access information on the file system of Kubernetes cluster nodes. To meet this requirement, the deployment of a thin Rapid7 Node Scanner as a daemonset is necessary. This scanner is particularly important for manually managed Kubernetes deployments that might be exposed to deployment mistakes more than Kubernetes clusters managed by a cloud provider. The guide below will walk you through the installation process for the scanner, along with the required configurations and steps.
Prerequisites
This scanner is relevant for standard Kubernetes clusters and has been tested on AWS' Elastic Kubernetes Service (EKS), GCP's Google Kubernetes Engine (GKE), and Azure Kubernetes Service (AKS)
- It is not, however, for clusters with fully-managed infrastructure, such as AWS' EKS Fargate and GKE Autopilot
A Kubernetes cluster that was configured according to the Kubernetes Local Scanner or Kubernetes Remote Scanner documentation
Clusters scanned by local Kubernetes Scanners and are standard (see the first bullet) will require this config to be added to the helm command (default is false):
bash1--set Config.HasNodeFsAccess=trueSave the Node Scanner YAML code as a file named
r7-node-scan.yaml
and ensure it's available to the cluster you're attempting to configure
Node Scanner YAML
Deployment Limitations?
Take into consideration any cluster deployment limitations, such as node affinity, that might require changes in the Node Scanner YAML in order to allow the daemonset pods run on all nodes.
yaml
1# ****** IMPORTANT. read this ************2# First prepare a secret in the SAME NAMESPACE as the daemonset will be deployed3# kubectl -n same-namespace-of-daemonset create secret generic r7-node-scanner-key --from-literal=encKey=$(openssl rand -hex 32)4# If the secret was updated make sure to restart the pods56apiVersion: apps/v17kind: DaemonSet8metadata:9name: r7-node-scanner10spec:11selector:12matchLabels:13app: r7-node-scanner14template:15metadata:16labels:17app: r7-node-scanner18spec:19tolerations:20- key: node-role.kubernetes.io/control-plane21operator: Exists22effect: NoSchedule23- key: node-role.kubernetes.io/master24operator: Exists25effect: NoSchedule26### runAsUser is only possible if K8S config files are visible by that user ##27containers:28# Do not modify this container's name. r7-k8s-scanner will be looking for it by name.29- name: r7-node-scanner30image: alpine31command: ["/bin/sh", "-c"]32args:33- |34apk add openssl3536cat > /workdir/r7-node-scanner.sh << "EOF"37#!/bin/sh3839# POSIX_LINE_MAX represent the minimum value for line width by Posix standard40POSIX_LINE_MAX=204841OUTPUT_VERSION=v0.0.14243WORK_DIR=/workdir44RESULTS_FILE=scan.json-lines45FILE_NAMES=file_names.list4647ENC_KEY=$(cat /mnt/enc-key/encKey)48ENC_IV=$(openssl rand -hex 16)49if [[ "$ENC_KEY" == "" ]]; then50echo "ENC_KEY is empty"51exit 152fi53if [[ "$ENC_IV" == "" ]]; then54echo "ENC_IV is empty"55exit 156fi5758while true; do59START_TIME=$(date +%s)6061mkdir -p $WORK_DIR62cd $WORK_DIR63rm -f $RESULTS_FILE64rm -f $FILE_NAMES6566HOST_FS_ROOT=/mnt/host-fs6768chroot $HOST_FS_ROOT find /etc /var -maxdepth 7 -type f | grep kube | grep -v "fluent\|/var/log\|lib/kubelet/pods\|/var/lib/cni" >> $FILE_NAMES6970for f in $(cat $FILE_NAMES); do71# save ps containing the file name. stream directly to file in order to avoid too long command line72printf "{\"psB64\": \"" >> $RESULTS_FILE73chroot $HOST_FS_ROOT ps ax | grep -v grep | grep $f | base64 | tr -d '\n' >> $RESULTS_FILE74chroot $HOST_FS_ROOT [ -f $f ] && \75chroot $HOST_FS_ROOT stat -c "\", \"file\": \"%n\", \"perms\": %a, \"gid\": %g, \"group\": \"%G\", \"uid\": %u, \"user\": \"%U\" }" "$f" >> $RESULTS_FILE76done7778tar czf $RESULTS_FILE.tar.gz $RESULTS_FILE79FILES_LINE_COUNT=$(wc -l $RESULTS_FILE | awk '{print $1}')80rm $RESULTS_FILE8182TGZ_SIZE=$(ls -l $RESULTS_FILE.tar.gz | awk {'print $5'})8384cat $RESULTS_FILE.tar.gz | \85openssl enc -e -aes-256-cbc -K $ENC_KEY -iv $ENC_IV | \86base64 -w $POSIX_LINE_MAX > $RESULTS_FILE.tgz.base648788rm $RESULTS_FILE.tar.gz89BASE64_LINES_COUNT=$(wc -l $RESULTS_FILE.tgz.base64 | awk '{print $1}')9091cat $RESULTS_FILE.tgz.base6492rm $RESULTS_FILE.tgz.base649394END_TIME=$(date +%s)95TOTAL_EXEC_SEC=$(( END_TIME - START_TIME ))9697SUMMARY=$(echo "{98'ver': '$OUTPUT_VERSION',99'totalSec': $TOTAL_EXEC_SEC,100'filesLineCount': $FILES_LINE_COUNT,101'tgzSize': $TGZ_SIZE,102'ivHex': '$ENC_IV',103'base64LineCount': $BASE64_LINES_COUNT,104'nodeName': '$NODE_NAME',105'podName': '$POD_NAME'106}" | tr "'" '"')107108echo $SUMMARY109110touch $LAST_SUCCESS_FLAG_FILE111sleep $EXECUTION_INTERVAL_SECONDS112done113114EOF115116chmod 550 /workdir/r7-node-scanner.sh117# run with exec to keep commandline clean (without exec, the entire script preparation will be shown on "ps ax")118exec /workdir/r7-node-scanner.sh119env:120- name: NODE_NAME121valueFrom:122fieldRef:123fieldPath: spec.nodeName124- name: POD_NAME125valueFrom:126fieldRef:127fieldPath: metadata.name128- name: LAST_SUCCESS_FLAG_FILE129value: "/workdir/last-success-flag-file"130- name: EXECUTION_INTERVAL_SECONDS131value: "360"132volumeMounts:133- name: host-fs134mountPath: /mnt/host-fs135readOnly: true136- name: workdir137mountPath: /workdir138- name: r7-node-scanner-key139mountPath: /mnt/enc-key140volumes:141- name: host-fs142hostPath:143path: /144- name: workdir145emptyDir: {}146- name: r7-node-scanner-key147secret:148secretName: r7-node-scanner-key149
Installation
Cluster Administrator Required
Due to its required permissions, the installation needs to be performed locally on each cluster by the cluster administrator.
Create a namespace of your choice for the Rapid7 Node Scanner. For this guide, we used
r7-node-scan
as an example:bash1kubectl create namespace r7-node-scanCreate a secret to encrypt the scanner data. This secret will be used by the cluster's configured scanner (local or remote) to decrypt and consume the scan results. Anyone with access to this secret will be able to read the scan results. Use the following command to create the secret and ensure that the
openssl
command is installed:bash1kubectl -n r7-node-scan create secret generic r7-node-scanner-key --from-literal=encKey=$(openssl rand -hex 32)After ensuring the Node Scanner YAML file is available to the cluster, deploy the Rapid7 Node Scanner by applying the file using the following command:
Isolated Environment
The Node Scanner will run apk add openssl
on startup, which requires access to the internet. In isolated environments, you can replace the image with your own Alpine image that has pre-installed openssl
.
bash
1kubectl -n r7-node-scan apply -f r7-node-scan.yaml
Related Insights
To compliment the Rapid7 Kubernetes Node Scanner, there are several Insights available to check compliance throughout your cloud environments. Once the node scanner is up-and-running, you can use the Ensure that r7-node-scanner is deployed
Insight (introduced in InsightCloudSec version 23.7.25) to see which clusters have (or do not have) the Node Scanner deployed. The CIS - Kubernetes 1.6.0 Compliance Pack has also been updated to feature the following Insights (which require the Node Scanner to be deployed):
Ensure that the kubelet service file permissions are set to 644 or more restrictive
Ensure that the kubelet service file ownership is set to root:root
If proxy kubeconfig file exists ensure permissions are set to 644 or more restrictive
If proxy kubeconfig file exists ensure ownership is set to root:root
Ensure that the --kubeconfig kubelet.conf file permissions are set to 644 or more restrictive
Ensure that the --kubeconfig kubelet.conf file ownership is set to root:root
Ensure that the certificate authorities file permissions are set to 644 or more restrictive
Ensure that the client certificate authorities file ownership is set to root:root
Ensure that the kubelet --config configuration file has permissions set to 644 or more restrictive
Ensure that the kubelet --config configuration file ownership is set to root:root