diff --git a/functions/functions_lib.sh b/functions/functions_lib.sh new file mode 100644 index 0000000..1dec4c5 --- /dev/null +++ b/functions/functions_lib.sh @@ -0,0 +1,392 @@ +#!/bin/sh + +host_configuration() { + check_1 + check_1_1 + check_1_1_1 + check_1_1_2 + check_1_2 + check_1_2_1 + check_1_2_2 + check_1_2_3 + check_1_2_4 + check_1_2_5 + check_1_2_6 + check_1_2_7 + check_1_2_8 + check_1_2_9 + check_1_2_10 + check_1_2_11 + check_1_2_12 + check_1_end +} + +host_configuration_level1() { + check_1 + check_1_1 + check_1_1_1 + check_1_1_2 + check_1_2 + check_1_2_1 + check_1_2_2 + check_1_2_3 + check_1_2_5 + check_1_2_6 + check_1_2_7 + check_1_2_8 + check_1_2_9 + check_1_2_10 + check_1_2_11 + check_1_2_12 + check_1_end +} + +host_general_configuration() { + check_1 + check_1_1 + check_1_1_1 + check_1_1_2 + check_1_end +} + +linux_hosts_specific_configuration() { + check_1 + check_1_2 + check_1_2_1 + check_1_2_2 + check_1_2_3 + check_1_2_4 + check_1_2_5 + check_1_2_6 + check_1_2_7 + check_1_2_8 + check_1_2_9 + check_1_2_10 + check_1_2_11 + check_1_2_12 + check_1_end +} + +docker_daemon_configuration() { + check_2 + check_2_1 + check_2_2 + check_2_3 + check_2_4 + check_2_5 + check_2_6 + check_2_7 + check_2_8 + check_2_9 + check_2_10 + check_2_11 + check_2_12 + check_2_13 + check_2_14 + check_2_15 + check_2_16 + check_2_17 + check_2_end +} + +docker_daemon_configuration_level1() { + check_2 + check_2_1 + check_2_2 + check_2_3 + check_2_4 + check_2_5 + check_2_6 + check_2_7 + check_2_13 + check_2_14 + check_2_16 + check_2_17 + check_2_end +} + +docker_daemon_files() { + check_3 + check_3_1 + check_3_2 + check_3_3 + check_3_4 + check_3_5 + check_3_6 + check_3_7 + check_3_8 + check_3_9 + check_3_10 + check_3_11 + check_3_12 + check_3_13 + check_3_14 + check_3_15 + check_3_16 + check_3_17 + check_3_18 + check_3_19 + check_3_20 + check_3_21 + check_3_22 + check_3_end +} + +docker_daemon_files_level1() { + check_3 + check_3_1 + check_3_2 + check_3_3 + check_3_4 + check_3_5 + check_3_6 + check_3_7 + check_3_8 + check_3_9 + check_3_10 + check_3_11 + check_3_12 + check_3_13 + check_3_14 + check_3_15 + check_3_16 + check_3_17 + check_3_18 + check_3_19 + check_3_20 + check_3_21 + check_3_22 + check_3_end +} + +container_images() { + check_4 + check_4_1 + check_4_2 + check_4_3 + check_4_4 + check_4_5 + check_4_6 + check_4_7 + check_4_8 + check_4_9 + check_4_10 + check_4_11 + check_4_end +} + +container_images_level1() { + check_4 + check_4_1 + check_4_2 + check_4_3 + check_4_4 + check_4_6 + check_4_7 + check_4_9 + check_4_10 + check_4_end +} + +container_runtime() { + check_5 + check_running_containers + check_5_1 + check_5_2 + check_5_3 + check_5_4 + check_5_5 + check_5_6 + check_5_7 + check_5_8 + check_5_9 + check_5_10 + check_5_11 + check_5_12 + check_5_13 + check_5_14 + check_5_15 + check_5_16 + check_5_17 + check_5_18 + check_5_19 + check_5_20 + check_5_21 + check_5_22 + check_5_23 + check_5_24 + check_5_25 + check_5_26 + check_5_27 + check_5_28 + check_5_29 + check_5_30 + check_5_31 + check_5_end +} + +container_runtime_level1() { + check_5 + check_running_containers + check_5_1 + check_5_3 + check_5_4 + check_5_5 + check_5_6 + check_5_7 + check_5_8 + check_5_9 + check_5_10 + check_5_11 + check_5_12 + check_5_13 + check_5_14 + check_5_15 + check_5_16 + check_5_17 + check_5_18 + check_5_19 + check_5_20 + check_5_21 + check_5_24 + check_5_25 + check_5_26 + check_5_27 + check_5_28 + check_5_30 + check_5_31 + check_5_end +} + +docker_security_operations() { + check_6 + check_6_1 + check_6_2 + check_6_end +} + +docker_security_operations_level1() { + check_6 + check_6_1 + check_6_2 + check_6_end +} + +docker_swarm_configuration() { + check_7 + check_7_1 + check_7_2 + check_7_3 + check_7_4 + check_7_5 + check_7_6 + check_7_7 + check_7_8 + check_7_9 + check_7_10 + check_7_end +} + +docker_swarm_configuration_level1() { + check_7 + check_7_1 + check_7_2 + check_7_3 + check_7_4 + check_7_7 + check_7_end +} + +docker_enterprise_configuration() { + check_8 + check_product_license + check_8_1 + check_8_1_1 + check_8_1_2 + check_8_1_3 + check_8_1_4 + check_8_1_5 + check_8_1_6 + check_8_1_7 + check_8_2 + check_8_2_1 + check_8_end +} + +docker_enterprise_configuration_level1() { + check_8 + check_product_license + check_8_1 + check_8_1_1 + check_8_1_2 + check_8_1_3 + check_8_1_4 + check_8_1_5 + check_8_1_6 + check_8_1_7 + check_8_2 + check_8_2_1 + check_8_end +} + +universal_control_plane_configuration() { + check_8 + check_8_1 + check_8_1_1 + check_8_1_2 + check_8_1_3 + check_8_1_4 + check_8_1_5 + check_8_1_6 + check_8_1_7 + check_8_end +} + +docker_trusted_registry_configuration() { + check_8 + check_8_2 + check_8_2_1 + check_8_end +} + +community_checks() { + check_c + check_c_1 + check_c_1_1 + check_c_2 + check_c_end +} + +# CIS +cis() { + host_configuration + docker_daemon_configuration + docker_daemon_files + container_images + container_runtime + docker_security_operations + docker_swarm_configuration + docker_enterprise_configuration +} + +cis_level1() { + host_configuration_level1 + docker_daemon_configuration_level1 + docker_daemon_files_level1 + container_images_level1 + container_runtime_level1 + docker_security_operations_level1 + docker_swarm_configuration_level1 + docker_enterprise_configuration_level1 +} + +# Community contributed +community() { + community_checks +} + +# All +all() { + cis + community +} diff --git a/functions/helper_lib.sh b/functions/helper_lib.sh new file mode 100644 index 0000000..e24a14c --- /dev/null +++ b/functions/helper_lib.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +# Returns the absolute path of a given string +abspath () { case "$1" in /*)printf "%s\n" "$1";; *)printf "%s\n" "$PWD/$1";; esac; } + +# Audit rules default path +auditrules="/etc/audit/audit.rules" + +# Compares versions of software of the format X.Y.Z +do_version_check() { + [ "$1" = "$2" ] && return 10 + + ver1front=$(printf "%s" "$1" | cut -d "." -f -1) + ver1back=$(printf "%s" "$1" | cut -d "." -f 2-) + ver2front=$(printf "%s" "$2" | cut -d "." -f -1) + ver2back=$(printf "%s" "$2" | cut -d "." -f 2-) + + if [ "$ver1front" != "$1" ] || [ "$ver2front" != "$2" ]; then + [ "$ver1front" -gt "$ver2front" ] && return 11 + [ "$ver1front" -lt "$ver2front" ] && return 9 + + [ "$ver1front" = "$1" ] || [ -z "$ver1back" ] && ver1back=0 + [ "$ver2front" = "$2" ] || [ -z "$ver2back" ] && ver2back=0 + do_version_check "$ver1back" "$ver2back" + return $? + else + [ "$1" -gt "$2" ] && return 11 || return 9 + fi +} + +# Extracts commandline args from the newest running processes named like the first parameter +get_command_line_args() { + PROC="$1" + + for PID in $(pgrep -f -n "$PROC"); do + tr "\0" " " < /proc/"$PID"/cmdline + done +} + +# Extract the cumulative command line arguments for the docker daemon +# +# If specified multiple times, all matches are returned. +# Accounts for long and short variants, call with short option. +# Does not account for option defaults or implicit options. +get_docker_cumulative_command_line_args() { + OPTION="$1" + + if ! get_command_line_args "docker daemon" >/dev/null 2>&1 ; then + line_arg="docker daemon" + else + line_arg="dockerd" + fi + + get_command_line_args "$line_arg" | + # normalize known long options to their short versions + sed \ + -e 's/\-\-debug/-D/g' \ + -e 's/\-\-host/-H/g' \ + -e 's/\-\-log-level/-l/g' \ + -e 's/\-\-version/-v/g' \ + | + # normalize parameters separated by space(s) to -O=VALUE + sed \ + -e 's/\-\([DHlv]\)[= ]\([^- ][^ ]\)/-\1=\2/g' \ + | + # get the last interesting option + tr ' ' "\n" | + grep "^${OPTION}" | + # normalize quoting of values + sed \ + -e 's/"//g' \ + -e "s/'//g" +} + +# Extract the effective command line arguments for the docker daemon +# +# Accounts for multiple specifications, takes the last option. +# Accounts for long and short variants, call with short option +# Does not account for option default or implicit options. +get_docker_effective_command_line_args() { + OPTION="$1" + get_docker_cumulative_command_line_args "$OPTION" | tail -n1 +} + +get_docker_configuration_file() { + FILE="$(get_docker_effective_command_line_args '--config-file' | \ + sed 's/.*=//g')" + + if [ -f "$FILE" ]; then + CONFIG_FILE="$FILE" + elif [ -f '/etc/docker/daemon.json' ]; then + CONFIG_FILE='/etc/docker/daemon.json' + else + CONFIG_FILE='/dev/null' + fi +} + +get_docker_configuration_file_args() { + OPTION="$1" + + get_docker_configuration_file + + grep "$OPTION" "$CONFIG_FILE" | sed 's/.*://g' | tr -d '" ', +} + +get_service_file() { + SERVICE="$1" + + if [ -f "/etc/systemd/system/$SERVICE" ]; then + echo "/etc/systemd/system/$SERVICE" + elif [ -f "/lib/systemd/system/$SERVICE" ]; then + echo "/lib/systemd/system/$SERVICE" + elif systemctl show -p FragmentPath "$SERVICE" 2> /dev/null 1>&2; then + systemctl show -p FragmentPath "$SERVICE" | sed 's/.*=//' + else + echo "/usr/lib/systemd/system/$SERVICE" + fi +} + +yell_info() { +yell "# -------------------------------------------------------------------------------------------- +# Docker Bench for Security v$version +# +# Docker, Inc. (c) 2015-$(date +"%Y") +# +# Checks for dozens of common best-practices around deploying Docker containers in production. +# Inspired by the CIS Docker Benchmark v1.2.0. +# --------------------------------------------------------------------------------------------" +} diff --git a/functions/output_lib.sh b/functions/output_lib.sh new file mode 100644 index 0000000..f893cb5 --- /dev/null +++ b/functions/output_lib.sh @@ -0,0 +1,189 @@ +#!/bin/sh + +if [ -n "$nocolor" ] && [ "$nocolor" = "nocolor" ]; then + bldred='' + bldgrn='' + bldblu='' + bldylw='' + txtrst='' +else + bldred='\033[1;31m' # Bold Red + bldgrn='\033[1;32m' # Bold Green + bldblu='\033[1;34m' # Bold Blue + bldylw='\033[1;33m' # Bold Yellow + txtrst='\033[0m' +fi + +logit () { + printf "%b\n" "$1" | tee -a "$logger" +} + +info () { + local infoCountCheck + while getopts c args + do + case $args in + c) infoCountCheck="true" ;; + *) exit 1 ;; + esac + done + if [ "$infoCountCheck" = "true" ]; then + printf "%b\n" "${bldblu}[INFO]${txtrst} $2" | tee -a "$logger" + totalChecks=$((totalChecks + 1)) + else + printf "%b\n" "${bldblu}[INFO]${txtrst} $1" | tee -a "$logger" + fi +} + +pass () { + local passScored + while getopts sc args + do + case $args in + s) passScored="true" ;; + c) passCountCheck="true" ;; + *) exit 1 ;; + esac + done + if [ "$passScored" = "true" ]; then + printf "%b\n" "${bldgrn}[PASS]${txtrst} $2" | tee -a "$logger" + totalChecks=$((totalChecks + 1)) + currentScore=$((currentScore + 1)) + elif [ "$passCountCheck" = "true" ]; then + printf "%b\n" "${bldgrn}[PASS]${txtrst} $2" | tee -a "$logger" + totalChecks=$((totalChecks + 1)) + else + printf "%b\n" "${bldgrn}[PASS]${txtrst} $1" | tee -a "$logger" + fi +} + +warn () { + local warnScored + while getopts s args + do + case $args in + s) warnScored="true" ;; + *) exit 1 ;; + esac + done + if [ "$warnScored" = "true" ]; then + printf "%b\n" "${bldred}[WARN]${txtrst} $2" | tee -a "$logger" + totalChecks=$((totalChecks + 1)) + currentScore=$((currentScore - 1)) + else + printf "%b\n" "${bldred}[WARN]${txtrst} $1" | tee -a "$logger" + fi +} + +note () { + local noteCountCheck + while getopts c args + do + case $args in + c) noteCountCheck="true" ;; + *) exit 1 ;; + esac + done + if [ "$noteCountCheck" = "true" ]; then + printf "%b\n" "${bldylw}[NOTE]${txtrst} $2" | tee -a "$logger" + totalChecks=$((totalChecks + 1)) + else + printf "%b\n" "${bldylw}[NOTE]${txtrst} $1" | tee -a "$logger" + fi +} + +yell () { + printf "%b\n" "${bldylw}$1${txtrst}\n" +} + +appendjson () { + if [ -s "$logger.json" ]; then + tail -n 1 "$logger.json" | wc -c | xargs -I {} truncate "$logger.json" -s -{} + printf "},\n" | tee -a "$logger.json" 2>/dev/null 1>&2 + else + printf "[" | tee -a "$logger.json" 2>/dev/null 1>&2 + fi +} + +beginjson () { + printf "{\n \"dockerbenchsecurity\": \"%s\",\n \"start\": %s,\n \"tests\": [" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 +} + +endjson (){ + printf "\n ], \"checks\": %s, \"score\": %s, \"end\": %s\n}]" "$1" "$2" "$3" | tee -a "$logger.json" 2>/dev/null 1>&2 +} + +logjson (){ + printf "\n \"%s\": \"%s\"," "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 +} + +SSEP= +SEP= +startsectionjson() { + printf "%s\n {\"id\": \"%s\", \"desc\": \"%s\", \"results\": [" "$SSEP" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 + SEP= + SSEP="," +} + +endsectionjson() { + printf "\n ]}" | tee -a "$logger.json" 2>/dev/null 1>&2 +} + +starttestjson() { + printf "%s\n {\"id\": \"%s\", \"desc\": \"%s\", " "$SEP" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 + SEP="," +} + +logcheckresult() { + # Log to JSON + if [ $# -eq 1 ]; then + printf "\"result\": \"%s\"" "$1" | tee -a "$logger.json" 2>/dev/null 1>&2 + elif [ $# -eq 2 ]; then + # Result also contains details + printf "\"result\": \"%s\", \"details\": \"%s\"" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 + else + # Result also includes details and a list of items. Add that directly to details and to an array property "items" + # Also limit the number of items to $limit, if $limit is non-zero + if [ $limit != 0 ]; then + truncItems="" + ITEM_COUNT=0 + for item in $3; do + truncItems="$truncItems $item" + ITEM_COUNT=$((ITEM_COUNT + 1)); + if [ "$ITEM_COUNT" == "$limit" ]; then + truncItems="$truncItems (truncated)" + break; + fi + done + else + truncItems=$3 + fi + itemsJson=$(printf "["; ISEP=""; ITEMCOUNT=0; for item in $truncItems; do printf "%s\"%s\"" "$ISEP" "$item"; ISEP=","; done; printf "]") + printf "\"result\": \"%s\", \"details\": \"%s: %s\", \"items\": %s" "$1" "$2" "$truncItems" "$itemsJson" | tee -a "$logger.json" 2>/dev/null 1>&2 + fi + + # Log remediation measure to JSON + if [ -n "$remediation" ] && [ "$1" != "PASS" ]; then + printf ", \"remediation\": \"%s\"" "$remediation" | tee -a "$logger.json" 2>/dev/null 1>&2 + if [ -n "$remediationImpact" ]; then + printf ", \"remediation-impact\": \"%s\"" "$remediationImpact" | tee -a "$logger.json" 2>/dev/null 1>&2 + fi + fi + printf "}" | tee -a "$logger.json" 2>/dev/null 1>&2 + + # Save remediation measure for print log to stdout + if [ -n "$remediation" ] && [ "$1" != "PASS" ]; then + if [ -n "${checkHeader}" ]; then + if [ -n "${addSpaceHeader}" ]; then + globalRemediation="${globalRemediation}\n" + fi + globalRemediation="${globalRemediation}\n${bldblu}[INFO]${txtrst} ${checkHeader}" + checkHeader="" + addSpaceHeader="1" + fi + globalRemediation="${globalRemediation}\n${bldblu}[INFO]${txtrst} ${id} - ${remediation}" + if [ -n "${remediationImpact}" ]; then + globalRemediation="${globalRemediation} Impact: ${remediationImpact}" + fi + fi +}