feat(deployment): add secure Linux runtime (#17)
This commit is contained in:
Executable
+148
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SERVICE_URL="${RENDEZVOUS_SMOKE_HTTP_URL:-http://127.0.0.1:8080/}"
|
||||
MEDIATOR="${RENDEZVOUS_SMOKE_UDP_ENDPOINT:-127.0.0.1:9050}"
|
||||
TIMEOUT_SECONDS="${RENDEZVOUS_SMOKE_TIMEOUT_SECONDS:-30}"
|
||||
LOCAL_KEY="${RENDEZVOUS_SMOKE_LOCAL_KEY:-$ROOT/deploy/compose/secrets/signing-key}"
|
||||
PROJECT="$ROOT/src/FinalFactory.Rendezvous.TestClient/FinalFactory.Rendezvous.TestClient.csproj"
|
||||
BUILD_CONFIGURATION="${RENDEZVOUS_SMOKE_CONFIGURATION:-Release}"
|
||||
GAME_ID="${RENDEZVOUS_SMOKE_GAME_ID:-space-game}"
|
||||
ENVIRONMENT_ID="${RENDEZVOUS_SMOKE_ENVIRONMENT_ID:-smoke}"
|
||||
REGION="${RENDEZVOUS_SMOKE_REGION:-local}"
|
||||
PROTOCOL_VERSION="${RENDEZVOUS_SMOKE_PROTOCOL_VERSION:-1}"
|
||||
|
||||
for command in curl date dotnet jq mktemp od openssl tail tr wc; do
|
||||
command -v "$command" >/dev/null || {
|
||||
printf 'Missing required command: %s\n' "$command" >&2
|
||||
exit 2
|
||||
}
|
||||
done
|
||||
|
||||
if [[ ! "$TIMEOUT_SECONDS" =~ ^[0-9]+$ ]] || (( TIMEOUT_SECONDS < 1 || TIMEOUT_SECONDS > 300 )); then
|
||||
printf 'RENDEZVOUS_SMOKE_TIMEOUT_SECONDS must be an integer from 1 through 300.\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ ! "$PROTOCOL_VERSION" =~ ^[0-9]+$ ]] || (( PROTOCOL_VERSION < 1 )); then
|
||||
printf 'RENDEZVOUS_SMOKE_PROTOCOL_VERSION must be a positive integer.\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
for scoped_value in "$GAME_ID" "$ENVIRONMENT_ID" "$REGION"; do
|
||||
if [[ -z "$scoped_value" ]]; then
|
||||
printf 'Smoke game, environment, and region values must not be empty.\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
done
|
||||
|
||||
base64url() {
|
||||
openssl base64 -A | tr '+/' '-_' | tr -d '='
|
||||
}
|
||||
|
||||
local_credential() {
|
||||
if [[ ! -f "$LOCAL_KEY" ]] || [[ "$(wc -c < "$LOCAL_KEY")" -ne 32 ]]; then
|
||||
printf 'Local Compose smoke key must be exactly 32 bytes: %s\n' "$LOCAL_KEY" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
local now expires nonce payload encoded signed hex signature
|
||||
now="$(date +%s)"
|
||||
expires="$((now + 600))"
|
||||
nonce="$(openssl rand -hex 16)"
|
||||
payload="$(jq -cn \
|
||||
--arg issuer final-factory-rendezvous-smoke \
|
||||
--arg audience rendezvous-service \
|
||||
--arg subject local-smoke-host \
|
||||
--arg kind dedicatedPublisher \
|
||||
--arg gameId "$GAME_ID" \
|
||||
--arg environmentId "$ENVIRONMENT_ID" \
|
||||
--arg region "$REGION" \
|
||||
--arg nonce "$nonce" \
|
||||
--argjson now "$now" \
|
||||
--argjson expires "$expires" \
|
||||
'{version:1,issuer:$issuer,audience:$audience,subject:$subject,kind:$kind,gameId:$gameId,environmentId:$environmentId,regions:[$region],permissions:[],issuedAtUnixSeconds:$now,notBeforeUnixSeconds:$now,expiresAtUnixSeconds:$expires,nonce:$nonce}')"
|
||||
encoded="$(printf '%s' "$payload" | base64url)"
|
||||
signed="rv1.local-smoke-1.$encoded"
|
||||
hex="$(od -An -v -tx1 "$LOCAL_KEY" | tr -d ' \n')"
|
||||
signature="$(printf '%s' "$signed" \
|
||||
| openssl dgst -sha256 -mac HMAC -macopt "hexkey:$hex" -binary \
|
||||
| base64url)"
|
||||
printf '%s.%s' "$signed" "$signature"
|
||||
}
|
||||
|
||||
credential="${RENDEZVOUS_PUBLISHER_CREDENTIAL:-}"
|
||||
if [[ -z "$credential" ]]; then
|
||||
credential="$(local_credential)"
|
||||
fi
|
||||
export RENDEZVOUS_PUBLISHER_CREDENTIAL="$credential"
|
||||
|
||||
curl --fail --silent --show-error --max-time 5 "${SERVICE_URL%/}/health/live" >/dev/null
|
||||
curl --fail --silent --show-error --max-time 5 "${SERVICE_URL%/}/health/ready" >/dev/null
|
||||
|
||||
temp_dir="$(mktemp -d)"
|
||||
host_log="$temp_dir/host.jsonl"
|
||||
join_log="$temp_dir/join.jsonl"
|
||||
host_pid=''
|
||||
cleanup() {
|
||||
local status="$?"
|
||||
if [[ -n "$host_pid" ]] && kill -0 "$host_pid" 2>/dev/null; then
|
||||
kill -TERM "$host_pid" 2>/dev/null || true
|
||||
wait "$host_pid" 2>/dev/null || true
|
||||
fi
|
||||
if [[ "$status" -ne 0 ]]; then
|
||||
printf 'Deployment smoke failed; sanitized diagnostic events follow.\n' >&2
|
||||
[[ -f "$host_log" ]] && jq -c . "$host_log" >&2 || true
|
||||
[[ -f "$join_log" ]] && jq -c . "$join_log" >&2 || true
|
||||
fi
|
||||
rm -rf "$temp_dir"
|
||||
return "$status"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
|
||||
dotnet run --project "$PROJECT" --configuration "$BUILD_CONFIGURATION" --no-build -- \
|
||||
host --service "$SERVICE_URL" --mediator "$MEDIATOR" \
|
||||
--game "$GAME_ID" --environment "$ENVIRONMENT_ID" --region "$REGION" --protocol "$PROTOCOL_VERSION" \
|
||||
--script --json --exit-after-echo --timeout-seconds "$TIMEOUT_SECONDS" \
|
||||
>"$host_log" 2>&1 &
|
||||
host_pid="$!"
|
||||
|
||||
ready=false
|
||||
for ((iteration = 0; iteration < TIMEOUT_SECONDS * 4; iteration++)); do
|
||||
if jq -e 'select(.event == "host.ready")' "$host_log" >/dev/null 2>&1; then
|
||||
ready=true
|
||||
break
|
||||
fi
|
||||
if ! kill -0 "$host_pid" 2>/dev/null; then
|
||||
printf 'Host diagnostic stopped before it became ready.\n' >&2
|
||||
jq -c . "$host_log" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 0.25
|
||||
done
|
||||
if [[ "$ready" != true ]]; then
|
||||
printf 'Host diagnostic did not become ready within %s seconds.\n' "$TIMEOUT_SECONDS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
listing_id="$(jq -r 'select(.event == "host.registered") | .listingId' "$host_log" | tail -n 1)"
|
||||
if [[ -z "$listing_id" || "$listing_id" == null ]]; then
|
||||
printf 'Host diagnostic did not report a listing ID.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotnet run --project "$PROJECT" --configuration "$BUILD_CONFIGURATION" --no-build -- \
|
||||
join --service "$SERVICE_URL" --mediator "$MEDIATOR" \
|
||||
--game "$GAME_ID" --environment "$ENVIRONMENT_ID" --region "$REGION" --protocol "$PROTOCOL_VERSION" \
|
||||
--listing "$listing_id" --script --json --timeout-seconds "$TIMEOUT_SECONDS" \
|
||||
>"$join_log" 2>&1
|
||||
wait "$host_pid"
|
||||
host_pid=''
|
||||
|
||||
jq -e 'select(.event == "host.direct-traffic" and .status == "verified")' "$host_log" >/dev/null
|
||||
jq -e 'select(.event == "host.deregistered" and .status == "complete")' "$host_log" >/dev/null
|
||||
jq -e 'select(.event == "join.direct-traffic" and .status == "verified")' "$join_log" >/dev/null
|
||||
jq -e 'select(.event == "join.outcome-report" and .status == "accepted")' "$join_log" >/dev/null
|
||||
|
||||
printf 'Rendezvous deployment smoke passed: HTTP live/ready and authenticated UDP mediation/direct traffic.\n'
|
||||
Reference in New Issue
Block a user