Validate FCM service account credentials

This commit is contained in:
SimpleTest 2026-07-23 20:51:51 +03:00
parent a7f076514c
commit 6582b61dc4
7 changed files with 141 additions and 12 deletions

View File

@ -332,8 +332,12 @@ fcm_credentials =
raise "Set only one of FCM_SERVICE_ACCOUNT_FILE or FCM_SERVICE_ACCOUNT_JSON_BASE64." raise "Set only one of FCM_SERVICE_ACCOUNT_FILE or FCM_SERVICE_ACCOUNT_JSON_BASE64."
end end
if fcm_credentials && fcm_credentials["type"] != "service_account" do if fcm_credentials &&
raise "The configured FCM credentials must be a Google service-account document." (fcm_credentials["type"] != "service_account" ||
Enum.any?(["project_id", "client_email", "private_key"], fn field ->
not is_binary(fcm_credentials[field]) or fcm_credentials[field] == ""
end)) do
raise "The configured FCM credentials must be a complete Google service-account document."
end end
fcm_configuration = fcm_configuration =

View File

@ -70,6 +70,15 @@ contains_template_marker() {
"$observed" == *example.com* || "$observed" == *example.invalid* ]] "$observed" == *example.com* || "$observed" == *example.invalid* ]]
} }
valid_fcm_service_account_json() {
jq -e '
.type == "service_account" and
(.project_id | type == "string" and length > 0) and
(.client_email | type == "string" and length > 0) and
(.private_key | type == "string" and length > 0)
' >/dev/null 2>&1
}
failures=0 failures=0
warnings=0 warnings=0
@ -178,16 +187,19 @@ elif [[ -z "$(value FCM_PROJECT_ID)" || (-n "$fcm_file" && -n "$fcm_base64") ||
(-z "$fcm_file" && -z "$fcm_base64") ]]; then (-z "$fcm_file" && -z "$fcm_base64") ]]; then
partial "Android FCM delivery" "project ID and exactly one credential source are required" partial "Android FCM delivery" "project ID and exactly one credential source are required"
elif [[ -n "$fcm_file" ]]; then elif [[ -n "$fcm_file" ]]; then
if [[ "$fcm_file" == /* && -r "$fcm_file" ]]; then if [[ "$fcm_file" == /* && -r "$fcm_file" ]] &&
ready "Android FCM delivery" "readable service-account file is configured" valid_fcm_service_account_json <"$fcm_file"; then
ready "Android FCM delivery" "complete service-account file is configured"
else else
invalid "Android FCM delivery" "FCM_SERVICE_ACCOUNT_FILE must be an absolute readable file" invalid "Android FCM delivery" \
"FCM_SERVICE_ACCOUNT_FILE must be an absolute readable complete service-account JSON file"
fi fi
elif printf '%s' "$fcm_base64" | base64 --decode 2>/dev/null | elif printf '%s' "$fcm_base64" | base64 --decode 2>/dev/null |
jq -e '.type == "service_account" and (.project_id | type == "string")' >/dev/null 2>&1; then valid_fcm_service_account_json; then
ready "Android FCM delivery" "valid Base64 service-account document is configured" ready "Android FCM delivery" "complete Base64 service-account document is configured"
else else
invalid "Android FCM delivery" "Base64 credential is not a service-account JSON document" invalid "Android FCM delivery" \
"Base64 credential is not a complete service-account JSON document"
fi fi
if all_empty ANDROID_APP_LINKS_PACKAGE_NAME ANDROID_APP_LINKS_SHA256_CERT_FINGERPRINTS; then if all_empty ANDROID_APP_LINKS_PACKAGE_NAME ANDROID_APP_LINKS_SHA256_CERT_FINGERPRINTS; then

View File

@ -114,6 +114,26 @@ if { [ -n "$fcm_project_id" ] || [ -n "$fcm_service_account_json_base64" ]; } &&
exit 1 exit 1
fi fi
if [ -n "$fcm_service_account_json_base64" ]; then
for command in base64 jq; do
command -v "$command" >/dev/null 2>&1 || {
echo "Required command is unavailable for FCM validation: $command" >&2
exit 1
}
done
if ! printf '%s' "$fcm_service_account_json_base64" |
base64 --decode 2>/dev/null |
jq -e '
.type == "service_account" and
(.project_id | type == "string" and length > 0) and
(.client_email | type == "string" and length > 0) and
(.private_key | type == "string" and length > 0)
' >/dev/null 2>&1; then
echo "Production FCM credential is not a complete service-account JSON document." >&2
exit 1
fi
fi
case "$compose_project_name" in case "$compose_project_name" in
*[!a-zA-Z0-9_-]* | '') *[!a-zA-Z0-9_-]* | '')
echo "PRODUCTION_COMPOSE_PROJECT_NAME must contain only letters, numbers, underscores, or hyphens." >&2 echo "PRODUCTION_COMPOSE_PROJECT_NAME must contain only letters, numbers, underscores, or hyphens." >&2

View File

@ -125,6 +125,26 @@ if [[ (-n "$fcm_project_id" || -n "$fcm_service_account_json_base64") &&
exit 1 exit 1
fi fi
if [[ -n "$fcm_service_account_json_base64" ]]; then
for command in base64 jq; do
command -v "$command" >/dev/null 2>&1 || {
echo "Required command is unavailable for FCM validation: $command" >&2
exit 1
}
done
if ! printf '%s' "$fcm_service_account_json_base64" |
base64 --decode 2>/dev/null |
jq -e '
.type == "service_account" and
(.project_id | type == "string" and length > 0) and
(.client_email | type == "string" and length > 0) and
(.private_key | type == "string" and length > 0)
' >/dev/null 2>&1; then
echo "Test FCM credential is not a complete service-account JSON document." >&2
exit 1
fi
fi
for pair in "HTTP:$http_port" "MAILPIT:$mailpit_port"; do for pair in "HTTP:$http_port" "MAILPIT:$mailpit_port"; do
value=${pair#*:} value=${pair#*:}
if [[ ! "$value" =~ ^[0-9]+$ ]] || ((value < 1 || value > 65535)); then if [[ ! "$value" =~ ^[0-9]+$ ]] || ((value < 1 || value > 65535)); then

View File

@ -117,7 +117,8 @@ test "$(sha256sum "$legacy_load_env" | awk '{print $1}')" = "$legacy_load_hash"
echo "Checking independent test and production environment initialization" echo "Checking independent test and production environment initialization"
quality_fcm_base64=$( quality_fcm_base64=$(
printf '%s' '{"type":"service_account","project_id":"quality-production"}' | printf '%s' \
'{"type":"service_account","project_id":"quality-production","client_email":"quality-fcm@quality-production.iam.gserviceaccount.com","private_key":"quality-private-key"}' |
base64 -w 0 base64 -w 0
) )
test_env="$scan_dir/test.env" test_env="$scan_dir/test.env"
@ -206,6 +207,25 @@ PRODUCTION_SUPPORT_INBOX_ADDRESS=support@help.test \
test "$(stat -c '%a' "$production_env")" = 600 test "$(stat -c '%a' "$production_env")" = 600
./scripts/validate-production-env.sh "$production_env" help.test >/dev/null ./scripts/validate-production-env.sh "$production_env" help.test >/dev/null
./scripts/check-environment-readiness.sh "$production_env" --require-release >/dev/null ./scripts/check-environment-readiness.sh "$production_env" --require-release >/dev/null
invalid_fcm_env="$scan_dir/production.invalid-fcm.env"
invalid_fcm_base64=$(
printf '%s' '{"type":"service_account","project_id":"quality-production"}' |
base64 -w 0
)
cp "$production_env" "$invalid_fcm_env"
sed -i \
"s|^FCM_SERVICE_ACCOUNT_JSON_BASE64=.*|FCM_SERVICE_ACCOUNT_JSON_BASE64=$invalid_fcm_base64|" \
"$invalid_fcm_env"
if ./scripts/validate-production-env.sh \
"$invalid_fcm_env" help.test >/dev/null 2>&1; then
echo "Production validation accepted an incomplete FCM service account." >&2
exit 1
fi
if ./scripts/check-environment-readiness.sh \
"$invalid_fcm_env" --require-release >/dev/null 2>&1; then
echo "Environment readiness accepted an incomplete FCM service account." >&2
exit 1
fi
grep -Fx 'COMPOSE_PROJECT_NAME=who_need_help_production' "$production_env" >/dev/null grep -Fx 'COMPOSE_PROJECT_NAME=who_need_help_production' "$production_env" >/dev/null
grep -E '^APP_IMAGE=who-need-help:production-[0-9a-f]{12}$' "$production_env" >/dev/null grep -E '^APP_IMAGE=who-need-help:production-[0-9a-f]{12}$' "$production_env" >/dev/null
grep -Fx 'EDGE_COMPOSE_PROJECT_NAME=who_need_help_edge' "$production_env" >/dev/null grep -Fx 'EDGE_COMPOSE_PROJECT_NAME=who_need_help_edge' "$production_env" >/dev/null

View File

@ -59,6 +59,15 @@ require_value() {
printf '%s' "$value" printf '%s' "$value"
} }
valid_fcm_service_account_json() {
jq -e '
.type == "service_account" and
(.project_id | type == "string" and length > 0) and
(.client_email | type == "string" and length > 0) and
(.private_key | type == "string" and length > 0)
' >/dev/null 2>&1
}
reject_marker() { reject_marker() {
local key=$1 local key=$1
local value=$2 local value=$2
@ -378,10 +387,18 @@ if [[ -n "$fcm_project_id" || -n "$fcm_service_account_file" ||
} }
if [[ -n "$fcm_service_account_file" ]]; then if [[ -n "$fcm_service_account_file" ]]; then
command -v jq >/dev/null 2>&1 || {
echo "Required command is unavailable for FCM validation: jq" >&2
exit 1
}
[[ "$fcm_service_account_file" == /* && -r "$fcm_service_account_file" ]] || { [[ "$fcm_service_account_file" == /* && -r "$fcm_service_account_file" ]] || {
echo "FCM_SERVICE_ACCOUNT_FILE must be an absolute readable file." >&2 echo "FCM_SERVICE_ACCOUNT_FILE must be an absolute readable file." >&2
exit 1 exit 1
} }
valid_fcm_service_account_json <"$fcm_service_account_file" || {
echo "FCM_SERVICE_ACCOUNT_FILE is not a complete service-account JSON document." >&2
exit 1
}
else else
for command in base64 jq; do for command in base64 jq; do
command -v "$command" >/dev/null 2>&1 || { command -v "$command" >/dev/null 2>&1 || {
@ -391,9 +408,8 @@ if [[ -n "$fcm_project_id" || -n "$fcm_service_account_file" ||
done done
printf '%s' "$fcm_service_account_json_base64" | printf '%s' "$fcm_service_account_json_base64" |
base64 --decode 2>/dev/null | base64 --decode 2>/dev/null |
jq -e '.type == "service_account" and (.project_id | type == "string")' \ valid_fcm_service_account_json || {
>/dev/null 2>&1 || { echo "FCM_SERVICE_ACCOUNT_JSON_BASE64 is not a complete service-account JSON document." >&2
echo "FCM_SERVICE_ACCOUNT_JSON_BASE64 is not a service-account JSON document." >&2
exit 1 exit 1
} }
fi fi

View File

@ -41,6 +41,15 @@ require_value() {
printf '%s' "$value" printf '%s' "$value"
} }
valid_fcm_service_account_json() {
jq -e '
.type == "service_account" and
(.project_id | type == "string" and length > 0) and
(.client_email | type == "string" and length > 0) and
(.private_key | type == "string" and length > 0)
' >/dev/null 2>&1
}
[[ "$(require_value DEPLOYMENT_ENV)" == test ]] || { [[ "$(require_value DEPLOYMENT_ENV)" == test ]] || {
echo "Test validation requires DEPLOYMENT_ENV=test." >&2 echo "Test validation requires DEPLOYMENT_ENV=test." >&2
exit 1 exit 1
@ -174,6 +183,34 @@ if [[ -n "$fcm_project_id" || -n "$fcm_service_account_file" ||
echo "Test FCM project ID and exactly one credential source are required together." >&2 echo "Test FCM project ID and exactly one credential source are required together." >&2
exit 1 exit 1
} }
if [[ -n "$fcm_service_account_file" ]]; then
command -v jq >/dev/null 2>&1 || {
echo "Required command is unavailable for FCM validation: jq" >&2
exit 1
}
[[ "$fcm_service_account_file" == /* && -r "$fcm_service_account_file" ]] || {
echo "Test FCM_SERVICE_ACCOUNT_FILE must be an absolute readable file." >&2
exit 1
}
valid_fcm_service_account_json <"$fcm_service_account_file" || {
echo "Test FCM_SERVICE_ACCOUNT_FILE is not a complete service-account JSON document." >&2
exit 1
}
else
for command in base64 jq; do
command -v "$command" >/dev/null 2>&1 || {
echo "Required command is unavailable for FCM validation: $command" >&2
exit 1
}
done
printf '%s' "$fcm_service_account_json_base64" |
base64 --decode 2>/dev/null |
valid_fcm_service_account_json || {
echo "Test FCM_SERVICE_ACCOUNT_JSON_BASE64 is not a complete service-account JSON document." >&2
exit 1
}
fi
fi fi
android_package=$(read_value ANDROID_APP_LINKS_PACKAGE_NAME 2>/dev/null || true) android_package=$(read_value ANDROID_APP_LINKS_PACKAGE_NAME 2>/dev/null || true)