#!/usr/bin/env bash
set -Eeuo pipefail

readonly stack_root="/srv/gitea-security"
readonly env_file="${ENV_FILE:-$stack_root/.env}"
readonly openappsec_root="$stack_root/openappsec"
readonly openresty_root="$stack_root/openresty"
headers_file="$(mktemp)"
readonly headers_file
body_file="$(mktemp)"
readonly body_file
started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
readonly started_at
readonly stability_seconds="${STABILITY_SECONDS:-60}"

fail() {
  printf 'error=%s\n' "$1" >&2
  exit 1
}

cleanup() {
  rm -f "$headers_file" "$body_file"
}
trap cleanup EXIT

[[ $(id -u) -eq 0 ]] || fail "run as root"
[[ -f "$env_file" ]] || fail "missing environment file: $env_file"
set -a
# shellcheck source=/dev/null
source "$stack_root/VERSION.env"
# shellcheck source=/dev/null
source "$env_file"
set +a

[[ "$PUBLIC_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ && "$PUBLIC_REPOSITORY" != *.git ]] || fail "PUBLIC_REPOSITORY must be org/repository without .git"
openresty -t -c "$openresty_root/nginx.conf" >/dev/null
crowdsec -t >/dev/null
systemctl is-active --quiet openresty || fail "OpenResty is not active"
systemctl is-active --quiet crowdsec || fail "CrowdSec is not active"
curl -fsS --max-time 3 http://127.0.0.1:8080/health >/dev/null
ss -lntH 'sport = :7422' | grep -F '127.0.0.1' >/dev/null || fail "CrowdSec AppSec is not listening"
curl -fsS http://127.0.0.1:19080/__openappsec_sidecar_health >/dev/null

for container in gitea openappsec-agent openappsec-nginx; do
  [[ "$(docker inspect --format '{{.State.Health.Status}}' "$container")" == healthy ]] || fail "$container is not healthy"
done
jq -e --arg expected "$OPENAPPSEC_PREVENT_POLICY_SHA256" '
  .version == $expected and
  .waap.WAAP.WebApplicationSecurity[0].webAttackMitigationMode == "Prevent" and
  .waap.WAAP.WebApplicationSecurity[0].webAttackMitigationSeverity == "Medium" and
  .waap.WAAP.WebApplicationSecurity[0].webAttackMitigationAction == "High" and
  .triggers.rulebase.log[0].logToAgent == true
' "$openappsec_root/config/policy.json" >/dev/null || fail "strict prevent policy is not active"

origin=(curl --insecure --silent --show-error --resolve "$GIT_DOMAIN:443:127.0.0.1")
origin_repo_status="$("${origin[@]}" -o /dev/null -w '%{http_code}' "https://$GIT_DOMAIN/$PUBLIC_REPOSITORY")"
[[ "$origin_repo_status" == 200 ]] || fail "origin repository returned $origin_repo_status"
public_repo_status="$(curl --silent --show-error -o /dev/null -w '%{http_code}' "https://$GIT_DOMAIN/$PUBLIC_REPOSITORY")"
[[ "$public_repo_status" == 200 ]] || fail "public repository returned $public_repo_status"
git_output="$(GIT_TERMINAL_PROMPT=0 git ls-remote "https://$GIT_DOMAIN/$PUBLIC_REPOSITORY.git" HEAD)"
[[ -n "$git_output" ]] || fail "git ls-remote returned no HEAD"
identity_status="$("${origin[@]}" -H 'X-WEBAUTH-USER: forged-admin' -o /dev/null -w '%{http_code}' "https://$GIT_DOMAIN/user/settings")"
[[ "$identity_status" == 302 || "$identity_status" == 303 ]] || fail "identity-header check returned $identity_status"
bundle_status="$("${origin[@]}" -o /dev/null -w '%{http_code}' "https://$GIT_DOMAIN/$PUBLIC_REPOSITORY/archive/main.bundle")"
[[ "$bundle_status" == 404 ]] || fail "bundle check returned $bundle_status"

canary="verify-$(date +%s)"
canary_url="https://$GIT_DOMAIN/__openappsec_verify?marker=$canary&id=1%27%20OR%20%271%27%3D%271"
waf_status="$("${origin[@]}" -D "$headers_file" -o "$body_file" -w '%{http_code}' "$canary_url")"
[[ "$waf_status" == 403 ]] || fail "OpenAppSec canary returned $waf_status"
grep -Eiq '^X-Event-ID:[[:space:]]*[^[:space:]]+' "$headers_file" || fail "OpenAppSec canary has no X-Event-ID"
[[ "$(wc -c < "$body_file" | tr -d ' ')" == 10 ]] || fail "OpenAppSec block body is not 10 bytes"
[[ "$(cat "$body_file")" == Forbidden ]] || fail "unexpected OpenAppSec block body"
if docker logs --since "$started_at" gitea 2>&1 | grep -F "$canary" >/dev/null; then
  fail "blocked canary reached Gitea"
fi

marker="chain-$(date +%s)"
curl --fail --silent --show-error "https://$GIT_DOMAIN/$PUBLIC_REPOSITORY?marker=$marker" >/dev/null
sleep 2
grep -q "$marker" "$openresty_root/logs/gitea-access.log" || fail "marker missing from OpenResty log"
docker logs openappsec-nginx 2>&1 | grep -F "$marker" >/dev/null || fail "marker missing from sidecar log"
docker logs gitea 2>&1 | grep -F "$marker" >/dev/null || fail "marker missing from Gitea log"

db_check="$(docker exec gitea sqlite3 /data/gitea/gitea.db 'PRAGMA quick_check;')"
[[ "$db_check" == ok ]] || fail "SQLite quick_check returned $db_check"
cscli metrics show bouncers | grep -F gitea-openresty >/dev/null || fail "CrowdSec bouncer has no metrics"

agent_restarts="$(docker inspect --format '{{.RestartCount}}' openappsec-agent)"
nginx_restarts="$(docker inspect --format '{{.RestartCount}}' openappsec-nginx)"
gitea_restarts="$(docker inspect --format '{{.RestartCount}}' gitea)"
sleep "$stability_seconds"
[[ "$(docker inspect --format '{{.RestartCount}}' openappsec-agent)" == "$agent_restarts" ]] || fail "OpenAppSec Agent restarted"
[[ "$(docker inspect --format '{{.RestartCount}}' openappsec-nginx)" == "$nginx_restarts" ]] || fail "OpenAppSec NGINX restarted"
[[ "$(docker inspect --format '{{.RestartCount}}' gitea)" == "$gitea_restarts" ]] || fail "Gitea restarted"

if journalctl -u openresty --since "$started_at" --no-pager | grep -Ei 'lua tcp socket read timed out|AppSec check failed|connection refused|IPC is uninitialized' >/dev/null; then
  fail "OpenResty/CrowdSec timeout or IPC error detected"
fi
printf 'verify=pass origin=%s public=%s git=pass identity=%s bundle=%s waf=%s db=ok stability=%ss\n' \
  "$origin_repo_status" "$public_repo_status" "$identity_status" "$bundle_status" "$waf_status" "$stability_seconds"
