#! /bin/bash
# script to run audit while populating local host data
# 13th Sept 2021 - Initial
# 9th Nov 2021 - Added root user check - more posix compliant for multiple OS types
# 10 Dec 2021 - Enhanced so more linux OS agnostic, less input required
#             - added vars options for bespoke vars file
#             - Ability to run as script from remediation role increased consistency
# 17 Dec 2021 - Added system_type variable - default Server will change to workstations with -w switch
# 02 Mar 2022 - Updated benchmark variable naming
# 06 Apr 2022 - Added format option in output inline with goss options e.g. json documentation this is for fault finding
# 03 May 2022 - update for audit variables improvement added by @pavloos - https://github.com/ansible-lockdown/RHEL8-CIS-Audit/pull/29
# 10 Jun 2022 - added format output for different type - supports json,documentation or rspecish
# 04 Oct 2022 - Changed default content location to /opt
# 14 Sep 2023 - Tidyup of code,
#               linting (thanks to @cf-sewe)
#               Oracle included by default if RHEL family
#               benchmark vars moved
# December 2023 Added goss version and testing
# April 2024    Updating of OS discovery to work for all supported OSs
# August 2024   Improve failure capture
# January 2025  Added Suse OS discovery

# Variables in upper case tend to be able to be adjusted
# lower case variables are discovered or built from other variables

# Goss benchmark variables (these should not need changing unless new release)
BENCHMARK=CIS # Benchmark Name aligns to the audit
BENCHMARK_VER=1.1.0
BENCHMARK_OS=DEBIAN

# Goss host Variables
AUDIT_BIN="${AUDIT_BIN:-/usr/local/bin/goss}"  # location of the goss executable
AUDIT_BIN_MIN_VER="0.4.4"
AUDIT_FILE="${AUDIT_FILE:-goss.yml}"  # the default goss file used by the audit provided by the audit configuration
AUDIT_CONTENT_LOCATION="${AUDIT_CONTENT_LOCATION:-/opt}"  # Location of the audit configuration file as available to the OS

# help output
Help()
{
  # Display Help
  echo "Script to run the goss audit"
  echo
  echo "Syntax: $0 [-f|-g|-o|-v|-w|-h]"
  echo "options:"
  echo "-f     optional - change the format output (default value = json)"
  echo "-g     optional - Add a group that the server should be grouped with (default value = ungrouped)"
  echo "-o     optional - file to output audit data"
  echo "-v     optional - relative path to thevars file to load (default e.g. $AUDIT_CONTENT_LOCATION/RHEL7-$BENCHMARK/vars/$BENCHMARK.yml)"
  echo "-w     optional - Sets the system_type to workstation (Default - Server)"
  echo "-h     Print this Help."
  echo
}

# Default vars that can be set
host_system_type=Server

## option statement
while getopts f:g:o:v::wh option; do
  case "${option}" in
    f ) FORMAT=${OPTARG} ;;
    g ) GROUP=${OPTARG} ;;
    o ) OUTFILE=${OPTARG} ;;
    v ) VARS_PATH=${OPTARG} ;;
    w ) host_system_type=Workstation ;;
    h ) # display Help
      Help
      exit;;
    ? ) # Invalid option
      echo "Invalid option: -${OPTARG}."
      Help
      exit;;
  esac
done

#### Pre-Checks

# check access need to run as root or privileges due to some configuration access
if [ "$(/usr/bin/id -u)" -ne 0 ]; then
  echo "Script need to run with root privileges"
  exit 1
fi

#### Main Script ####

# Discover OS version aligning with audit
# Define os_vendor variable
if [ "$(uname -a | grep -c amzn)" -ge 1 ]; then
    os_vendor="AMAZON"
elif [ "$(grep -Ec "rhel|oracle" /etc/os-release)" != 0 ]; then
  os_vendor="RHEL"
else
  os_vendor="$(hostnamectl | grep Oper | cut -d : -f2 | awk '{print toupper($1)}')"
  if [ "${os_vendor}" = "OPENSUSE" ]; then
   os_vendor="SUSE"
  fi
fi

os_maj_ver="$(grep -w VERSION_ID= /etc/os-release | awk -F\" '{print $2}' | cut -d '.' -f1)"
audit_content_version=$os_vendor$os_maj_ver-$BENCHMARK-Audit
audit_content_dir=$AUDIT_CONTENT_LOCATION/$audit_content_version
audit_vars=vars/${BENCHMARK}.yml

# Set variable for format output
if [ -z "$FORMAT" ]; then
  export format="json"
else
  export format=$FORMAT
fi

# Set variable for autogroup
if [ -z "$GROUP" ]; then
  export host_auto_group="ungrouped"
else
  export host_auto_group=$GROUP
fi

# set default variable for varfile_path
if [ -z "$VARS_PATH" ]; then
  export varfile_path=$audit_content_dir/$audit_vars
else
  # Check -v exists fail if not
  if [ -f "$VARS_PATH" ]; then
    export varfile_path=$VARS_PATH
  else
    echo "passed option '-v' $VARS_PATH does not exist"
    exit 1
  fi
fi

## System variables captured for metadata

host_machine_uuid="$(if [ -f /sys/class/dmi/id/product_uuid ]; then cat /sys/class/dmi/id/product_uuid; else dmidecode -s system-uuid; fi)"
host_epoch="$(date +%s)"
host_os_locale="$(date +%Z)"
host_os_name="$(grep "^NAME=" /etc/os-release | cut -d '"' -f2 | sed 's/ //' | cut -d' ' -f1)"
host_os_version="$(grep "^VERSION_ID=" /etc/os-release | cut -d '"' -f2)"
host_os_hostname="$(hostname)"

## Set variable audit_out
if [ -z "$OUTFILE" ]; then
  export audit_out=${AUDIT_CONTENT_LOCATION}/audit_${host_os_hostname}-${BENCHMARK}-${BENCHMARK_OS}_${host_epoch}.$format
else
  export audit_out=${OUTFILE}
fi

## Set the AUDIT json string
audit_json_vars='{"benchmark_type":"'"$BENCHMARK"'","benchmark_os":"'"$BENCHMARK_OS"'","benchmark_version":"'"$BENCHMARK_VER"'","machine_uuid":"'"$host_machine_uuid"'","epoch":"'"$host_epoch"'","os_locale":"'"$host_os_locale"'","os_release":"'"$host_os_version"'","os_distribution":"'"$host_os_name"'","os_hostname":"'"$host_os_hostname"'","auto_group":"'"$host_auto_group"'","system_type":"'"$host_system_type"'"}'

## Run pre checks

echo
echo "## Pre-Checks Start"
echo

export FAILURE=0
if [ -s "${AUDIT_BIN}" ]; then
  echo "OK - Audit binary $AUDIT_BIN is available"
  goss_installed_version="$($AUDIT_BIN -v | awk '{print $NF}' | cut -dv -f2)"
  newer_version=$(echo -e "$goss_installed_version\n$AUDIT_BIN_MIN_VER" | sort -V | tail -n 1)
  if [ "$goss_installed_version" = "$newer_version" ] || [ "$goss_installed_version" = "$AUDIT_BIN_MIN_VER" ]; then
    echo "OK - Goss is installed and version is ok ($goss_installed_version >= $AUDIT_BIN_MIN_VER)"
  else
    echo "WARNING - Goss installed = ${goss_installed_version}, does not met minimum of ${AUDIT_BIN_MIN_VER}"
    export FAILURE=2
  fi
else
  echo "WARNING - The audit binary is not available at $AUDIT_BIN "
  export FAILURE=1
fi

if [ -f "${audit_content_dir}/${AUDIT_FILE}" ]; then
  echo "OK - ${audit_content_dir}/${AUDIT_FILE} is available"
else
  echo "WARNING - the $audit_content_dir/$AUDIT_FILE is not available"; export FAILURE=3
fi

if [ "${FAILURE}" != 0 ]; then
  echo "## Pre-checks failed please see output"
  exit 1
else
  echo
  echo "## Pre-checks Successful"
  echo
fi

# format output types
# json, rspecish = grep -A 4 \"summary\": $audit_out
# tap junit no output as no summary
# documentation = tail -2 $audit_out

# defaults
output_summary="tail -2 $audit_out"
format_output="-f $format"

if [ "$format" = json ]; then
   format_output="-f json -o pretty"
   output_summary='grep -A 4 \"summary\": $audit_out'
elif [ "$format" = junit ] || [ "$format" = tap ]; then
   output_summary=""
fi

## Run commands
echo "#############"
echo "Audit Started"
echo "#############"
echo
$AUDIT_BIN -g "$audit_content_dir/$AUDIT_FILE" --vars "$varfile_path"  --vars-inline "$audit_json_vars" v $format_output > "$audit_out"

# create screen output
if [ "$(grep -c Count: "$audit_out")" -ge 1 ]  || [ "$format" = junit ] || [ "$format" = tap ]; then
  eval $output_summary
  echo "Completed file can be found at $audit_out"
  echo "###############"
  echo "Audit Completed"
  echo "###############"
else
  echo -e "Fail: There were issues when running the audit please investigate $audit_out";
  exit 1
fi
