#!/bin/bash print_usage() { cat </dev/null } cs_sensor_is_running() { if pgrep -u root falcon-sensor >/dev/null 2>&1; then echo "sensor is already running... exiting" exit 0 fi } cs_sensor_restart() { if type systemctl >/dev/null 2>&1; then systemctl restart falcon-sensor elif type service >/dev/null 2>&1; then service falcon-sensor restart else die "Could not restart falcon sensor" fi } cs_golden_image_prep() { local wait_time=60 local sleep_interval=5 local aid get_aid() { /opt/CrowdStrike/falconctl -g --aid | awk -F '"' '{print $2}' } aid=$(get_aid) while [ -z "$aid" ]; do if [ "$wait_time" -le 0 ]; then echo '[ Failed ]' die "Failed to retrieve existing AID. Please check the sensor status." fi sleep "$sleep_interval" wait_time=$((wait_time - sleep_interval)) aid=$(get_aid) done # Remove the aid /opt/CrowdStrike/falconctl -d -f --aid >/dev/null # Check if a provisioning token was used, if so add it back if [ -n "$cs_falcon_token" ]; then /opt/CrowdStrike/falconctl -s -f --provisioning-token="$cs_falcon_token" >/dev/null fi } cs_sensor_install() { local tempdir package_name tempdir=$(mktemp -d) tempdir_cleanup() { rm -rf "$tempdir"; } trap tempdir_cleanup EXIT get_oauth_token package_name=$(cs_sensor_download "$tempdir") os_install_package "$package_name" tempdir_cleanup } cs_sensor_download_only() { local destination_dir destination_dir="${FALCON_DOWNLOAD_PATH:-$PWD}" get_oauth_token cs_sensor_download "$destination_dir" } cs_sensor_remove() { remove_package() { local pkg="$1" if type dnf >/dev/null 2>&1; then dnf remove -q -y "$pkg" || rpm -e --nodeps "$pkg" elif type yum >/dev/null 2>&1; then yum remove -q -y "$pkg" || rpm -e --nodeps "$pkg" elif type zypper >/dev/null 2>&1; then zypper --quiet remove -y "$pkg" || rpm -e --nodeps "$pkg" elif type apt >/dev/null 2>&1; then DEBIAN_FRONTEND=noninteractive apt purge -y "$pkg" >/dev/null else rpm -e --nodeps "$pkg" fi } remove_package "falcon-sensor" } cs_sensor_policy_version() { local cs_policy_name="$1" sensor_update_policy sensor_update_versions sensor_update_policy=$( curl_command -G "https://$(cs_cloud)/policy/combined/sensor-update/v2" \ --data-urlencode "filter=platform_name:\"Linux\"+name.raw:\"$cs_policy_name\"" ) handle_curl_error $? if echo "$sensor_update_policy" | grep "authorization failed"; then die "Access denied: Please make sure that your Falcon API credentials allow access to sensor update policies (scope Sensor update policies [read])" elif echo "$sensor_update_policy" | grep "invalid bearer token"; then die "Invalid Access Token: $cs_falcon_oauth_token" fi sensor_update_versions=$(echo "$sensor_update_policy" | json_value "sensor_version") if [ -z "$sensor_update_versions" ]; then die "Could not find a sensor update policy with name: $cs_policy_name" fi oldIFS=$IFS IFS=" " # shellcheck disable=SC2086 set -- $sensor_update_versions if [ "$(echo "$sensor_update_versions" | wc -w)" -gt 1 ]; then if [ "$cs_os_arch" = "aarch64" ]; then echo "$2" else echo "$1" fi else echo "$1" fi IFS=$oldIFS } cs_sensor_download() { local destination_dir="$1" existing_installers sha_list INDEX sha file_type installer if [ -n "$cs_sensor_policy_name" ]; then cs_sensor_version=$(cs_sensor_policy_version "$cs_sensor_policy_name") cs_api_version_filter="+version:\"$cs_sensor_version\"" if [ "$cs_falcon_sensor_version_dec" -gt 0 ]; then echo "WARNING: Disabling FALCON_SENSOR_VERSION_DECREMENT because it conflicts with FALCON_SENSOR_UPDATE_POLICY_NAME" cs_falcon_sensor_version_dec=0 fi fi existing_installers=$( curl_command -G "https://$(cs_cloud)/sensors/combined/installers/v2?sort=version|desc" \ --data-urlencode "filter=os:\"$cs_os_name\"+os_version:\"*$cs_os_version*\"$cs_api_version_filter$cs_os_arch_filter" ) handle_curl_error $? if echo "$existing_installers" | grep "authorization failed"; then die "Access denied: Please make sure that your Falcon API credentials allow sensor download (scope Sensor Download [read])" elif echo "$existing_installers" | grep "invalid bearer token"; then die "Invalid Access Token: $cs_falcon_oauth_token" fi sha_list=$(echo "$existing_installers" | json_value "sha256") if [ -z "$sha_list" ]; then die "No sensor found for OS: $cs_os_name, Version: $cs_os_version. Either the OS or the OS version is not yet supported." fi # Set the index accordingly (the json_value expects and index+1 value) INDEX=$((cs_falcon_sensor_version_dec + 1)) sha=$(echo "$existing_installers" | json_value "sha256" "$INDEX" | sed 's/ *$//g' | sed 's/^ *//g') if [ -z "$sha" ]; then die "Unable to identify a sensor installer matching: $cs_os_name, version: $cs_os_version, index: N-$cs_falcon_sensor_version_dec" fi file_type=$(echo "$existing_installers" | json_value "file_type" "$INDEX" | sed 's/ *$//g' | sed 's/^ *//g') installer="${destination_dir}/falcon-sensor.${file_type}" curl_command "https://$(cs_cloud)/sensors/entities/download-installer/v1?id=$sha" -o "${installer}" handle_curl_error $? echo "$installer" } os_install_package() { local pkg="$1" rpm_install_package() { local pkg="$1" cs_falcon_gpg_import if type dnf >/dev/null 2>&1; then dnf install -q -y "$pkg" || rpm -ivh --nodeps "$pkg" elif type yum >/dev/null 2>&1; then yum install -q -y "$pkg" || rpm -ivh --nodeps "$pkg" elif type zypper >/dev/null 2>&1; then zypper --quiet install -y "$pkg" || rpm -ivh --nodeps "$pkg" else rpm -ivh --nodeps "$pkg" fi } # shellcheck disable=SC2221,SC2222 case "${os_name}" in Amazon | CentOS | Oracle | RHEL | Rocky | AlmaLinux | SLES) rpm_install_package "$pkg" ;; Debian) DEBIAN_FRONTEND=noninteractive apt-get -qq install -y "$pkg" >/dev/null ;; Ubuntu) # If this is ubuntu 14, we need to use dpkg instead if [ "${cs_os_version}" -eq 14 ]; then DEBIAN_FRONTEND=noninteractive dpkg -i "$pkg" >/dev/null 2>&1 || true DEBIAN_FRONTEND=noninteractive apt-get -qq install -f -y >/dev/null else DEBIAN_FRONTEND=noninteractive apt-get -qq install -y "$pkg" >/dev/null fi ;; *) die "Unrecognized OS: ${os_name}" ;; esac } aws_ssm_parameter() { local param_name="$1" hmac_sha256() { key="$1" data="$2" echo -n "$data" | openssl dgst -sha256 -mac HMAC -macopt "$key" | sed 's/^.* //' } token=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") api_endpoint="AmazonSSM.GetParameters" iam_role="$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/iam/security-credentials/)" aws_my_region="$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/placement/availability-zone | sed s/.$//)" _security_credentials="$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/iam/security-credentials/"$iam_role")" access_key_id="$(echo "$_security_credentials" | grep AccessKeyId | sed -e 's/ "AccessKeyId" : "//' -e 's/",$//')" access_key_secret="$(echo "$_security_credentials" | grep SecretAccessKey | sed -e 's/ "SecretAccessKey" : "//' -e 's/",$//')" security_token="$(echo "$_security_credentials" | grep Token | sed -e 's/ "Token" : "//' -e 's/",$//')" datetime=$(date -u +"%Y%m%dT%H%M%SZ") date=$(date -u +"%Y%m%d") request_data='{"Names":["'"${param_name}"'"],"WithDecryption":"true"}' request_data_dgst=$(echo -n "$request_data" | openssl dgst -sha256 | awk -F' ' '{print $2}') request_dgst=$( cat <"$tempfile" <&2 exit 1 } cs_cloud() { case "${cs_falcon_cloud}" in us-1) echo "api.crowdstrike.com" ;; us-2) echo "api.us-2.crowdstrike.com" ;; eu-1) echo "api.eu-1.crowdstrike.com" ;; us-gov-1) echo "api.laggar.gcw.crowdstrike.com" ;; *) die "Unrecognized Falcon Cloud: ${cs_falcon_cloud}" ;; esac } # Check if curl is greater or equal to 7.55 old_curl=$( if ! command -v curl >/dev/null 2>&1; then die "The 'curl' command is missing. Please install it before continuing. Aborting..." fi version=$(curl --version | head -n 1 | awk '{ print $2 }') minimum="7.55" # Check if the version is less than the minimum if printf "%s\n" "$version" "$minimum" | sort -V -C; then echo 0 else echo 1 fi ) # Old curl print warning message if [ "$old_curl" -eq 0 ]; then if [ "${ALLOW_LEGACY_CURL}" != "true" ]; then echo """ WARNING: Your version of curl does not support the ability to pass headers via stdin. For security considerations, we strongly recommend upgrading to curl 7.55.0 or newer. To bypass this warning, set the environment variable ALLOW_LEGACY_CURL=true """ exit 1 fi fi # Handle error codes returned by curl handle_curl_error() { local err_msg # Failed to download the file to destination if [ "$1" -eq 23 ]; then err_msg="Failed writing received data to disk/destination (exit code 23). Please check the destination path and permissions." die "$err_msg" fi # Proxy related errors if [ "$1" = "28" ]; then err_msg="Operation timed out (exit code 28)." if [ -n "$proxy" ]; then err_msg="$err_msg A proxy was used to communicate ($proxy). Please check your proxy settings." fi die "$err_msg" fi if [ "$1" = "5" ]; then err_msg="Couldn't resolve proxy (exit code 5). The address ($proxy) of the given proxy host could not be resolved. Please check your proxy settings." die "$err_msg" fi if [ "$1" = "7" ]; then err_msg="Failed to connect to host (exit code 7). Host found, but unable to open connection with host." if [ -n "$proxy" ]; then err_msg="$err_msg A proxy was used to communicate ($proxy). Please check your proxy settings." fi die "$err_msg" fi } curl_command() { # Dash does not support arrays, so we have to pass the args as separate arguments set -- "$@" if [ "$old_curl" -eq 0 ]; then curl -s -x "$proxy" -L -H "Authorization: Bearer ${cs_falcon_oauth_token}" "$@" else echo "Authorization: Bearer ${cs_falcon_oauth_token}" | curl -s -x "$proxy" -L -H @- "$@" fi } check_aws_instance() { local aws_instance # Check if running on EC2 hypervisor if [ -f /sys/hypervisor/uuid ] && grep -qi ec2 /sys/hypervisor/uuid; then aws_instance=true # Check if DMI board asset tag matches EC2 instance pattern elif [ -f /sys/devices/virtual/dmi/id/board_asset_tag ] && grep -q '^i-[a-z0-9]*$' /sys/devices/virtual/dmi/id/board_asset_tag; then aws_instance=true # Check if EC2 instance identity document is accessible else curl_output="$(curl -s --connect-timeout 5 http://169.254.169.254/latest/dynamic/instance-identity/)" if [ -n "$curl_output" ] && ! echo "$curl_output" | grep --silent -i 'not.*found'; then aws_instance=true fi fi echo "$aws_instance" } get_falcon_credentials() { if [ -z "$FALCON_ACCESS_TOKEN" ]; then aws_instance=$(check_aws_instance) cs_falcon_client_id=$( if [ -n "$FALCON_CLIENT_ID" ]; then echo "$FALCON_CLIENT_ID" elif [ -n "$aws_instance" ]; then aws_ssm_parameter "FALCON_CLIENT_ID" | json_value Value 1 else die "Missing FALCON_CLIENT_ID environment variable. Please provide your OAuth2 API Client ID for authentication with CrowdStrike Falcon platform. Establishing and retrieving OAuth2 API credentials can be performed at https://falcon.crowdstrike.com/support/api-clients-and-keys." fi ) cs_falcon_client_secret=$( if [ -n "$FALCON_CLIENT_SECRET" ]; then echo "$FALCON_CLIENT_SECRET" elif [ -n "$aws_instance" ]; then aws_ssm_parameter "FALCON_CLIENT_SECRET" | json_value Value 1 else die "Missing FALCON_CLIENT_SECRET environment variable. Please provide your OAuth2 API Client Secret for authentication with CrowdStrike Falcon platform. Establishing and retrieving OAuth2 API credentials can be performed at https://falcon.crowdstrike.com/support/api-clients-and-keys." fi ) else if [ -z "$FALCON_CLOUD" ]; then die "If setting the FALCON_ACCESS_TOKEN manually, you must also specify the FALCON_CLOUD" fi fi } get_oauth_token() { # Get credentials first get_falcon_credentials cs_falcon_oauth_token=$( if [ -n "$FALCON_ACCESS_TOKEN" ]; then token=$FALCON_ACCESS_TOKEN else token_result=$(echo "client_id=$cs_falcon_client_id&client_secret=$cs_falcon_client_secret" | curl -X POST -s -x "$proxy" -L "https://$(cs_cloud)/oauth2/token" \ -H 'Content-Type: application/x-www-form-urlencoded; charset=utf-8' \ -H "User-Agent: crowdstrike-falcon-scripts/$VERSION" \ --dump-header "${response_headers}" \ --data @-) handle_curl_error $? token=$(echo "$token_result" | json_value "access_token" | sed 's/ *$//g' | sed 's/^ *//g') if [ -z "$token" ]; then die "Unable to obtain CrowdStrike Falcon OAuth Token. Double check your credentials and/or ensure you set the correct cloud region." fi fi echo "$token" ) if [ -z "$FALCON_ACCESS_TOKEN" ]; then region_hint=$(grep -i ^x-cs-region: "$response_headers" | head -n 1 | tr '[:upper:]' '[:lower:]' | tr -d '\r' | sed 's/^x-cs-region: //g') if [ -z "${FALCON_CLOUD}" ]; then if [ -z "${region_hint}" ]; then die "Unable to obtain region hint from CrowdStrike Falcon OAuth API, Please provide FALCON_CLOUD environment variable as an override." fi cs_falcon_cloud="${region_hint}" else if [ "x${FALCON_CLOUD}" != "x${region_hint}" ]; then echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2 fi fi fi rm "${response_headers}" } get_provisioning_token() { local check_settings is_required token_value # First, let's check if installation tokens are required check_settings=$(curl_command "https://$(cs_cloud)/installation-tokens/entities/customer-settings/v1") handle_curl_error $? if echo "$check_settings" | grep "authorization failed" >/dev/null; then # For now we just return. We can error out once more people get a chance to update their API keys return fi is_required=$(echo "$check_settings" | json_value "tokens_required" | xargs) if [ "$is_required" = "true" ]; then local token_query token_id token_result # Get the token ID token_query=$(curl_command "https://$(cs_cloud)/installation-tokens/queries/tokens/v1") token_id=$(echo "$token_query" | tr -d '\n" ' | awk -F'[][]' '{print $2}' | cut -d',' -f1) if [ -z "$token_id" ]; then die "No installation token found in a required token environment." fi # Get the token value from ID token_result=$(curl_command "https://$(cs_cloud)/installation-tokens/entities/tokens/v1?ids=$token_id") token_value=$(echo "$token_result" | json_value "value" | xargs) if [ -z "$token_value" ]; then die "Could not obtain installation token value." fi fi echo "$token_value" } get_falcon_cid() { if [ -n "$FALCON_CID" ]; then echo "$FALCON_CID" else cs_target_cid=$(curl_command "https://$(cs_cloud)/sensors/queries/installers/ccid/v1") handle_curl_error $? if [ -z "$cs_target_cid" ]; then die "Unable to obtain CrowdStrike Falcon CID. Response was $cs_target_cid" fi echo "$cs_target_cid" | tr -d '\n" ' | awk -F'[][]' '{print $2}' fi } # shellcheck disable=SC2034 cs_uninstall=$( if [ "$FALCON_UNINSTALL" ]; then echo -n 'Removing Falcon Sensor ... ' cs_sensor_remove echo '[ Ok ]' echo 'Falcon Sensor removed successfully.' exit 2 fi ) os_name=$( # returns either: Amazon, Ubuntu, CentOS, RHEL, or SLES # lsb_release is not always present name=$(cat /etc/*release | grep ^NAME= | awk -F'=' '{ print $2 }' | sed "s/\"//g;s/Red Hat.*/RHEL/g;s/ Linux$//g;s/ GNU\/Linux$//g;s/Oracle.*/Oracle/g;s/Amazon.*/Amazon/g") if [ -z "$name" ]; then if lsb_release -s -i | grep -q ^RedHat; then name="RHEL" elif [ -f /usr/bin/lsb_release ]; then name=$(/usr/bin/lsb_release -s -i) fi fi if [ -z "$name" ]; then die "Cannot recognise operating system" fi echo "$name" ) os_version=$( version=$(cat /etc/*release | grep VERSION_ID= | awk '{ print $1 }' | awk -F'=' '{ print $2 }' | sed "s/\"//g") if [ -z "$version" ]; then if type rpm >/dev/null 2>&1; then # older systems may have *release files of different form version=$(rpm -qf /etc/redhat-release --queryformat '%{VERSION}' | sed 's/\([[:digit:]]\+\).*/\1/g') elif [ -f /etc/debian_version ]; then version=$(cat /etc/debian_version) elif [ -f /usr/bin/lsb_release ]; then version=$(/usr/bin/lsb_release -r | /usr/bin/cut -f 2-) fi fi if [ -z "$version" ]; then cat /etc/*release >&2 die "Could not determine distribution version" fi echo "$version" ) cs_os_name=$( # returns OS name as recognised by CrowdStrike Falcon API # shellcheck disable=SC2221,SC2222 case "${os_name}" in Amazon) echo "Amazon Linux" ;; CentOS | Oracle | RHEL | Rocky | AlmaLinux) echo "*RHEL*" ;; Debian) echo "Debian" ;; SLES) echo "SLES" ;; Ubuntu) echo "Ubuntu" ;; *) die "Unrecognized OS: ${os_name}" ;; esac ) cs_os_arch=$( uname -m ) cs_os_arch_filter=$( case "${cs_os_arch}" in x86_64) echo "+architectures:\"x86_64\"" ;; aarch64) echo "+architectures:\"arm64\"" ;; s390x) echo "+architectures:\"s390x\"" ;; *) die "Unrecognized OS architecture: ${cs_os_arch}" ;; esac ) cs_os_version=$( version=$(echo "$os_version" | awk -F'.' '{print $1}') # Check if we are using Amazon Linux 1 if [ "${os_name}" = "Amazon" ]; then if [ "$version" != "2" ] && [ "$version" -le 2018 ]; then version="1" fi fi echo "$version" ) cs_falcon_token=$( if [ -n "$FALCON_PROVISIONING_TOKEN" ]; then echo "$FALCON_PROVISIONING_TOKEN" fi ) cs_falcon_cloud=$( if [ -n "$FALCON_CLOUD" ]; then echo "$FALCON_CLOUD" else # Auto-discovery is using us-1 initially echo "us-1" fi ) cs_sensor_policy_name=$( if [ -n "$FALCON_SENSOR_UPDATE_POLICY_NAME" ]; then echo "$FALCON_SENSOR_UPDATE_POLICY_NAME" else echo "" fi ) cs_falcon_sensor_version_dec=$( re='^[0-9]\+$' if [ -n "$FALCON_SENSOR_VERSION_DECREMENT" ]; then if ! expr "$FALCON_SENSOR_VERSION_DECREMENT" : "$re" >/dev/null 2>&1; then die "The FALCON_SENSOR_VERSION_DECREMENT must be an integer greater than or equal to 0 or less than 5. FALCON_SENSOR_VERSION_DECREMENT: \"$FALCON_SENSOR_VERSION_DECREMENT\"" elif [ "$FALCON_SENSOR_VERSION_DECREMENT" -lt 0 ] || [ "$FALCON_SENSOR_VERSION_DECREMENT" -gt 5 ]; then die "The FALCON_SENSOR_VERSION_DECREMENT must be an integer greater than or equal to 0 or less than 5. FALCON_SENSOR_VERSION_DECREMENT: \"$FALCON_SENSOR_VERSION_DECREMENT\"" else echo "$FALCON_SENSOR_VERSION_DECREMENT" fi else echo "0" fi ) response_headers=$(mktemp) # shellcheck disable=SC2001 proxy=$( proxy="" if [ -n "$FALCON_APH" ]; then proxy="$(echo "$FALCON_APH" | sed "s|http.*://||")" if [ -n "$FALCON_APP" ]; then proxy="$proxy:$FALCON_APP" fi fi if [ -n "$proxy" ]; then # Remove redundant quotes proxy="$(echo "$proxy" | sed "s/[\'\"]//g")" proxy="http://$proxy" fi echo "$proxy" ) if [ -n "$FALCON_APD" ]; then cs_falcon_apd=$( case "${FALCON_APD}" in true) echo "true" ;; false) echo "false" ;; *) die "Unrecognized APD: ${FALCON_APD} value must be one of : [true|false]" ;; esac ) fi if [ -n "$FALCON_BILLING" ]; then cs_falcon_billing=$( case "${FALCON_BILLING}" in default) echo "default" ;; metered) echo "metered" ;; *) die "Unrecognized BILLING: ${FALCON_BILLING} value must be one of : [default|metered]" ;; esac ) fi if [ -n "$FALCON_BACKEND" ]; then cs_falcon_backend=$( case "${FALCON_BACKEND}" in auto) echo "auto" ;; bpf) echo "bpf" ;; kernel) echo "kernel" ;; *) die "Unrecognized BACKEND: ${FALCON_BACKEND} value must be one of : [auto|bpf|kernel]" ;; esac ) fi if [ -n "$FALCON_TRACE" ]; then cs_falcon_trace=$( case "${FALCON_TRACE}" in none) echo "none" ;; err) echo "err" ;; warn) echo "warn" ;; info) echo "info" ;; debug) echo "debug" ;; *) die "Unrecognized TRACE: ${FALCON_TRACE} value must be one of : [none|err|warn|info|debug]" ;; esac ) fi main "$@"