Originally published February 29, 2020 @ 5:04 pm

Lynis is an excellent security audit tool for Linux and various Unix derivatives. I have a small wrapper script that runs Lynis via a cron job, does a selective diff with the previous run’s output, and sends me an email.

Unfortunately, Lynis does not update itself automatically and this is the sort of application that needs to be up-to-date. I installed mine via a tarball – the most common and recommended way for this utility – and so it does not update when I patch the system.

Here is a, perhaps, unnecessarily convoluted script that checks the version of Lynis and updates it, if required. In a nutshell, all this script does is download the most recent version of Lynis and overwrites the existing version on your machine.

But there are a lot of checks in the background designed to detect a variety of issues. When writing this sort of scripts I try to hard-code as little as possible. Primarily, because the folks maintaining such online resources usually have little regard for automation and can make silly changes to names and URLs without notice.

The script is below for your review. If you’re going to use it, I suggest downloading it from my GitHub repo to avoid any copy-paste weirdness.

#!/bin/bash
#
#                                      |
#                                  ___/"\___
#                          __________/ o \__________
#                            (I) (G) \___/ (O) (R)
#                                   Igor Os
#                           igor@comradegeneral.com
#                                  2020-02-29
# ----------------------------------------------------------------------------
# Update current installation of Lynis. Only use this script if Lynis was
# installed via a tarball. If you have a version installed via RPM or another
# package managements tool, uninstall it first.
# ----------------------------------------------------------------------------

configure() {
  echo "Running script configuration"
  # Specify Lynis executable
  LYNIS=/usr/bin/lynis
  [ -x "${LYNIS}" ] || (echo "Lynis executable ${LYNIS} not found. Exiting..." && exit 1110)

  this_script=$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")

  # Get update info
  echo "Getting update info"
  update_info="$(${LYNIS} update info)"
  [ -z "${update_info}" ] && (echo "Unable to get update info. Exiting..." && exit 1120)

  # Parse update info
  echo "Parsing update info"
  update_status="$(echo "${update_info}" | grep -E '^\s+Status' | awk '{print $NF}')"
  release_date="$(echo "${update_info}" | grep -E '^\s+Release' | awk '{print $NF}')"
  current_version="$(echo "${update_info}" | grep -E '^\s+Version' | awk '{print $NF}')"
  update_location="$(echo "${update_info}" | grep -E '^\s+Update location' | awk '{print $NF}' | sed 's/\/$//g')"
  update_host="$(echo "${update_location}" | grep -oP "(?<=:\/\/).*?(?=\/)")"

  ([ -z "${update_status}" ] || [ -z "${current_version}" ] || \
  [ -z "${release_date}" ] || [ -z "${update_location}" ] || [ -z "${update_host}" ]) && \
  (echo "Unable to parse update info. Exiting..." && exit 1130)

  # Resolve update host
  echo "Resolving update host"
  update_host_ip="$(dig +short "${update_host}" | \
  grep -m1 -oE "([0-9]{1,3}\.){3}([0-9]{1,3})")"
  [ -z "${update_host_ip}" ] && \
  (echo "Unable to resolve ${update_host}. Exiting..." && exit 1135)

  # Verify update host port access
  echo "Verifying update host port access"
  nc -v -i1 -w1 "${update_host_ip}" 443 2>/dev/null 1>&2 || \
  (echo "Unable to access ${update_host_ip}:443. Exiting..." && exit 1137)

  # Verify access to update URL
  echo "Verifying update location availability"
  if [ ! $(curl -s0SfkL "${update_location}" 2>/dev/null | \
           grep -oP "(?<=\<title\>).*?(?=\<\/title\>)" 2>/dev/null | \
           grep -c Lynis) -gt 0 ]
  then
    echo "Unable to reach ${update_location}. Exiting..."
    exit 1140
  fi

  # Determine download URL
  echo "Checking for download URL"
  base_url="$(awk -F'/' '{print $1"/"$2"/"$3}' <<<"${update_location}")"
  path_url="$(curl -s0SfkL "${update_location}" 2>/dev/null | awk 'BEGIN{
  RS="</a>"
  IGNORECASE=1
  }
  {
    for(o=1;o<=NF;o++){
      if ( $o ~ /href/){
        gsub(/.*href=2/,"",$o)
        gsub(/2.*/,"",$o)
        print $(o)
      }
    }
  }' | grep -m1 -iE "(lynis.*downloads)|(downloads.*lynis)")"
  [ -z "${path_url}" ] && (echo "Unable to determine download URL. Exiting..." && exit 1150)
  download_url="${base_url}/${path_url}"

  # Determine latest version download URL
  echo "Checking for the latest version download link"
  version_url="$(curl -s0SfkL "${download_url}" 2>/dev/null | awk 'BEGIN{
  RS="</a>"
  IGNORECASE=1
  }
  {
    for(o=1;o<=NF;o++){
      if ( $o ~ /href/){
        gsub(/.*href=2/,"",$o)
        gsub(/2.*/,"",$o)
        print $(o)
      }
    }
  }' | grep -m1 -iE "lynis.*\.(tar\.gz|tgz)")"
  [ -z "${version_url}" ] && (echo "Unable to determine latest version download link. Exiting..." && exit 1150)
  version_file="$(echo "${version_url}" | awk -F'/' '{print $NF}')"
}

lynis_update() {
  # Check if update is needed
  if [ "${update_status}" == "Outdated" ]
  then
    echo "Update is required."

    # Download the latest version
    echo "Downloading ${version_file}"
    curl -s0SfkL "${version_url}" > ~/"${version_file}"
    [ $(file --mime-type ~/"${version_file}" -F$'\t' | awk -F'\t *' '$2 ~/^application\/x-gzip/ { print $1 }') ] || \
    (echo "Unable to download ${version_url}. Exiting..." && exit 1160)

    # Uncompress the latest version
    echo "Uncompressing ~/${version_file}"
    [ -d ~/lynis ] && /bin/mv ~/lynis ~/lynis_$(date +'%Y-%m-%d_%H%M') 2>/dev/null
    cd ~ && tar xfz ~/"${version_file}" || (echo "Unable to uncompress ~/${version_file}. Exiting..." && exit 1170)
    [ -d /usr/local/lynis ] && cd /usr/local && tar cfz ~/lynis_previous_$(date +'%Y-%m-%d_%H%M').tar.gz lynis && \
    /bin/rm -rf lynis && cd ~
    /bin/mv -f ~/lynis /usr/local/ && ln -s /usr/local/lynis/lynis ${LYNIS} 2>/dev/null
    /bin/rm ~/"${version_file}" 2>/dev/null

    ${LYNIS} update info
    echo "Update complete"
  else
    echo "Update is not required at this time. Exiting"
    exit 0
  fi
}

# ----------------------------------------------------------------------------
# RUNTIME
# \(^_^)/                                      __|__
#                                     __|__ *---o0o---*
#                            __|__ *---o0o---*
#                         *---o0o---*
# ----------------------------------------------------------------------------
configure
lynis_update