Add read-only production runtime audit
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
name: Audit production runtime (read-only)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: audit-production-runtime
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Inspect production over SSH
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
host: ${{ secrets.EC2_HOST }}
|
||||
username: ubuntu
|
||||
key: ${{ secrets.EC2_SSH_KEY }}
|
||||
command_timeout: 10m
|
||||
script: |
|
||||
set -eu
|
||||
cd ~/app
|
||||
|
||||
printf '%s\n' '=== source ==='
|
||||
printf 'git_sha='
|
||||
git rev-parse HEAD
|
||||
printf 'git_branch='
|
||||
git branch --show-current
|
||||
|
||||
cd infra/docker
|
||||
ai_id=$(sudo docker compose -f docker-compose.prod.yml ps -q ai-service)
|
||||
web_id=$(sudo docker compose -f docker-compose.prod.yml ps -q web)
|
||||
postgres_id=$(sudo docker compose -f docker-compose.prod.yml ps -q postgres)
|
||||
qdrant_id=$(sudo docker compose -f docker-compose.prod.yml ps -q qdrant)
|
||||
test -n "$ai_id"
|
||||
test -n "$web_id"
|
||||
test -n "$postgres_id"
|
||||
test -n "$qdrant_id"
|
||||
|
||||
printf '%s\n' '=== containers ==='
|
||||
for entry in "ai-service:$ai_id" "web:$web_id" "postgres:$postgres_id" "qdrant:$qdrant_id"; do
|
||||
service=${entry%%:*}
|
||||
container=${entry#*:}
|
||||
state=$(sudo docker inspect --format '{{.State.Status}}' "$container")
|
||||
image_id=$(sudo docker inspect --format '{{.Image}}' "$container")
|
||||
printf '%s state=%s image_id=%s\n' "$service" "$state" "$image_id"
|
||||
done
|
||||
|
||||
printf '%s\n' '=== ai_runtime_contract ==='
|
||||
sudo docker exec -i "$ai_id" python - <<'PY'
|
||||
import json
|
||||
|
||||
from config import Settings
|
||||
|
||||
settings = Settings()
|
||||
safe_fields = (
|
||||
"app_name",
|
||||
"environment",
|
||||
"qdrant_url",
|
||||
"qdrant_collection",
|
||||
"embedding_provider",
|
||||
"embedding_dimensions",
|
||||
"evidence_minimum_score",
|
||||
"aws_region",
|
||||
"answer_provider",
|
||||
"answer_model_id",
|
||||
"rerank_enabled",
|
||||
"metrics_enabled",
|
||||
"otel_enabled",
|
||||
"otel_service_name",
|
||||
"otel_exporter_otlp_endpoint",
|
||||
"otel_sample_ratio",
|
||||
"entities_path",
|
||||
"max_wall_clock_ms",
|
||||
"max_llm_calls_per_turn",
|
||||
)
|
||||
contract = {name: str(getattr(settings, name)) for name in safe_fields}
|
||||
print(json.dumps(contract, ensure_ascii=True, sort_keys=True))
|
||||
PY
|
||||
|
||||
printf '%s\n' '=== persistent_mounts ==='
|
||||
for entry in "postgres:$postgres_id" "qdrant:$qdrant_id"; do
|
||||
service=${entry%%:*}
|
||||
container=${entry#*:}
|
||||
sudo docker inspect --format \
|
||||
"$service {{range .Mounts}}{{.Type}}:{{.Name}}:{{.Destination}} {{end}}" \
|
||||
"$container"
|
||||
done
|
||||
|
||||
printf '%s\n' '=== datastore_identity ==='
|
||||
sudo docker exec -i "$ai_id" python - <<'PY'
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
from config import Settings
|
||||
|
||||
settings = Settings()
|
||||
url = settings.qdrant_url.rstrip("/") + "/collections/" + settings.qdrant_collection
|
||||
with urllib.request.urlopen(url, timeout=10) as response:
|
||||
payload = json.load(response)
|
||||
result = payload.get("result", {})
|
||||
config = result.get("config", {}).get("params", {}).get("vectors", {})
|
||||
print(json.dumps({
|
||||
"collection": settings.qdrant_collection,
|
||||
"points_count": result.get("points_count"),
|
||||
"status": result.get("status"),
|
||||
"vector_config": config,
|
||||
}, ensure_ascii=True, sort_keys=True))
|
||||
PY
|
||||
|
||||
printf '%s\n' '=== health ==='
|
||||
sudo docker exec -i "$ai_id" python - <<'PY'
|
||||
import urllib.request
|
||||
|
||||
for path in ("/health", "/ready"):
|
||||
with urllib.request.urlopen("http://127.0.0.1:8000" + path, timeout=10) as response:
|
||||
print(path, response.status)
|
||||
PY
|
||||
Reference in New Issue
Block a user