who_need_help/scripts/generate-vapid-env.sh

162 lines
4.0 KiB
Bash
Executable File

#!/bin/sh
set -eu
umask 077
if [ "$#" -ne 2 ]; then
echo "Usage: $0 ENV_FILE VAPID_SUBJECT" >&2
exit 1
fi
ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
env_file=$1
subject=$2
if [ ! -f "$env_file" ]; then
echo "Environment file does not exist: $env_file" >&2
exit 1
fi
if [ "$(stat -c '%a' "$env_file")" != 600 ]; then
echo "Environment file must have mode 0600: $env_file" >&2
exit 1
fi
case "$subject" in
mailto:?* | https://?*) ;;
*)
echo "VAPID_SUBJECT must be a non-empty mailto: or https:// URI without whitespace." >&2
exit 1
;;
esac
case "$subject" in
*'
'*)
echo "VAPID_SUBJECT must be a non-empty mailto: or https:// URI without whitespace." >&2
exit 1
;;
esac
if printf '%s' "$subject" | LC_ALL=C grep -q '[[:space:]]'; then
echo "VAPID_SUBJECT must be a non-empty mailto: or https:// URI without whitespace." >&2
exit 1
fi
for command in awk chmod date docker grep mktemp rm stat; do
if ! command -v "$command" >/dev/null 2>&1; then
echo "Required command is unavailable: $command" >&2
exit 1
fi
done
for key in \
WEB_PUSH_VAPID_PUBLIC_KEY \
WEB_PUSH_VAPID_PRIVATE_KEY \
WEB_PUSH_VAPID_SUBJECT; do
key_count=$(
awk -F= -v key="$key" '$1 == key { count++ } END { print count + 0 }' \
"$env_file"
)
if [ "$key_count" -ne 1 ]; then
echo "Environment must contain exactly one $key entry before VAPID generation." >&2
exit 1
fi
existing_value=$(
awk -F= -v key="$key" '
$1 == key {
print substr($0, index($0, "=") + 1)
exit
}
' "$env_file"
)
if [ -n "$existing_value" ]; then
echo "Refusing to rotate an existing VAPID identity: $key is already configured." >&2
exit 1
fi
done
unset existing_value
env_dir=$(CDPATH='' cd -- "$(dirname -- "$env_file")" && pwd)
temporary_dir=$(mktemp -d "$env_dir/.vapid-generation.XXXXXX")
chmod 700 "$temporary_dir"
raw_keys="$temporary_dir/generated.txt"
values_file="$temporary_dir/values.env"
generator_image=${WNH_VAPID_GENERATOR_IMAGE:-}
owned_image=false
run_id="$(date -u +%Y%m%d%H%M%S)-$$"
generator_container="wnh-vapid-tool-$run_id"
cleanup() {
trap - EXIT HUP INT TERM
rm -rf "$temporary_dir"
docker rm --force "$generator_container" >/dev/null 2>&1 || true
if [ "$owned_image" = true ]; then
docker image rm "$generator_image" >/dev/null 2>&1 || true
fi
}
cleanup_on_exit() {
exit_status=$?
cleanup
exit "$exit_status"
}
trap cleanup_on_exit EXIT
trap 'cleanup; exit 129' HUP
trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM
if [ -z "$generator_image" ]; then
generator_image="who-need-help:vapid-tool-$run_id"
owned_image=true
docker build --quiet --target test --tag "$generator_image" "$ROOT" >/dev/null
fi
docker image inspect "$generator_image" >/dev/null
docker run --rm \
--name "$generator_container" \
--network none \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=16m \
--entrypoint mix \
"$generator_image" \
generate.vapid.keys >"$raw_keys"
chmod 600 "$raw_keys"
if ! awk -F'"' -v subject="$subject" '
/^[[:space:]]*vapid_private_key:/ {
private_count++
private_key = $2
}
/^[[:space:]]*vapid_public_key:/ {
public_count++
public_key = $2
}
END {
valid_private = private_key ~ /^[A-Za-z0-9_-]+$/
valid_public = public_key ~ /^[A-Za-z0-9_-]+$/
if (private_count != 1 ||
public_count != 1 ||
!valid_private ||
!valid_public ||
private_key == public_key) {
exit 40
}
print "WEB_PUSH_VAPID_PUBLIC_KEY=" public_key
print "WEB_PUSH_VAPID_PRIVATE_KEY=" private_key
print "WEB_PUSH_VAPID_SUBJECT=" subject
}
' "$raw_keys" >"$values_file"; then
echo "The pinned web_push_elixir generator returned an unexpected key format." >&2
exit 1
fi
chmod 600 "$values_file"
"$ROOT/scripts/set-env-values.sh" "$env_file" "$values_file" >/dev/null
echo "Generated a new environment-specific VAPID identity without printing its keys."
echo "Updated the single mode-0600 environment file: $env_file"