From d546fb2eb4d56e87e17e9449dcd54c893324853a Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Mon, 21 Apr 2025 15:03:32 -0400 Subject: [PATCH 01/11] feat(bash): add migration script --- bash/migrate/falcon-linux-migrate.sh | 1354 ++++++++++++++++++++++++++ 1 file changed, 1354 insertions(+) create mode 100755 bash/migrate/falcon-linux-migrate.sh diff --git a/bash/migrate/falcon-linux-migrate.sh b/bash/migrate/falcon-linux-migrate.sh new file mode 100755 index 00000000..7347b57c --- /dev/null +++ b/bash/migrate/falcon-linux-migrate.sh @@ -0,0 +1,1354 @@ +#!/bin/bash +# +# Bash script to migrate Falcon sensor to another falcon CID. +# + +VERSION="1.7.4" + +print_usage() { + cat <&2 + exit 1 +} + +# If -h or --help is passed, print the usage and exit +if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + print_usage + exit 0 +fi + +# Ensure script is ran as root/sudo +if [ "$(id -u)" -ne 0 ]; then + die "This script must be ran as root or with sudo." +fi + +# Validate required environment variables +if [ -z "$OLD_FALCON_CLIENT_ID" ] || [ -z "$OLD_FALCON_CLIENT_SECRET" ] || [ -z "$NEW_FALCON_CLIENT_ID" ] || [ -z "$NEW_FALCON_CLIENT_SECRET" ]; then + die "Required environment variables are not set. Please ensure OLD_FALCON_CLIENT_ID, OLD_FALCON_CLIENT_SECRET, NEW_FALCON_CLIENT_ID, and NEW_FALCON_CLIENT_SECRET are all set." +fi + +# Use the system's temporary directory +log_path="${LOG_PATH:-/tmp}" +recovery_file="$log_path/falcon_migration_recovery.csv" +log_file="$log_path/falcon_migration_$(date +%Y%m%d_%H%M%S).log" +migrate_tags="${MIGRATE_TAGS:-true}" + +log() { + local level="$1" + local message="$2" + echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] $message" | tee -a "$log_file" +} + +install_sensor() { + echo -n 'Check if Falcon Sensor is running ... ' + cs_sensor_is_running + echo '[ Not present ]' + echo -n 'Falcon Sensor Install ... ' + cs_sensor_install + echo '[ Ok ]' + echo -n 'Falcon Sensor Register ... ' + cs_sensor_register + echo '[ Ok ]' + echo -n 'Falcon Sensor Restart ... ' + cs_sensor_restart + echo '[ Ok ]' + log "INFO" 'Falcon Sensor installed successfully.' +} + +uninstall_sensor() { + # Check if Falcon sensor is installed + cs_sensor_installed + + # Handle maintenance token + cs_maintenance_token="" + if [ -n "$FALCON_MAINTENANCE_TOKEN" ]; then + cs_maintenance_token="$FALCON_MAINTENANCE_TOKEN" + elif [ -n "$FALCON_CLIENT_ID" ] && [ -n "$FALCON_CLIENT_SECRET" ] && [ -n "$aid" ]; then + get_maintenance_token + log "INFO" "Retrieved maintenance token via API" + fi + + echo -n 'Removing Falcon Sensor ... ' + cs_sensor_remove + echo '[ Ok ]' + if [ "${FALCON_REMOVE_HOST}" = "true" ]; then + echo -n 'Removing host from console ... ' + cs_remove_host_from_console + echo '[ Ok ]' + fi + log "INFO" 'Falcon Sensor removed successfully.' +} + +# Shared functions +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 +) + +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 +} + +handle_curl_error() { + 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 +} + +check_package_manager_lock() { + lock_file="/var/lib/rpm/.rpm.lock" + lock_type="RPM" + local timeout=300 interval=5 elapsed=0 + + if type dpkg >/dev/null 2>&1; then + lock_file="/var/lib/dpkg/lock" + lock_type="DPKG" + fi + + while lsof -w "$lock_file" >/dev/null 2>&1; do + if [ $elapsed -eq 0 ]; then + echo "" + echo "Package manager is locked. Waiting up to ${timeout} seconds for lock to be released..." + fi + + if [ $elapsed -ge $timeout ]; then + echo "Timed out waiting for ${lock_type} lock to be released after ${timeout} seconds." + echo "You may need to manually investigate processes locking ${lock_file}:" + lsof -w "$lock_file" || true + die "Installation aborted due to package manager lock timeout." + fi + + sleep $interval + elapsed=$((elapsed + interval)) + echo "Retrying again in ${interval} seconds..." + done +} + +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 +} + +# Shared auth functions +get_falcon_credentials() { + if [ -z "$FALCON_ACCESS_TOKEN" ]; then + cs_falcon_client_id=$( + if [ -n "$FALCON_CLIENT_ID" ]; then + echo "$FALCON_CLIENT_ID" + 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" + 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_user_agent() { + local user_agent="crowdstrike-falcon-scripts/$VERSION" + if [ -n "$USER_AGENT" ]; then + user_agent="${user_agent} ${USER_AGENT}" + fi + echo "$user_agent" +} + +get_oauth_token() { + # Get credentials first + get_falcon_credentials + + response_headers=$(mktemp) + + 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: $(get_user_agent)" \ + --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}" +} + +# Uninstall functions + +# +get_aid() { + local aid + aid="$(/opt/CrowdStrike/falconctl -g --aid | awk -F '"' '{print $2}')" + echo "$aid" +} + +cs_sensor_installed() { + if ! test -f /opt/CrowdStrike/falconctl; then + log "WARNING" "Falcon sensor is already uninstalled." && exit 0 + fi + # Get AID if FALCON_REMOVE_HOST is set to true or if we need to get a maintenance token + if [ "${FALCON_REMOVE_HOST}" = "true" ] || [ -n "$FALCON_CLIENT_ID" ] && [ -n "$FALCON_CLIENT_SECRET" ] && [ -z "$FALCON_MAINTENANCE_TOKEN" ]; then + aid=$(get_aid) + fi +} + +cs_sensor_remove() { + remove_package() { + 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 2>&1 + else + rpm -e --nodeps "$pkg" + fi + } + + # Handle maintenance protection + if [ -n "$cs_maintenance_token" ]; then + # shellcheck disable=SC2086 + if ! /opt/CrowdStrike/falconctl -s -f --maintenance-token=${cs_maintenance_token} >/dev/null 2>&1; then + die "Failed to apply maintenance token. Uninstallation may fail." + fi + fi + + # Check for package manager lock prior to uninstallation + check_package_manager_lock + + remove_package "falcon-sensor" +} + +cs_remove_host_from_console() { + if [ -z "$aid" ]; then + echo 'Unable to find AID. Skipping host removal from console.' + else + payload="{\"ids\": [\"$aid\"]}" + url="https://$(cs_cloud)/devices/entities/devices-actions/v2?action_name=hide_host" + + curl_command -X "POST" -H "Content-Type: application/json" -d "$payload" "$url" >/dev/null + + handle_curl_error $? + fi +} + +get_maintenance_token() { + if [ -z "$aid" ]; then + die "Unable to find AID. Cannot retrieve maintenance token." + fi + + echo "Retrieving maintenance token from the CrowdStrike Falcon API..." + + payload="{\"device_id\": \"$aid\", \"audit_message\": \"CrowdStrike Falcon Uninstall Bash Script\"}" + url="https://$(cs_cloud)/policy/combined/reveal-uninstall-token/v1" + + response=$(curl_command -X "POST" -H "Content-Type: application/json" -d "$payload" "$url") + + handle_curl_error $? + + if echo "$response" | grep -q "\"uninstall_token\""; then + cs_maintenance_token=$(echo "$response" | json_value "uninstall_token" 1 | sed 's/ *$//g' | sed 's/^ *//g') + if [ -z "$cs_maintenance_token" ]; then + die "Retrieved empty maintenance token from API." + fi + else + die "Failed to retrieve maintenance token. Response: $response" + fi +} + +# Install functions +cs_sensor_register() { + # Get the falcon cid + cs_falcon_cid="$(get_falcon_cid)" + # If cs_falcon_token is not set, try getting it from api + if [ -z "${cs_falcon_token}" ]; then + cs_falcon_token="$(get_provisioning_token)" + fi + # add the cid to the params + cs_falcon_args=--cid="${cs_falcon_cid}" + if [ -n "${cs_falcon_token}" ]; then + cs_token=--provisioning-token="${cs_falcon_token}" + cs_falcon_args="$cs_falcon_args $cs_token" + fi + # add tags to the params + if [ -n "${FALCON_TAGS}" ]; then + cs_falconctl_opt_tags=--tags="$FALCON_TAGS" + cs_falcon_args="$cs_falcon_args $cs_falconctl_opt_tags" + fi + # add proxy enable/disable param + if [ -n "${cs_falcon_apd}" ]; then + cs_falconctl_opt_apd=--apd=$cs_falcon_apd + cs_falcon_args="$cs_falcon_args $cs_falconctl_opt_apd" + fi + # add proxy host to the params + if [ -n "${FALCON_APH}" ]; then + cs_falconctl_opt_aph=--aph="${FALCON_APH}" + cs_falcon_args="$cs_falcon_args $cs_falconctl_opt_aph" + fi + # add proxy port to the params + if [ -n "${FALCON_APP}" ]; then + cs_falconctl_opt_app=--app="${FALCON_APP}" + cs_falcon_args="$cs_falcon_args $cs_falconctl_opt_app" + fi + # add the billing type to the params + if [ -n "${FALCON_BILLING}" ]; then + cs_falconctl_opt_billing=--billing="${cs_falcon_billing}" + cs_falcon_args="$cs_falcon_args $cs_falconctl_opt_billing" + fi + # add the backend to the params + if [ -n "${cs_falcon_backend}" ]; then + cs_falconctl_opt_backend=--backend="${cs_falcon_backend}" + cs_falcon_args="$cs_falcon_args $cs_falconctl_opt_backend" + fi + # add the trace level to the params + if [ -n "${cs_falcon_trace}" ]; then + cs_falconctl_opt_trace=--trace="${cs_falcon_trace}" + cs_falcon_args="$cs_falcon_args $cs_falconctl_opt_trace" + fi + # run the configuration command + # shellcheck disable=SC2086 + /opt/CrowdStrike/falconctl -s -f ${cs_falcon_args} >/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_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_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" + # Check for package manager lock prior to uninstallation + check_package_manager_lock + + 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 +} + +cs_falcon_gpg_import() { + tempfile=$(mktemp) + cat >"$tempfile" </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 +} + +# Recovery file and tags functions +# Create a recovery file to track tags and AID in case migration fails +create_recovery_file() { + local sensor_tags="$1" + local falcon_tags="$2" + local old_aid="$3" + local path="$4" + + # Create directory if it doesn't exist + mkdir -p "$(dirname "$path")" + + # Write data to recovery file in simple CSV format + echo "OldAid,SensorTags,FalconTags" >"$path" + echo "$old_aid,$sensor_tags,$falcon_tags" >>"$path" + + log "INFO" "Recovery file created at $path" +} + +# Read data from recovery file +read_recovery_file() { + local path="$1" + + if [ ! -f "$path" ]; then + log "WARNING" "Recovery file not found at $path" + return 1 + fi + + # Skip header line and read data + old_aid=$(tail -n 1 "$path" | cut -d ',' -f 1) + sensor_tags=$(tail -n 1 "$path" | cut -d ',' -f 2) + falcon_tags=$(tail -n 1 "$path" | cut -d ',' -f 3) + + log "INFO" "Recovery data loaded: AID=$old_aid" + return 0 +} + +# Get the host's tags from a specific Falcon instance +get_falcon_tags() { + log "INFO" "Retrieving tags for host with AID: $aid" + + local response + response=$(curl_command "https://$(cs_cloud)/devices/entities/devices/v2?ids=$aid") + + handle_curl_error $? + + if echo "$response" | grep "authorization failed" >/dev/null; then + die "Access denied: Please make sure your Falcon API credentials allow access to host data (scope Host [read])" + elif echo "$response" | grep "invalid bearer token" >/dev/null; then + die "Invalid Access Token: $cs_falcon_oauth_token" + fi + + # Extract tags from response + local tags + tags=$(echo "$response" | grep -o '"tags":\[[^]]*\]' | sed 's/"tags":\[//;s/\]//') + + # Strip quotes + tags=$(echo "$tags" | tr -d '"' | tr ',' ' ') + + echo "$tags" +} + +# Split tags into sensor tags and falcon tags +split_tags() { + local tags="$1" + local sensor_tags="" + local falcon_tags="" + local tag="" + + # Create a temporary file with one tag per line + local tmpfile + tmpfile=$(mktemp) + echo "$tags" | tr ' ' '\n' >"$tmpfile" + + while read -r tag; do + case "$tag" in + SensorGroupingTags/*) + # Extract the tag part after SensorGroupingTags/ + local sensor_tag + sensor_tag=$(echo "$tag" | sed 's/^SensorGroupingTags\///') + if [ -n "$sensor_tags" ]; then + sensor_tags="$sensor_tags,$sensor_tag" + else + sensor_tags="$sensor_tag" + fi + ;; + FalconGroupingTags/*) + # Extract the tag part after FalconGroupingTags/ + local falcon_tag + falcon_tag=$(echo "$tag" | sed 's/^FalconGroupingTags\///') + if [ -n "$falcon_tags" ]; then + falcon_tags="$falcon_tags,$falcon_tag" + else + falcon_tags="$falcon_tag" + fi + ;; + esac + done <"$tmpfile" + + rm -f "$tmpfile" + + echo "$sensor_tags;$falcon_tags" +} + +# Set tags for a host in a Falcon instance +set_falcon_tags() { + local aid="$1" + local tags="$2" + + log "INFO" "Setting tags for host with AID: $aid" + + # Prepare the tags payload + local payload + payload=$( + cat </dev/null; then + die "Access denied: Please make sure your Falcon API credentials allow access to host data (scope Host [write])" + elif echo "$response" | grep "invalid bearer token" >/dev/null; then + die "Invalid Access Token: $cs_falcon_oauth_token" + elif echo "$response" | grep "\"updated\":true" >/dev/null; then + log "INFO" "Successfully set tags on host" + return 0 + else + log "WARNING" "Failed to set tags: $response" + return 1 + fi +} + +# Format tags for API request +format_tags_for_api() { + local tags="$1" + local prefix="$2" # e.g., "FalconGroupingTags" or "SensorGroupingTags" + local formatted_tags="" + + # Create a temp file with one tag per line + local tmpfile + tmpfile=$(mktemp) + echo "$tags" | tr ',' '\n' >"$tmpfile" + + while read -r tag; do + # Skip empty tags + if [ -n "$tag" ]; then + if [ -n "$formatted_tags" ]; then + formatted_tags="$formatted_tags,\"$prefix/$tag\"" + else + formatted_tags="\"$prefix/$tag\"" + fi + fi + done <"$tmpfile" + + rm -f "$tmpfile" + echo "$formatted_tags" +} + +# Merge tags with new tags, removing duplicates +merge_tags() { + local existing_tags="$1" + local new_tags="$2" + local merged_tags="$existing_tags" + + # Create a temp file with one tag per line from new_tags + local tmpfile + tmpfile=$(mktemp) + echo "$new_tags" | tr ',' '\n' >"$tmpfile" + + while read -r tag; do + # Skip empty tags + if [ -n "$tag" ]; then + # Check if tag exists in merged_tags + if ! echo ",$merged_tags," | grep -q ",$tag,"; then + if [ -n "$merged_tags" ]; then + merged_tags="$merged_tags,$tag" + else + merged_tags="$tag" + fi + fi + fi + done <"$tmpfile" + + rm -f "$tmpfile" + echo "$merged_tags" +} + +#### start processing +set -e + +# 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" +) + +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_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 +) + +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() { + # Start of migration + touch "$log_file" + echo "Migration file created at: $log_file" + echo "Migration started at $(date)" >> "$log_file" + # auth with old credentials + FALCON_CLIENT_ID=$OLD_FALCON_CLIENT_ID + FALCON_CLIENT_SECRET=$OLD_FALCON_CLIENT_SECRET + cs_falcon_cloud="${OLD_FALCON_CLOUD:-us-1}" + log "INFO" "Authenticating to old CID..." + get_oauth_token + # Check if we are in recovery mode + local recovery_mode=false + if [ -f "$recovery_file" ]; then + recovery_mode=true + log "INFO" "Recovery file detected. Attempting to recover from previous migration attempt." + if read_recovery_file "$recovery_file"; then + log "INFO" "Loaded recovery data: old_aid=$old_aid" + else + log "WARNING" "Failed to load recovery data. Starting fresh migration." + recovery_mode=false + fi + fi + + # Get the AID if not in recovery mode + if [ "$recovery_mode" = false ]; then + # Get the AID and tags + old_aid=$(get_aid) + + if [ -z "$old_aid" ]; then + log "WARNING" "No AID found. The sensor may not be properly installed." + else + log "INFO" "Found AID: $old_aid" + + if [ "$migrate_tags" = "true" ]; then + log "INFO" "Retrieving existing tags from current installation..." + + # Get the tags + local tags + tags=$(get_falcon_tags) + + # Split tags into sensor and falcon grouping tags + local split_result + split_result=$(split_tags "$tags") + + # Parse split result (semicolon-separated) + sensor_tags=$(echo "$split_result" | cut -d ';' -f 1) + falcon_tags=$(echo "$split_result" | cut -d ';' -f 2) + + log "INFO" "Found Sensor grouping tags: $sensor_tags" + log "INFO" "Found Falcon grouping tags: $falcon_tags" + + # Create recovery file + create_recovery_file "$sensor_tags" "$falcon_tags" "$old_aid" "$recovery_file" + fi + fi + fi + + # Uninstall sensor + log "INFO" "Uninstalling old Falcon sensor..." + uninstall_sensor | tee -a "$log_file" + + # Install new sensor + # auth with new credentials + FALCON_CLIENT_ID=$NEW_FALCON_CLIENT_ID + FALCON_CLIENT_SECRET=$NEW_FALCON_CLIENT_SECRET + FALCON_CID=$NEW_FALCON_CID + cs_falcon_cloud="${NEW_FALCON_CLOUD:-us-1}" + log "INFO" "Authenticating to new CID..." + get_oauth_token + + log "INFO" "Checking if tags need to be migrated..." + # Handle tags if migrating + if [ "$migrate_tags" = "true" ]; then + # Merge existing sensor tags with any new tags specified + if [ -n "$FALCON_TAGS" ]; then + sensor_tags=$(merge_tags "$sensor_tags" "$FALCON_TAGS") + log "INFO" "Merged sensor tags: $sensor_tags" + fi + + # Set the tags for the sensor install + FALCON_TAGS="$sensor_tags" + fi + + log "INFO" "Installing Falcon sensor to new CID..." + install_sensor | tee -a "$log_file" + + # If migrate tags - make sure all tags get added + # Set Falcon grouping tags if migrating tags and falcon tags exist + if [ "$migrate_tags" = "true" ] && [ -n "$falcon_tags" ]; then + log "INFO" "Waiting for new sensor registration before setting Falcon grouping tags..." + + # Wait for the new AID to be available + local max_wait=60 + local wait_count=0 + local new_aid="" + + while [ $wait_count -lt $max_wait ]; do + new_aid=$(get_aid) + if [ -n "$new_aid" ]; then + break + fi + log "INFO" "Waiting for new AID to be available... ($wait_count/$max_wait)" + wait_count=$((wait_count + 1)) + sleep 5 + done + + if [ -n "$new_aid" ]; then + log "INFO" "New AID obtained: $new_aid" + + # Merge existing falcon tags with any new falcon tags specified + if [ -n "$FALCON_GROUPING_TAGS" ]; then + falcon_tags=$(merge_tags "$falcon_tags" "$FALCON_GROUPING_TAGS") + log "INFO" "Merged Falcon grouping tags: $falcon_tags" + fi + + # Format tags for API request + local formatted_tags + formatted_tags=$(format_tags_for_api "$falcon_tags" "FalconGroupingTags") + + # Set falcon tags via API + log "INFO" "Setting Falcon grouping tags via API..." + local max_attempts=5 + local attempt=1 + local success=false + + while [ $attempt -le $max_attempts ] && [ "$success" = false ]; do + if set_falcon_tags "$new_aid" "$formatted_tags"; then + success=true + else + log "WARNING" "Failed to set Falcon tags, attempt $attempt of $max_attempts" + attempt=$((attempt + 1)) + sleep 5 + fi + done + + if [ "$success" = true ]; then + log "INFO" "Successfully set Falcon grouping tags" + else + log "WARNING" "Failed to set Falcon grouping tags after $max_attempts attempts" + fi + else + log "WARNING" "Could not obtain new AID after $max_wait attempts" + fi + fi + + # Remove recovery file when done + if [ -f "$recovery_file" ]; then + log "INFO" "Removing recovery file..." + # rm -f "$recovery_file" + fi + + log "INFO" "Falcon sensor migration completed successfully" +} + +main "$@" From 51eb01faf023bb7ce456b6ba7f5fff6526629d7b Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Mon, 21 Apr 2025 15:07:04 -0400 Subject: [PATCH 02/11] doc: make version decrement clearer --- bash/install/falcon-linux-install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/bash/install/falcon-linux-install.sh b/bash/install/falcon-linux-install.sh index d1680d08..daea439d 100755 --- a/bash/install/falcon-linux-install.sh +++ b/bash/install/falcon-linux-install.sh @@ -33,6 +33,7 @@ Other Options - FALCON_SENSOR_VERSION_DECREMENT (default: 0 [latest]) The number of versions prior to the latest release to install. + For example, 1 would install version N-1. - FALCON_PROVISIONING_TOKEN (default: unset) The provisioning token to use for installing the sensor. From 16e211cfc462043d90dc5d8da9d26f5c63d0dfa8 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Mon, 21 Apr 2025 16:54:03 -0400 Subject: [PATCH 03/11] fix: update migrate script logic and fix tag errors --- bash/migrate/falcon-linux-migrate.sh | 35 ++++++++++++---------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/bash/migrate/falcon-linux-migrate.sh b/bash/migrate/falcon-linux-migrate.sh index 7347b57c..becbbdbd 100755 --- a/bash/migrate/falcon-linux-migrate.sh +++ b/bash/migrate/falcon-linux-migrate.sh @@ -169,7 +169,7 @@ install_sensor() { echo -n 'Falcon Sensor Restart ... ' cs_sensor_restart echo '[ Ok ]' - log "INFO" 'Falcon Sensor installed successfully.' + echo 'Falcon Sensor installed successfully.' } uninstall_sensor() { @@ -182,7 +182,7 @@ uninstall_sensor() { cs_maintenance_token="$FALCON_MAINTENANCE_TOKEN" elif [ -n "$FALCON_CLIENT_ID" ] && [ -n "$FALCON_CLIENT_SECRET" ] && [ -n "$aid" ]; then get_maintenance_token - log "INFO" "Retrieved maintenance token via API" + echo "Retrieved maintenance token via API" fi echo -n 'Removing Falcon Sensor ... ' @@ -193,7 +193,7 @@ uninstall_sensor() { cs_remove_host_from_console echo '[ Ok ]' fi - log "INFO" 'Falcon Sensor removed successfully.' + echo 'Falcon Sensor removed successfully.' } # Shared functions @@ -791,9 +791,9 @@ create_recovery_file() { # Create directory if it doesn't exist mkdir -p "$(dirname "$path")" - # Write data to recovery file in simple CSV format - echo "OldAid,SensorTags,FalconTags" >"$path" - echo "$old_aid,$sensor_tags,$falcon_tags" >>"$path" + # Write data to recovery file in CSV format using semicolons as delimiters + echo "OldAid;SensorTags;FalconTags" >"$path" + echo "$old_aid;$sensor_tags;$falcon_tags" >>"$path" log "INFO" "Recovery file created at $path" } @@ -807,10 +807,10 @@ read_recovery_file() { return 1 fi - # Skip header line and read data - old_aid=$(tail -n 1 "$path" | cut -d ',' -f 1) - sensor_tags=$(tail -n 1 "$path" | cut -d ',' -f 2) - falcon_tags=$(tail -n 1 "$path" | cut -d ',' -f 3) + # Skip header line and read data using semicolon as delimiter + old_aid=$(tail -n 1 "$path" | cut -d ';' -f 1) + sensor_tags=$(tail -n 1 "$path" | cut -d ';' -f 2) + falcon_tags=$(tail -n 1 "$path" | cut -d ';' -f 3) log "INFO" "Recovery data loaded: AID=$old_aid" return 0 @@ -818,6 +818,8 @@ read_recovery_file() { # Get the host's tags from a specific Falcon instance get_falcon_tags() { + local aid="$1" + log "INFO" "Retrieving tags for host with AID: $aid" local response @@ -833,10 +835,7 @@ get_falcon_tags() { # Extract tags from response local tags - tags=$(echo "$response" | grep -o '"tags":\[[^]]*\]' | sed 's/"tags":\[//;s/\]//') - - # Strip quotes - tags=$(echo "$tags" | tr -d '"' | tr ',' ' ') + tags=$(echo "$response" | grep 'GroupingTags/' | sed 's/[",]//g' | sed 's/^[[:space:]]*//') echo "$tags" } @@ -907,11 +906,7 @@ EOF handle_curl_error $? - if echo "$response" | grep "authorization failed" >/dev/null; then - die "Access denied: Please make sure your Falcon API credentials allow access to host data (scope Host [write])" - elif echo "$response" | grep "invalid bearer token" >/dev/null; then - die "Invalid Access Token: $cs_falcon_oauth_token" - elif echo "$response" | grep "\"updated\":true" >/dev/null; then + if [ "$(echo "$response" | json_value "updated" | xargs)" == "true" ]; then log "INFO" "Successfully set tags on host" return 0 else @@ -1235,7 +1230,7 @@ main() { # Get the tags local tags - tags=$(get_falcon_tags) + tags=$(get_falcon_tags "$old_aid") # Split tags into sensor and falcon grouping tags local split_result From 9cfb24dc3f0d60afd724c3aca4561657ac8b5973 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Tue, 22 Apr 2025 09:40:09 -0400 Subject: [PATCH 04/11] chore: minor clean up --- bash/migrate/falcon-linux-migrate.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bash/migrate/falcon-linux-migrate.sh b/bash/migrate/falcon-linux-migrate.sh index becbbdbd..49b58fc6 100755 --- a/bash/migrate/falcon-linux-migrate.sh +++ b/bash/migrate/falcon-linux-migrate.sh @@ -907,7 +907,6 @@ EOF handle_curl_error $? if [ "$(echo "$response" | json_value "updated" | xargs)" == "true" ]; then - log "INFO" "Successfully set tags on host" return 0 else log "WARNING" "Failed to set tags: $response" @@ -1268,7 +1267,7 @@ main() { # Merge existing sensor tags with any new tags specified if [ -n "$FALCON_TAGS" ]; then sensor_tags=$(merge_tags "$sensor_tags" "$FALCON_TAGS") - log "INFO" "Merged sensor tags: $sensor_tags" + log "INFO" "Sensor grouping tags to be migrated: $sensor_tags" fi # Set the tags for the sensor install @@ -1304,7 +1303,7 @@ main() { # Merge existing falcon tags with any new falcon tags specified if [ -n "$FALCON_GROUPING_TAGS" ]; then falcon_tags=$(merge_tags "$falcon_tags" "$FALCON_GROUPING_TAGS") - log "INFO" "Merged Falcon grouping tags: $falcon_tags" + log "INFO" "Falcon grouping tags to be migrated: $falcon_tags" fi # Format tags for API request @@ -1340,10 +1339,11 @@ main() { # Remove recovery file when done if [ -f "$recovery_file" ]; then log "INFO" "Removing recovery file..." - # rm -f "$recovery_file" + rm -f "$recovery_file" fi log "INFO" "Falcon sensor migration completed successfully" + log "INFO" "Log file: $log_file" } main "$@" From 4a35eddd51aff689c9ec05748e55cf790fc36d40 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Tue, 22 Apr 2025 10:22:05 -0400 Subject: [PATCH 05/11] chore: add refactoring to certain aspects --- bash/migrate/falcon-linux-migrate.sh | 157 ++++++++++++++++----------- 1 file changed, 91 insertions(+), 66 deletions(-) diff --git a/bash/migrate/falcon-linux-migrate.sh b/bash/migrate/falcon-linux-migrate.sh index 49b58fc6..a458a6c4 100755 --- a/bash/migrate/falcon-linux-migrate.sh +++ b/bash/migrate/falcon-linux-migrate.sh @@ -969,6 +969,90 @@ merge_tags() { echo "$merged_tags" } +# Function to wait for and set Falcon grouping tags after sensor installation +set_falcon_grouping_tags() { + local migrate_tags=$1 + local falcon_tags=$2 + + # Early return if we don't need to migrate tags + if [ "$migrate_tags" != "true" ] || [ -z "$falcon_tags" ]; then + log "INFO" "Skipping Falcon grouping tags migration (migrate_tags=$migrate_tags, falcon_tags=${falcon_tags:-empty})" + return 0 + fi + + log "INFO" "Waiting for new sensor registration before setting Falcon grouping tags..." + + # Wait for the new AID to be available + local max_wait=60 + local wait_count=0 + local new_aid="" + + while [ $wait_count -lt $max_wait ]; do + new_aid=$(get_aid) + if [ -n "$new_aid" ]; then + break + fi + log "INFO" "Waiting for new AID to be available... ($wait_count/$max_wait)" + wait_count=$((wait_count + 1)) + sleep 5 + done + + if [ -z "$new_aid" ]; then + log "WARNING" "Could not obtain new AID after $max_wait attempts" + return 1 + fi + + log "INFO" "New AID obtained: $new_aid" + + # Merge existing falcon tags with any new falcon tags specified + if [ -n "$FALCON_GROUPING_TAGS" ]; then + falcon_tags=$(merge_tags "$falcon_tags" "$FALCON_GROUPING_TAGS") + log "INFO" "Falcon grouping tags to be migrated: $falcon_tags" + fi + + # Format tags for API request + local formatted_tags + formatted_tags=$(format_tags_for_api "$falcon_tags" "FalconGroupingTags") + + # Set falcon tags via API + log "INFO" "Setting Falcon grouping tags via API..." + local max_attempts=5 + local attempt=1 + local success=false + + while [ $attempt -le $max_attempts ] && [ "$success" = false ]; do + if set_falcon_tags "$new_aid" "$formatted_tags"; then + success=true + else + log "WARNING" "Failed to set Falcon tags, attempt $attempt of $max_attempts" + attempt=$((attempt + 1)) + sleep 5 + fi + done + + if [ "$success" = true ]; then + log "INFO" "Successfully set Falcon grouping tags" + return 0 + else + log "WARNING" "Failed to set Falcon grouping tags after $max_attempts attempts" + return 1 + fi +} + +# Set vars needed for shared auth functions +authenticate_to_falcon() { + local client_id=$1 + local client_secret=$2 + local cloud=$3 + local cid=$4 + + FALCON_CLIENT_ID=$client_id + FALCON_CLIENT_SECRET=$client_secret + FALCON_CID=$cid + cs_falcon_cloud="${cloud:-us-1}" + get_oauth_token +} + #### start processing set -e @@ -1195,12 +1279,11 @@ main() { touch "$log_file" echo "Migration file created at: $log_file" echo "Migration started at $(date)" >> "$log_file" + # auth with old credentials - FALCON_CLIENT_ID=$OLD_FALCON_CLIENT_ID - FALCON_CLIENT_SECRET=$OLD_FALCON_CLIENT_SECRET - cs_falcon_cloud="${OLD_FALCON_CLOUD:-us-1}" log "INFO" "Authenticating to old CID..." - get_oauth_token + authenticate_to_falcon "$OLD_FALCON_CLIENT_ID" "$OLD_FALCON_CLIENT_SECRET" "$OLD_FALCON_CLOUD" + # Check if we are in recovery mode local recovery_mode=false if [ -f "$recovery_file" ]; then @@ -1254,12 +1337,8 @@ main() { # Install new sensor # auth with new credentials - FALCON_CLIENT_ID=$NEW_FALCON_CLIENT_ID - FALCON_CLIENT_SECRET=$NEW_FALCON_CLIENT_SECRET - FALCON_CID=$NEW_FALCON_CID - cs_falcon_cloud="${NEW_FALCON_CLOUD:-us-1}" log "INFO" "Authenticating to new CID..." - get_oauth_token + authenticate_to_falcon "$NEW_FALCON_CLIENT_ID" "$NEW_FALCON_CLIENT_SECRET" "$NEW_FALCON_CLOUD" "$NEW_FALCON_CID" log "INFO" "Checking if tags need to be migrated..." # Handle tags if migrating @@ -1277,63 +1356,9 @@ main() { log "INFO" "Installing Falcon sensor to new CID..." install_sensor | tee -a "$log_file" - # If migrate tags - make sure all tags get added - # Set Falcon grouping tags if migrating tags and falcon tags exist - if [ "$migrate_tags" = "true" ] && [ -n "$falcon_tags" ]; then - log "INFO" "Waiting for new sensor registration before setting Falcon grouping tags..." - - # Wait for the new AID to be available - local max_wait=60 - local wait_count=0 - local new_aid="" - - while [ $wait_count -lt $max_wait ]; do - new_aid=$(get_aid) - if [ -n "$new_aid" ]; then - break - fi - log "INFO" "Waiting for new AID to be available... ($wait_count/$max_wait)" - wait_count=$((wait_count + 1)) - sleep 5 - done - - if [ -n "$new_aid" ]; then - log "INFO" "New AID obtained: $new_aid" - - # Merge existing falcon tags with any new falcon tags specified - if [ -n "$FALCON_GROUPING_TAGS" ]; then - falcon_tags=$(merge_tags "$falcon_tags" "$FALCON_GROUPING_TAGS") - log "INFO" "Falcon grouping tags to be migrated: $falcon_tags" - fi - - # Format tags for API request - local formatted_tags - formatted_tags=$(format_tags_for_api "$falcon_tags" "FalconGroupingTags") - - # Set falcon tags via API - log "INFO" "Setting Falcon grouping tags via API..." - local max_attempts=5 - local attempt=1 - local success=false - - while [ $attempt -le $max_attempts ] && [ "$success" = false ]; do - if set_falcon_tags "$new_aid" "$formatted_tags"; then - success=true - else - log "WARNING" "Failed to set Falcon tags, attempt $attempt of $max_attempts" - attempt=$((attempt + 1)) - sleep 5 - fi - done - - if [ "$success" = true ]; then - log "INFO" "Successfully set Falcon grouping tags" - else - log "WARNING" "Failed to set Falcon grouping tags after $max_attempts attempts" - fi - else - log "WARNING" "Could not obtain new AID after $max_wait attempts" - fi + # Set Falcon grouping tags if needed + if [ ! "$(set_falcon_grouping_tags "$migrate_tags" "$falcon_tag")" ]; then + log "WARNING" "There was an issue setting the Falcon grouping tags" fi # Remove recovery file when done From 4b59875ce7c5bfbe6ede43a4c6b0b833058716b9 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Tue, 22 Apr 2025 10:45:09 -0400 Subject: [PATCH 06/11] fix: fixes issue with grouping tags not being set Incorrect logic was being applied --- bash/migrate/falcon-linux-migrate.sh | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/bash/migrate/falcon-linux-migrate.sh b/bash/migrate/falcon-linux-migrate.sh index a458a6c4..33f41c19 100755 --- a/bash/migrate/falcon-linux-migrate.sh +++ b/bash/migrate/falcon-linux-migrate.sh @@ -974,9 +974,15 @@ set_falcon_grouping_tags() { local migrate_tags=$1 local falcon_tags=$2 - # Early return if we don't need to migrate tags - if [ "$migrate_tags" != "true" ] || [ -z "$falcon_tags" ]; then - log "INFO" "Skipping Falcon grouping tags migration (migrate_tags=$migrate_tags, falcon_tags=${falcon_tags:-empty})" + # Skip if we're not migrating tags AND there are no new tags to add + if [ "$migrate_tags" != "true" ] && [ -z "$FALCON_GROUPING_TAGS" ]; then + log "INFO" "Skipping Falcon grouping tags (migrate_tags=$migrate_tags, no new tags specified)" + return 0 + fi + + # Skip if we have no existing tags to migrate AND no new tags to add + if [ -z "$falcon_tags" ] && [ -z "$FALCON_GROUPING_TAGS" ]; then + log "INFO" "Skipping Falcon grouping tags (no existing or new tags to apply)" return 0 fi @@ -1004,15 +1010,20 @@ set_falcon_grouping_tags() { log "INFO" "New AID obtained: $new_aid" - # Merge existing falcon tags with any new falcon tags specified + # If we have new tags to add, merge them with existing tags + local tags_to_apply="$falcon_tags" if [ -n "$FALCON_GROUPING_TAGS" ]; then - falcon_tags=$(merge_tags "$falcon_tags" "$FALCON_GROUPING_TAGS") - log "INFO" "Falcon grouping tags to be migrated: $falcon_tags" + if [ -n "$tags_to_apply" ]; then + tags_to_apply=$(merge_tags "$tags_to_apply" "$FALCON_GROUPING_TAGS") + else + tags_to_apply="$FALCON_GROUPING_TAGS" + fi + log "INFO" "Final Falcon grouping tags to apply: $tags_to_apply" fi # Format tags for API request local formatted_tags - formatted_tags=$(format_tags_for_api "$falcon_tags" "FalconGroupingTags") + formatted_tags=$(format_tags_for_api "$tags_to_apply" "FalconGroupingTags") # Set falcon tags via API log "INFO" "Setting Falcon grouping tags via API..." @@ -1357,7 +1368,7 @@ main() { install_sensor | tee -a "$log_file" # Set Falcon grouping tags if needed - if [ ! "$(set_falcon_grouping_tags "$migrate_tags" "$falcon_tag")" ]; then + if ! set_falcon_grouping_tags "$migrate_tags" "$falcon_tags"; then log "WARNING" "There was an issue setting the Falcon grouping tags" fi From 88692299841eb74b6f57701e96de66127d712fd3 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Tue, 22 Apr 2025 10:57:20 -0400 Subject: [PATCH 07/11] doc: add readme for migrate script --- bash/migrate/README.md | 264 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 bash/migrate/README.md diff --git a/bash/migrate/README.md b/bash/migrate/README.md new file mode 100644 index 00000000..f77f0fa0 --- /dev/null +++ b/bash/migrate/README.md @@ -0,0 +1,264 @@ +# Falcon Linux Migration Script + +Bash script to migrate Falcon sensor from one CID to another through the Falcon APIs on a Linux endpoint. By default, this script will uninstall the sensor from the old CID, install it in the new CID, and migrate any existing sensor and Falcon grouping tags. The script also creates a recovery file to facilitate resuming the migration in case of failure. + +## Security Recommendations + +### Use cURL version 7.55.0 or newer + +We have identified a security concern related to cURL versions prior to 7.55, which required request headers to be set using the `-H` option, thus allowing potential secrets to be exposed via the command line. In newer versions of cURL, you can pass headers from stdin using the `@-` syntax, which addresses this security concern. Although our script offers compatibility with the older method by allowing you to set the environment variable `ALLOW_LEGACY_CURL=true`, we strongly urge you to upgrade cURL if your environment permits. + +To check your version of cURL, run the following command: `curl --version` + +## Table of Contents + +- [Falcon API Permissions](#falcon-api-permissions) +- [Configuration](#configuration) + - [Setting up Authentication](#setting-up-authentication) +- [Usage](#usage) + - [Examples](#examples) +- [Troubleshooting](#troubleshooting) + +## Falcon API Permissions + +API clients are granted one or more API scopes. Scopes allow access to specific CrowdStrike APIs and describe the actions that an API client can perform. + +Ensure the following API scopes are enabled for both the old and new CIDs: + +- **Sensor Download** [read] + > Required for downloading the Falcon Sensor installation package. + +- **Installation Tokens** [read] + > Required if your environment enforces installation tokens for Falcon Sensor installation. + +- **Sensor update policies** [read] + > Required when using the `FALCON_SENSOR_UPDATE_POLICY_NAME` environment variable to specify a sensor update policy. + +- **Sensor update policies** [write] + > Required if you want the script to automatically retrieve a maintenance token from the API. + > Not needed if you directly provide the maintenance token via the `FALCON_MAINTENANCE_TOKEN` environment variable. + > Maintenance tokens are required to uninstall sensors that have uninstall protection enabled. + +- **Hosts** [read] + > Required for retrieving sensor and Falcon grouping tags. + +- **Hosts** [write] + > Required for setting Falcon grouping tags and when using the `FALCON_REMOVE_HOST=true` environment variable. + > + > :warning: + > It is recommended to use Host Retention Policies in the Falcon console instead. + +## Configuration + +### Setting up Authentication + +You must provide API credentials for both the old and new CIDs: + +```bash +# Old CID credentials +export OLD_FALCON_CLIENT_ID="XXXXXXX" +export OLD_FALCON_CLIENT_SECRET="YYYYYYYYY" +export OLD_FALCON_CLOUD="us-1" # Optional, defaults to us-1 + +# New CID credentials +export NEW_FALCON_CLIENT_ID="ZZZZZZZ" +export NEW_FALCON_CLIENT_SECRET="WWWWWWW" +export NEW_FALCON_CLOUD="us-2" # Optional, defaults to us-1 +export NEW_FALCON_CID="AAAAAAAAAAA" # Optional, will be auto-detected if not provided +``` + +#### Auto-Discovery of Falcon Cloud Region + +> [!IMPORTANT] +> Auto-discovery is only available for [us-1, us-2, eu-1] regions. + +The scripts support auto-discovery of the Falcon cloud region. If the `[OLD|NEW]FALCON_CLOUD` environment variable is not set, the script will attempt to auto-discover it. If you want to set the cloud region manually, or if your region does not support auto-discovery, you can set the `[OLD|NEW]FALCON_CLOUD` environment variable: + +```bash +export [OLD|NEW]FALCON_CLOUD="us-gov-1" +``` + +## Usage + +```terminal +Usage: falcon-linux-migrate.sh [-h|--help] + +Migrates the Falcon sensor to another Falcon CID. +Version: 1.7.4 + +This script recognizes the following environmental variables: + +Old CID Authentication: + - OLD_FALCON_CLIENT_ID (default: unset) [Required] + Your CrowdStrike Falcon API client ID for the old CID. + + - OLD_FALCON_CLIENT_SECRET (default: unset) [Required] + Your CrowdStrike Falcon API client secret for the old CID. + + - OLD_FALCON_MEMBER_CID (default: unset) + Member CID, used only in multi-CID ("Falcon Flight Control") configurations and + with a parent management CID for the old CID. + + - OLD_FALCON_CLOUD (default: 'us-1') + The cloud region where your old CrowdStrike Falcon instance is hosted. + Accepted values are ['us-1', 'us-2', 'eu-1', 'us-gov-1']. + +New CID Authentication: + - NEW_FALCON_CLIENT_ID (default: unset) [Required] + Your CrowdStrike Falcon API client ID for the new CID. + + - NEW_FALCON_CLIENT_SECRET (default: unset) [Required] + Your CrowdStrike Falcon API client secret for the new CID. + + - NEW_FALCON_MEMBER_CID (default: unset) + Member CID, used only in multi-CID ("Falcon Flight Control") configurations and + with a parent management CID for the new CID. + + - NEW_FALCON_CLOUD (default: 'us-1') + The cloud region where your new CrowdStrike Falcon instance is hosted. + Accepted values are ['us-1', 'us-2', 'eu-1', 'us-gov-1']. + + - NEW_FALCON_CID (default: unset) + Your CrowdStrike Falcon customer ID (CID) for the new CID. + If not specified, will be detected automatically via API. + +Migration Options: + - MIGRATE_TAGS (default: true) + Migrate the host's existing tags to the target CID. + Accepted values are ['true', 'false']. + + - LOG_PATH (default: /tmp) + Location for the log and recovery files. + +Other Options + - FALCON_MAINTENANCE_TOKEN (default: unset) + Sensor uninstall maintenance token used to unlock sensor uninstallation. + If not provided the script will try to retrieve the token from the API. + + - FALCON_PROVISIONING_TOKEN (default: unset) + The provisioning token to use for installing the sensor. + If the provisioning token is unset, the script will attempt to retrieve it from + the API using your authentication credentials and token requirements. + + - FALCON_REMOVE_HOST (default: unset) + Determines whether the host should be removed from the Falcon console after uninstalling the sensor. + Requires API Authentication. + NOTE: It is recommended to use Host Retention Policies in the Falcon console instead. + Accepted values are ['true', 'false']. + + - FALCON_SENSOR_VERSION_DECREMENT (default: 0 [latest]) + The number of versions prior to the latest release to install. + For example, 1 would install version N-1. + + - FALCON_SENSOR_UPDATE_POLICY_NAME (default: unset) + The name of the sensor update policy to use for installing the sensor. + + - FALCON_TAGS (default: unset) + A comma-separated list of sensor grouping tags to apply to the host. + If MIGRATE_TAGS=true these tags will be appended to any existing sensor tags. + + - FALCON_GROUPING_TAGS (default: unset) + A comma-separated list of Falcon grouping tags to apply to the host. + If MIGRATE_TAGS=true these tags will be appended to any existing grouping tags. + + - FALCON_APD (default: unset) + Configures if the proxy should be enabled or disabled. + + - FALCON_APH (default: unset) + The proxy host for the sensor to use when communicating with CrowdStrike. + + - FALCON_APP (default: unset) + The proxy port for the sensor to use when communicating with CrowdStrike. + + - FALCON_BILLING (default: default) + To configure the sensor billing type. + Accepted values are [default|metered]. + + - FALCON_BACKEND (default: auto) + For sensor backend. + Accepted values are values: [auto|bpf|kernel]. + + - FALCON_TRACE (default: none) + To configure the trace level. + Accepted values are [none|err|warn|info|debug] + + - ALLOW_LEGACY_CURL (default: false) + To use the legacy version of curl; version < 7.55.0. + + - USER_AGENT (default: unset) + User agent string to append to the User-Agent header when making + requests to the CrowdStrike API. + +This script recognizes the following argument: + -h, --help + Print this help message and exit. +``` + +### Examples + +#### Migrate a sensor from US-1 to US-2 with tag migration + +```bash +export OLD_FALCON_CLIENT_ID="XXXXXXX" +export OLD_FALCON_CLIENT_SECRET="YYYYYYYYY" +export OLD_FALCON_CLOUD="us-1" +export NEW_FALCON_CLIENT_ID="ZZZZZZZ" +export NEW_FALCON_CLIENT_SECRET="WWWWWWW" +export NEW_FALCON_CLOUD="us-2" +curl -L https://raw.githubusercontent.com/crowdstrike/falcon-scripts/v1.7.4/bash/migrate/falcon-linux-migrate.sh | sudo bash +``` + +#### Migrate a sensor to EU-1 with removal from old console + +```bash +export OLD_FALCON_CLIENT_ID="XXXXXXX" +export OLD_FALCON_CLIENT_SECRET="YYYYYYYYY" +export OLD_FALCON_CLOUD="us-1" +export NEW_FALCON_CLIENT_ID="ZZZZZZZ" +export NEW_FALCON_CLIENT_SECRET="WWWWWWW" +export NEW_FALCON_CLOUD="eu-1" +export FALCON_REMOVE_HOST="true" +curl -L https://raw.githubusercontent.com/crowdstrike/falcon-scripts/v1.7.4/bash/migrate/falcon-linux-migrate.sh | sudo bash +``` + +#### Migrate a sensor with custom tags + +```bash +export OLD_FALCON_CLIENT_ID="XXXXXXX" +export OLD_FALCON_CLIENT_SECRET="YYYYYYYYY" +export NEW_FALCON_CLIENT_ID="ZZZZZZZ" +export NEW_FALCON_CLIENT_SECRET="WWWWWWW" +export FALCON_TAGS="department/it,location/hq" +export FALCON_GROUPING_TAGS="environment/production,criticality/high" +curl -L https://raw.githubusercontent.com/crowdstrike/falcon-scripts/v1.7.4/bash/migrate/falcon-linux-migrate.sh | sudo bash +``` + +#### Migrate a sensor from one CID to another within the same cloud + +```bash +export OLD_FALCON_CLIENT_ID="XXXXXXX" +export OLD_FALCON_CLIENT_SECRET="YYYYYYYYY" +export OLD_FALCON_CLOUD="us-1" +export NEW_FALCON_CLIENT_ID="ZZZZZZZ" +export NEW_FALCON_CLIENT_SECRET="WWWWWWW" +export NEW_FALCON_CLOUD="us-1" +curl -L https://raw.githubusercontent.com/crowdstrike/falcon-scripts/v1.7.4/bash/migrate/falcon-linux-migrate.sh | sudo bash +``` + +## Troubleshooting + +To troubleshoot migration issues, you can run the script with `bash -x` for detailed output: + +```bash +bash -x falcon-linux-migrate.sh +``` + +or + +```bash +curl -L https://raw.githubusercontent.com/crowdstrike/falcon-scripts/v1.7.4/bash/migrate/falcon-linux-migrate.sh | bash -x +``` + +The script creates a log file at the location specified by `LOG_PATH` (defaults to `/tmp`) with the name format `falcon_migration_YYYYMMDD_HHMMSS.log`. This log contains detailed information about each step of the migration process. + +If the migration process is interrupted, the script creates a recovery file at `$LOG_PATH/falcon_migration_recovery.csv` that contains information about the previous sensor's AID and tags. When rerunning the script, it will detect this file and attempt to continue the migration process. From 296557fb7a17db0a5c2d4ee2a9841fa6c0ecd3eb Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Tue, 22 Apr 2025 11:26:06 -0400 Subject: [PATCH 08/11] ci: adding molecule ci for linux migrate --- molecule/bash_migrate/converge.yml | 25 ++++++++++++++++++++ molecule/bash_migrate/molecule.yml | 37 ++++++++++++++++++++++++++++++ molecule/bash_migrate/prepare.yml | 28 ++++++++++++++++++++++ molecule/bash_migrate/verify.yml | 35 ++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 molecule/bash_migrate/converge.yml create mode 100644 molecule/bash_migrate/molecule.yml create mode 100644 molecule/bash_migrate/prepare.yml create mode 100644 molecule/bash_migrate/verify.yml diff --git a/molecule/bash_migrate/converge.yml b/molecule/bash_migrate/converge.yml new file mode 100644 index 00000000..eff9c387 --- /dev/null +++ b/molecule/bash_migrate/converge.yml @@ -0,0 +1,25 @@ +--- +- name: Converge + hosts: all + gather_facts: false + become: true + environment: + OLD_FALCON_CLIENT_ID: "{{ lookup('env', 'FALCON_CLIENT_ID') }}" + OLD_FALCON_CLIENT_SECRET: "{{ lookup('env', 'FALCON_CLIENT_SECRET') }}" + NEW_FALCON_CLIENT_ID: "{{ lookup('env', 'FALCON_CLIENT_ID') }}" + NEW_FALCON_CLIENT_SECRET: "{{ lookup('env', 'FALCON_CLIENT_SECRET') }}" + ALLOW_LEGACY_CURL: "true" + FALCON_REMOVE_HOST: "true" + FALCON_TAGS: "migrate1,migrate2" + FALCON_GROUPING_TAGS: "fg-migrate1,fg-migrate2" + tasks: + # Execute shell command + - name: Migrate Falcon Sensor + ansible.builtin.script: + cmd: ../../bash/migrate/falcon-linux-migrate.sh + register: falcon_install + + # Print stdout + - name: Task STDOUT + ansible.builtin.debug: + msg: "{{ falcon_install.stdout_lines }}" diff --git a/molecule/bash_migrate/molecule.yml b/molecule/bash_migrate/molecule.yml new file mode 100644 index 00000000..f4faa952 --- /dev/null +++ b/molecule/bash_migrate/molecule.yml @@ -0,0 +1,37 @@ +--- +dependency: + name: galaxy +driver: + name: ec2 +# The Default Platform is Ubunutu 20.04 LTS | T2.Micro | us-west-2 +platforms: + - name: "${MOLECULE_INSTANCE_NAME:-default-bash-install}" + image_owner: "${MOLECULE_IMAGE_OWNER:-099720109477}" + image_filters: + - architecture: "${MOLECULE_IMAGE_ARCH:-x86_64}" + - name: "${MOLECULE_IMAGE_NAME:-ubuntu/images/hvm-ssd/ubuntu-focal-20.04*}" + instance_type: "${MOLECULE_INSTANCE_TYPE:-t2.micro}" + region: "${MOLECULE_REGION:-us-west-2}" + vpc_subnet_id: "${MOLECULE_VPC_SUBNET_ID}" + security_group_restrict_cidr_ip: "${MOLECULE_SECURITY_GROUP_RESTRICT_CIDR_IP:-true}" + boot_wait_seconds: ${MOLECULE_BOOT_WAIT_SECONDS:-60} +provisioner: + name: ansible + config_options: + defaults: + stdout_callback: yaml + playbooks: + create: ../shared/playbooks/create.yml + destroy: ../shared/playbooks/destroy.yml +verifier: + name: ansible +scenario: + test_sequence: + - dependency + - syntax + - create + - prepare + - converge + - side_effect + - verify + - destroy diff --git a/molecule/bash_migrate/prepare.yml b/molecule/bash_migrate/prepare.yml new file mode 100644 index 00000000..8cc8468e --- /dev/null +++ b/molecule/bash_migrate/prepare.yml @@ -0,0 +1,28 @@ +--- +- name: Prepare (Default) + hosts: all + become: true + environment: + FALCON_CLIENT_ID: "{{ lookup('env', 'FALCON_CLIENT_ID') }}" + FALCON_CLIENT_SECRET: "{{ lookup('env', 'FALCON_CLIENT_SECRET') }}" + ALLOW_LEGACY_CURL: "true" + tasks: + # Ubuntu specific + - name: Install apt dependencies + ansible.builtin.apt: + name: + - gpg-agent + - curl + update_cache: true + when: ansible_pkg_mgr == 'apt' + + - name: Install dependencies + ansible.builtin.package: + name: + - sudo + state: present + + - name: Install Falcon Sensor + ansible.builtin.script: + cmd: ../../bash/install/falcon-linux-install.sh + register: falcon_install diff --git a/molecule/bash_migrate/verify.yml b/molecule/bash_migrate/verify.yml new file mode 100644 index 00000000..e5421daa --- /dev/null +++ b/molecule/bash_migrate/verify.yml @@ -0,0 +1,35 @@ +--- +- name: Verify (Default) + hosts: all + gather_facts: false + become: true + pre_tasks: + - name: Set default sensor name and service + ansible.builtin.set_fact: + installed_sensor: falcon-sensor + sensor_service: falcon-sensor.service + + - name: Get list of installed packages + ansible.builtin.package_facts: + manager: auto + + - name: Collect status of falcon-sensor service + ansible.builtin.service_facts: + + - name: Get CID + ansible.builtin.command: /opt/CrowdStrike/falconctl -g --cid + register: get_cid + changed_when: false + + tasks: + - name: Verify Falcon Sensor is installed + ansible.builtin.assert: + that: installed_sensor in ansible_facts.packages + + - name: Verify falcon-sensor service is active and running + ansible.builtin.assert: + that: ansible_facts.services[sensor_service].state == 'running' + + - name: Verify CID + ansible.builtin.assert: + that: "'cid=' in get_cid.stdout" From a0cc66381f9830ae1e924ff42a8205512abcb11f Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Tue, 22 Apr 2025 11:46:43 -0400 Subject: [PATCH 09/11] ci: adding new workflow for linux migrate --- .github/workflows/bash_migrate.yml | 163 +++++++++++++++++++++++++++++ molecule/bash_migrate/molecule.yml | 2 +- 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/bash_migrate.yml diff --git a/.github/workflows/bash_migrate.yml b/.github/workflows/bash_migrate.yml new file mode 100644 index 00000000..280e5059 --- /dev/null +++ b/.github/workflows/bash_migrate.yml @@ -0,0 +1,163 @@ +name: "CI: bash_migrate" +on: + schedule: + - cron: '0 4 * * *' + + push: + paths: + - 'molecule/bash_migrate/**' + - 'bash/migrate/**.sh' + - '.github/workflows/bash_migrate.yml' + + pull_request_target: + types: [ labeled ] + paths: + - 'molecule/bash_migrate/**' + - 'bash/migrate/**.sh' + - '.github/workflows/bash_migrate.yml' + +jobs: + molecule: + if: | + github.event_name == 'push' || + github.event_name == 'schedule' || + (github.event_name == 'pull_request_target' && + github.event.label.name == 'ok-to-test') + name: ${{ matrix.molecule.distro }}-${{ matrix.collection_role }} + runs-on: ubuntu-latest + env: + PY_COLORS: 1 + ANSIBLE_FORCE_COLOR: 1 + FALCON_CLIENT_ID: ${{ secrets.FALCON_CLIENT_ID }} + FALCON_CLIENT_SECRET: ${{ secrets.FALCON_CLIENT_SECRET }} + AWS_REGION: "us-west-1" + MOLECULE_VPC_SUBNET_ID: ${{ secrets.MOLECULE_VPC_SUBNET_ID }} + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + molecule: + - distro: ubuntu-20.04 + image_owner: '099720109477' + image_arch: x86_64 + image_name: ubuntu/images/hvm-ssd/ubuntu-focal-20.04* + instance_type: t2.micro + - distro: ubuntu-22.04 + image_owner: '099720109477' + image_arch: x86_64 + image_name: ubuntu/images/hvm-ssd/ubuntu-jammy-22.04* + instance_type: t2.micro + - distro: amazon-2023 + image_owner: '137112412989' + image_arch: x86_64 + image_name: al2023-ami-2023* + instance_type: t2.micro + - distro: amazon-2 + image_owner: '137112412989' + image_arch: x86_64 + image_name: amzn2-ami-hvm-2.0*gp2 + instance_type: t2.micro + - distro: sles-15-sp5 + image_owner: '013907871322' + image_arch: x86_64 + image_name: suse-sles-15-sp5-v????????-hvm* + instance_type: t2.micro + - distro: almalinux-8 + image_owner: '679593333241' + image_arch: x86_64 + image_name: AlmaLinux OS 8* + instance_type: t2.micro + - distro: rhel-9 + image_owner: '309956199498' + image_arch: x86_64 + image_name: RHEL-9.?.?_HVM-* + instance_type: t2.micro + - distro: rhel-9-arm + image_owner: '309956199498' + image_arch: arm64 + image_name: RHEL-9.?.?_HVM-* + instance_type: t4g.micro + - distro: debian-11 + image_owner: '136693071363' + image_arch: x86_64 + image_name: debian-11-amd64-* + instance_type: t2.micro + collection_role: + - bash_migrate + + steps: + - name: Check out code + uses: actions/checkout@v4 + if: github.event_name != 'pull_request_target' + + - name: Check out code + uses: actions/checkout@v4 + with: + ref: ${{github.event.pull_request.head.sha}} + if: github.event_name == 'pull_request_target' + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_OIDC_ROLE }} + role-session-name: github-actions-molecule-ansible + aws-region: ${{ env.AWS_REGION }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: 'pip' + cache-dependency-path: '.github/workflows/bash_install.yml' + + - name: Install dependencies + run: | + sudo apt install apt-transport-https ca-certificates curl software-properties-common libssl-dev + python -m pip install --upgrade pip + pip install molecule "molecule-plugins[ec2]" ansible ansible-core==2.16.7 ansible-lint boto3 botocore + + - name: Run role tests + id: molecule-role-test + uses: nick-fields/retry@v3 + env: + MOLECULE_INSTANCE_NAME: ${{ matrix.molecule.distro }}-${{ matrix.collection_role }} + MOLECULE_IMAGE_OWNER: ${{ matrix.molecule.image_owner }} + MOLECULE_IMAGE_ARCH: ${{ matrix.molecule.image_arch }} + MOLECULE_IMAGE_NAME: '${{ matrix.molecule.image_name }}' + MOLECULE_INSTANCE_TYPE: ${{ matrix.molecule.instance_type }} + MOLECULE_REGION: ${{ env.AWS_REGION}} + with: + timeout_minutes: 15 + max_attempts: 3 + retry_on: error + command: >- + molecule --version && + ansible --version && + molecule --debug test --destroy never -s ${{ matrix.collection_role }} + continue-on-error: true + + - name: Ensure instances are destroyed + uses: nick-fields/retry@v3 + env: + MOLECULE_INSTANCE_NAME: ${{ matrix.molecule.distro }}-${{ matrix.collection_role }} + MOLECULE_IMAGE_OWNER: ${{ matrix.molecule.image_owner }} + MOLECULE_IMAGE_ARCH: ${{ matrix.molecule.image_arch }} + MOLECULE_IMAGE_NAME: '${{ matrix.molecule.image_name }}' + MOLECULE_INSTANCE_TYPE: ${{ matrix.molecule.instance_type }} + MOLECULE_REGION: ${{ env.AWS_REGION}} + with: + timeout_minutes: 10 + max_attempts: 3 + retry_on: error + command: >- + molecule --version && + ansible --version && + molecule --debug destroy -s ${{ matrix.collection_role }} + + - name: Assert molecule tests passed + uses: nick-fields/assert-action@v2 + with: + expected: success + actual: ${{ steps.molecule-role-test.outcome }} diff --git a/molecule/bash_migrate/molecule.yml b/molecule/bash_migrate/molecule.yml index f4faa952..748ce1da 100644 --- a/molecule/bash_migrate/molecule.yml +++ b/molecule/bash_migrate/molecule.yml @@ -5,7 +5,7 @@ driver: name: ec2 # The Default Platform is Ubunutu 20.04 LTS | T2.Micro | us-west-2 platforms: - - name: "${MOLECULE_INSTANCE_NAME:-default-bash-install}" + - name: "${MOLECULE_INSTANCE_NAME:-default-bash-migrate}" image_owner: "${MOLECULE_IMAGE_OWNER:-099720109477}" image_filters: - architecture: "${MOLECULE_IMAGE_ARCH:-x86_64}" From e3c0dfcb6cab99a0cf5fb8899f4fc1c4d98b70a2 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Tue, 22 Apr 2025 11:55:03 -0400 Subject: [PATCH 10/11] chore: fix dash shellcheck error --- bash/migrate/falcon-linux-migrate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bash/migrate/falcon-linux-migrate.sh b/bash/migrate/falcon-linux-migrate.sh index 33f41c19..32c8ffb2 100755 --- a/bash/migrate/falcon-linux-migrate.sh +++ b/bash/migrate/falcon-linux-migrate.sh @@ -906,7 +906,7 @@ EOF handle_curl_error $? - if [ "$(echo "$response" | json_value "updated" | xargs)" == "true" ]; then + if [ "$(echo "$response" | json_value "updated" | xargs)" = "true" ]; then return 0 else log "WARNING" "Failed to set tags: $response" From 4aa5f3365c16d00bd8f7753140673ca9ff244cd8 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Tue, 22 Apr 2025 16:58:07 -0400 Subject: [PATCH 11/11] added support for member_cid --- bash/migrate/falcon-linux-migrate.sh | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/bash/migrate/falcon-linux-migrate.sh b/bash/migrate/falcon-linux-migrate.sh index 32c8ffb2..35fe89c5 100755 --- a/bash/migrate/falcon-linux-migrate.sh +++ b/bash/migrate/falcon-linux-migrate.sh @@ -304,6 +304,13 @@ get_falcon_credentials() { 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 ) + + # Adding suppport for member_cid + cs_falcon_member_cid=$( + if [ -n "$FALCON_MEMBER_CID" ]; then + echo "$FALCON_MEMBER_CID" + fi + ) else if [ -z "$FALCON_CLOUD" ]; then die "If setting the FALCON_ACCESS_TOKEN manually, you must also specify the FALCON_CLOUD" @@ -329,7 +336,14 @@ get_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" | + # Build the auth request payload, adding member_cid if specified + auth_payload="client_id=$cs_falcon_client_id&client_secret=$cs_falcon_client_secret" + + if [ -n "$cs_falcon_member_cid" ]; then + auth_payload="${auth_payload}&member_cid=${cs_falcon_member_cid}" + fi + + token_result=$(echo "$auth_payload" | 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: $(get_user_agent)" \ @@ -1056,11 +1070,13 @@ authenticate_to_falcon() { local client_secret=$2 local cloud=$3 local cid=$4 + local member_cid=$5 FALCON_CLIENT_ID=$client_id FALCON_CLIENT_SECRET=$client_secret FALCON_CID=$cid cs_falcon_cloud="${cloud:-us-1}" + cs_falcon_member_cid=$member_cid get_oauth_token } @@ -1293,7 +1309,7 @@ main() { # auth with old credentials log "INFO" "Authenticating to old CID..." - authenticate_to_falcon "$OLD_FALCON_CLIENT_ID" "$OLD_FALCON_CLIENT_SECRET" "$OLD_FALCON_CLOUD" + authenticate_to_falcon "$OLD_FALCON_CLIENT_ID" "$OLD_FALCON_CLIENT_SECRET" "$OLD_FALCON_CLOUD" "$OLD_FALCON_MEMBER_CID" # Check if we are in recovery mode local recovery_mode=false @@ -1349,7 +1365,7 @@ main() { # Install new sensor # auth with new credentials log "INFO" "Authenticating to new CID..." - authenticate_to_falcon "$NEW_FALCON_CLIENT_ID" "$NEW_FALCON_CLIENT_SECRET" "$NEW_FALCON_CLOUD" "$NEW_FALCON_CID" + authenticate_to_falcon "$NEW_FALCON_CLIENT_ID" "$NEW_FALCON_CLIENT_SECRET" "$NEW_FALCON_CLOUD" "$NEW_FALCON_CID" "$NEW_FALCON_MEMBER_CID" log "INFO" "Checking if tags need to be migrated..." # Handle tags if migrating