diff --git a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh index d5ed8aa..fc379cc 100755 --- a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh +++ b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh @@ -284,19 +284,58 @@ handle_curl_error() { curl_command() { # Dash does not support arrays, so we have to pass the args as separate arguments - local token="$1" escaped_token auth_config + local token="$1" escaped_token auth_config headers body status hint old_host new_host arg rc shift # The configuration value must be quoted, because it holds a space and a # colon. curl processes backslash escapes inside a quoted value, so a # backslash or a double quote in the token has to be escaped first. escaped_token=$(printf '%s' "$token" | sed 's/\\/\\\\/g; s/"/\\"/g') auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token") + + headers=$(mktemp) + body=$(mktemp) + # No -L: the bearer token must never cross a redirect hop. The body is held + # back so that a redirect body is not emitted ahead of the retry's. printf '%s\n' "$auth_config" | - curl -s -L --proto '=https' --proto-redir '=https' -K- "$@" + curl -s --proto '=https' --dump-header "$headers" -K- "$@" >"$body" + rc=$? + + # A wrong region answers with a redirect naming the right one in x-cs-region. + # Re-issue against that region instead of following Location. The registry is + # a different host, so its URLs are never rewritten. + status=$(awk '/^HTTP\//{s=$2} END{print s}' "$headers") + case "$status" in + 301 | 302 | 307 | 308) + hint=$(grep -i ^x-cs-region: "$headers" | head -n 1 | tr '[:upper:]' '[:lower:]' | tr -d '\r' | sed 's/^x-cs-region: //g') + if [ -n "$hint" ]; then + old_host=$(cs_cloud) + # cs_cloud() validates the hint against its own allowlist. Check + # for empty rather than trusting its die, which does not stop bash. + new_host=$(cs_cloud "$hint") + if [ -n "$new_host" ] && [ "$new_host" != "$old_host" ]; then + for arg in "$@"; do + shift + case "$arg" in + "https://$old_host/"*) + arg="https://$new_host/${arg#"https://$old_host/"}" + ;; + esac + set -- "$@" "$arg" + done + printf '%s\n' "$auth_config" | + curl -s --proto '=https' -K- "$@" >"$body" + rc=$? + fi + fi + ;; + esac + + cat "$body" + rm -f "$headers" "$body" + return "$rc" } fetch_tags() { - # No -L, so --proto-redir is dropped too; nothing follows a redirect here. bearer_result=$(echo "-u $ART_USERNAME:$ART_PASSWORD" | curl -s --proto '=https' \ "https://$cs_registry/v2/token?account=$ART_USERNAME&scope=repository:$registry_opts/$repository_name:pull&service=$cs_registry" -K-) diff --git a/bash/install/falcon-linux-install.sh b/bash/install/falcon-linux-install.sh index fc66c99..5e3d918 100755 --- a/bash/install/falcon-linux-install.sh +++ b/bash/install/falcon-linux-install.sh @@ -704,14 +704,54 @@ handle_curl_error() { curl_command() { # Dash does not support arrays, so we have to pass the args as separate arguments - local escaped_token auth_config + local escaped_token auth_config headers body status hint old_host new_host arg rc # The configuration value must be quoted, because it holds a space and a # colon. curl processes backslash escapes inside a quoted value, so a # backslash or a double quote in the token has to be escaped first. escaped_token=$(printf '%s' "$cs_falcon_oauth_token" | sed 's/\\/\\\\/g; s/"/\\"/g') auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token") + + headers=$(mktemp) + body=$(mktemp) + # No -L: the bearer token must never cross a redirect hop. The body is held + # back so that a redirect body is not emitted ahead of the retry's. printf '%s\n' "$auth_config" | - curl -s -x "$proxy" -L --proto '=https' --proto-redir '=https' -K- "$@" + curl -s -x "$proxy" --proto '=https' --dump-header "$headers" -K- "$@" >"$body" + rc=$? + + # A wrong region answers with a redirect naming the right one in x-cs-region. + # Re-issue against that region instead of following Location. Take the last + # status line, because a proxy CONNECT dumps one of its own first. + status=$(awk '/^HTTP\//{s=$2} END{print s}' "$headers") + case "$status" in + 301 | 302 | 307 | 308) + hint=$(grep -i ^x-cs-region: "$headers" | head -n 1 | tr '[:upper:]' '[:lower:]' | tr -d '\r' | sed 's/^x-cs-region: //g') + if [ -n "$hint" ]; then + old_host=$(cs_cloud) + # cs_cloud() validates the hint against its own allowlist. Check + # for empty rather than trusting its die, which does not stop bash. + new_host=$(cs_cloud "$hint") + if [ -n "$new_host" ] && [ "$new_host" != "$old_host" ]; then + for arg in "$@"; do + shift + case "$arg" in + "https://$old_host/"*) + arg="https://$new_host/${arg#"https://$old_host/"}" + ;; + esac + set -- "$@" "$arg" + done + printf '%s\n' "$auth_config" | + curl -s -x "$proxy" --proto '=https' -K- "$@" >"$body" + rc=$? + fi + fi + ;; + esac + + cat "$body" + rm -f "$headers" "$body" + return "$rc" } check_aws_instance() { @@ -829,10 +869,11 @@ get_oauth_token() { 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 [ "${FALCON_CLOUD}" != "${region_hint}" ]; then - echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2 - fi + elif [ -n "${region_hint}" ] && [ "${FALCON_CLOUD}" != "${region_hint}" ]; then + echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2 + # Use the hint. The API answers the wrong region with a redirect, which + # curl_command no longer follows. + cs_falcon_cloud="${region_hint}" fi fi diff --git a/bash/install/falcon-linux-uninstall.sh b/bash/install/falcon-linux-uninstall.sh index 52abf39..12d7835 100755 --- a/bash/install/falcon-linux-uninstall.sh +++ b/bash/install/falcon-linux-uninstall.sh @@ -250,14 +250,54 @@ get_maintenance_token() { curl_command() { # Dash does not support arrays, so we have to pass the args as separate arguments - local escaped_token auth_config + local escaped_token auth_config headers body status hint old_host new_host arg rc # The configuration value must be quoted, because it holds a space and a # colon. curl processes backslash escapes inside a quoted value, so a # backslash or a double quote in the token has to be escaped first. escaped_token=$(printf '%s' "$cs_falcon_oauth_token" | sed 's/\\/\\\\/g; s/"/\\"/g') auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token") + + headers=$(mktemp) + body=$(mktemp) + # No -L: the bearer token must never cross a redirect hop. The body is held + # back so that a redirect body is not emitted ahead of the retry's. printf '%s\n' "$auth_config" | - curl -s -x "$proxy" -L --proto '=https' --proto-redir '=https' -K- "$@" + curl -s -x "$proxy" --proto '=https' --dump-header "$headers" -K- "$@" >"$body" + rc=$? + + # A wrong region answers with a redirect naming the right one in x-cs-region. + # Re-issue against that region instead of following Location. Take the last + # status line, because a proxy CONNECT dumps one of its own first. + status=$(awk '/^HTTP\//{s=$2} END{print s}' "$headers") + case "$status" in + 301 | 302 | 307 | 308) + hint=$(grep -i ^x-cs-region: "$headers" | head -n 1 | tr '[:upper:]' '[:lower:]' | tr -d '\r' | sed 's/^x-cs-region: //g') + if [ -n "$hint" ]; then + old_host=$(cs_cloud) + # cs_cloud() validates the hint against its own allowlist. Check + # for empty rather than trusting its die, which does not stop bash. + new_host=$(cs_cloud "$hint") + if [ -n "$new_host" ] && [ "$new_host" != "$old_host" ]; then + for arg in "$@"; do + shift + case "$arg" in + "https://$old_host/"*) + arg="https://$new_host/${arg#"https://$old_host/"}" + ;; + esac + set -- "$@" "$arg" + done + printf '%s\n' "$auth_config" | + curl -s -x "$proxy" --proto '=https' -K- "$@" >"$body" + rc=$? + fi + fi + ;; + esac + + cat "$body" + rm -f "$headers" "$body" + return "$rc" } handle_curl_error() { @@ -490,10 +530,11 @@ get_oauth_token() { 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 [ "${FALCON_CLOUD}" != "${region_hint}" ]; then - echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2 - fi + elif [ -n "${region_hint}" ] && [ "${FALCON_CLOUD}" != "${region_hint}" ]; then + echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2 + # Use the hint. The API answers the wrong region with a redirect, which + # curl_command no longer follows. + cs_falcon_cloud="${region_hint}" fi fi diff --git a/bash/migrate/falcon-linux-migrate.sh b/bash/migrate/falcon-linux-migrate.sh index df0850f..20f8c9c 100755 --- a/bash/migrate/falcon-linux-migrate.sh +++ b/bash/migrate/falcon-linux-migrate.sh @@ -228,14 +228,54 @@ fi curl_command() { # Dash does not support arrays, so we have to pass the args as separate arguments - local escaped_token auth_config + local escaped_token auth_config headers body status hint old_host new_host arg rc # The configuration value must be quoted, because it holds a space and a # colon. curl processes backslash escapes inside a quoted value, so a # backslash or a double quote in the token has to be escaped first. escaped_token=$(printf '%s' "$cs_falcon_oauth_token" | sed 's/\\/\\\\/g; s/"/\\"/g') auth_config=$(printf 'header = "Authorization: Bearer %s"' "$escaped_token") + + headers=$(mktemp) + body=$(mktemp) + # No -L: the bearer token must never cross a redirect hop. The body is held + # back so that a redirect body is not emitted ahead of the retry's. printf '%s\n' "$auth_config" | - curl -s -x "$proxy" -L --proto '=https' --proto-redir '=https' -K- "$@" + curl -s -x "$proxy" --proto '=https' --dump-header "$headers" -K- "$@" >"$body" + rc=$? + + # A wrong region answers with a redirect naming the right one in x-cs-region. + # Re-issue against that region instead of following Location. Take the last + # status line, because a proxy CONNECT dumps one of its own first. + status=$(awk '/^HTTP\//{s=$2} END{print s}' "$headers") + case "$status" in + 301 | 302 | 307 | 308) + hint=$(grep -i ^x-cs-region: "$headers" | head -n 1 | tr '[:upper:]' '[:lower:]' | tr -d '\r' | sed 's/^x-cs-region: //g') + if [ -n "$hint" ]; then + old_host=$(cs_cloud) + # cs_cloud() validates the hint against its own allowlist. Check + # for empty rather than trusting its die, which does not stop bash. + new_host=$(cs_cloud "$hint") + if [ -n "$new_host" ] && [ "$new_host" != "$old_host" ]; then + for arg in "$@"; do + shift + case "$arg" in + "https://$old_host/"*) + arg="https://$new_host/${arg#"https://$old_host/"}" + ;; + esac + set -- "$@" "$arg" + done + printf '%s\n' "$auth_config" | + curl -s -x "$proxy" --proto '=https' -K- "$@" >"$body" + rc=$? + fi + fi + ;; + esac + + cat "$body" + rm -f "$headers" "$body" + return "$rc" } handle_curl_error() { @@ -409,10 +449,11 @@ get_oauth_token() { 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 [ "${FALCON_CLOUD}" != "${region_hint}" ]; then - echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2 - fi + elif [ -n "${region_hint}" ] && [ "${FALCON_CLOUD}" != "${region_hint}" ]; then + echo "WARNING: FALCON_CLOUD='${FALCON_CLOUD}' environment variable specified while credentials only exists in '${region_hint}'" >&2 + # Use the hint. The API answers the wrong region with a redirect, which + # curl_command no longer follows. + cs_falcon_cloud="${region_hint}" fi fi