Initial Commit
This commit is contained in:
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# What the sub-agent container can and cannot reach.
|
||||
#
|
||||
# ./tests/vm_agent_asserts.sh
|
||||
#
|
||||
# WHY THIS IS A SEPARATE BOUNDARY FROM THE GUEST'S
|
||||
#
|
||||
# The VM's isolation is about code somebody else wrote. This one is about
|
||||
# OUR code, and it exists because of two specific things on the other
|
||||
# side of it:
|
||||
#
|
||||
# Ollama has no authentication at all, and serves DELETE /api/delete,
|
||||
# POST /api/pull and POST /api/create. Anything that can reach
|
||||
# 172.17.0.1:11434 can delete the model the whole assistant runs on.
|
||||
#
|
||||
# The Open WebUI key has to be an ADMIN key, because anyone may invoke
|
||||
# vm_agent and the chat belongs to whoever is talking. An admin key in
|
||||
# the agent container would be an agent that can read every
|
||||
# conversation on this instance.
|
||||
#
|
||||
# So the agent reaches exactly one address, and the thing at that address
|
||||
# exposes exactly three URLs. This checks both halves.
|
||||
# =====================================================================
|
||||
set -uo pipefail
|
||||
|
||||
AGENT=owuitools-vmagent
|
||||
GW=172.22.2.10:8000
|
||||
|
||||
fails=0
|
||||
ok() { printf ' ok %s\n' "$*"; }
|
||||
bad() { printf ' FAIL %s\n' "$*"; fails=$((fails+1)); }
|
||||
|
||||
docker inspect "$AGENT" >/dev/null 2>&1 || { echo "$AGENT is not running" >&2; exit 1; }
|
||||
|
||||
echo "== 1. the agent reaches vmgw, and nothing else"
|
||||
probe=$(docker exec -i "$AGENT" python3 - <<'PY'
|
||||
import socket
|
||||
def p(h, port):
|
||||
try:
|
||||
socket.create_connection((h, port), 3).close(); return "REACHED"
|
||||
except Exception: return "blocked"
|
||||
for h, port, name in [("172.22.2.10", 8000, "vmgw"), ("172.17.0.1", 11434, "ollama-direct"),
|
||||
("172.19.0.2", 8080, "openwebui-direct"), ("192.168.1.10", 7200, "gitea-lan"),
|
||||
("192.168.1.10", 22, "host-sshd"), ("192.168.1.1", 80, "lan-gateway"),
|
||||
("1.1.1.1", 443, "internet")]:
|
||||
print(name, p(h, port))
|
||||
PY
|
||||
)
|
||||
while read -r name verdict; do
|
||||
[ -z "$name" ] && continue
|
||||
if [ "$name" = "vmgw" ]; then
|
||||
[ "$verdict" = "REACHED" ] && ok "vmgw is reachable (positive control)" \
|
||||
|| bad "vmgw is NOT reachable - this suite proves nothing"
|
||||
else
|
||||
[ "$verdict" = "blocked" ] && ok "$name is blocked" || bad "$name was REACHABLE"
|
||||
fi
|
||||
done <<< "$probe"
|
||||
|
||||
echo
|
||||
echo "== 2. vmgw exposes two URLs and refuses everything else"
|
||||
paths=$(docker exec -i "$AGENT" python3 - <<PY
|
||||
import json, urllib.request, urllib.error
|
||||
BASE = "http://$GW"
|
||||
def code(path, method="GET"):
|
||||
try:
|
||||
r = urllib.request.Request(BASE + path, method=method,
|
||||
data=b"{}" if method == "POST" else None)
|
||||
if method == "POST": r.add_header("Content-Type", "application/json")
|
||||
with urllib.request.build_opener(urllib.request.ProxyHandler({})).open(r, timeout=8) as resp:
|
||||
return resp.status
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code
|
||||
except Exception:
|
||||
return 0
|
||||
print("health", code("/health"))
|
||||
print("ollama-chat-path-exists", 0 if code("/ollama/api/ps") != 200 else 200)
|
||||
for p, m in [("/ollama/api/delete", "DELETE"), ("/ollama/api/pull", "POST"),
|
||||
("/ollama/api/create", "POST"), ("/ollama/api/tags", "GET"),
|
||||
("/ollama/../api/delete", "DELETE"),
|
||||
# These two were a real location once, with an Open WebUI
|
||||
# ADMIN key injected here as a header. That design is gone -
|
||||
# live progress is collected by a filter inside Open WebUI -
|
||||
# and these assert that it stays gone.
|
||||
("/ui/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000/event", "POST"),
|
||||
("/api/v1/chats/a/messages/b/event", "POST")]:
|
||||
print("refuse:" + p, code(p, m))
|
||||
PY
|
||||
)
|
||||
while read -r name value; do
|
||||
[ -z "$name" ] && continue
|
||||
case "$name" in
|
||||
health) [ "$value" = "200" ] && ok "/health answers" || bad "/health returned $value" ;;
|
||||
ollama-chat-path-exists)
|
||||
[ "$value" = "200" ] && ok "the permitted Ollama path works (positive control)" \
|
||||
|| bad "the permitted Ollama path returned $value" ;;
|
||||
refuse:*)
|
||||
p=${name#refuse:}
|
||||
[ "$value" = "404" ] && ok "refused $p" || bad "$p returned $value, expected 404" ;;
|
||||
esac
|
||||
done <<< "$paths"
|
||||
|
||||
echo
|
||||
echo "== 3. no Open WebUI credential exists anywhere in this stack"
|
||||
# Not just absent from the agent: absent from the gateway too, which is
|
||||
# where it used to live. The whole point of moving live progress into a
|
||||
# filter was that no such key has to exist.
|
||||
gw_env=$(docker exec owuitools-vmgw env 2>/dev/null || true)
|
||||
if printf '%s' "$gw_env" | grep -q '^OWUITOOLS_OWUI_API_KEY=.'; then
|
||||
bad "owuitools-vmgw still holds an Open WebUI API key"
|
||||
else
|
||||
ok "owuitools-vmgw holds no Open WebUI API key"
|
||||
fi
|
||||
# A proxy_pass, not a mention: the config's own header explains what used
|
||||
# to be here, and matching that comment reported the opposite of the truth.
|
||||
if docker exec owuitools-vmgw grep -rqE '^[^#]*proxy_pass.*chats' /etc/nginx/conf.d/ 2>/dev/null; then
|
||||
bad "owuitools-vmgw still proxies the Open WebUI event endpoint"
|
||||
else
|
||||
ok "owuitools-vmgw does not proxy the Open WebUI event endpoint"
|
||||
fi
|
||||
|
||||
env_dump=$(docker exec "$AGENT" env 2>/dev/null || true)
|
||||
for secret in OWUITOOLS_OWUI_API_KEY GITEA_TOKEN OWUITOOLS_AUTH_TOKEN; do
|
||||
if printf '%s' "$env_dump" | grep -q "^${secret}=."; then
|
||||
bad "$secret is in the agent's environment"
|
||||
else
|
||||
ok "$secret is not in the agent's environment"
|
||||
fi
|
||||
done
|
||||
# vmd's config holds the Gitea token and is 0640 root:owuivmd.
|
||||
if docker exec "$AGENT" test -r /etc/owuivm/vmd.env 2>/dev/null; then
|
||||
bad "the agent can read vmd's credential file"
|
||||
else
|
||||
ok "the agent cannot read vmd's credential file"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ "$fails" -gt 0 ]; then echo "FAILED $fails"; exit 1; fi
|
||||
echo "the agent container is confined as designed"
|
||||
Reference in New Issue
Block a user