mirror of
https://github.com/langgenius/dify.git
synced 2026-07-27 23:18:33 +08:00
227 lines
7.6 KiB
Bash
Executable File
227 lines
7.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
IMAGE="${SSRF_PROXY_TEST_IMAGE:-ubuntu/squid:latest}"
|
|
CLIENT_IMAGE="${SSRF_PROXY_TEST_CLIENT_IMAGE:-busybox:latest}"
|
|
CONTAINER_NAME="${SSRF_PROXY_TEST_CONTAINER:-dify-ssrf-proxy-test-$$}"
|
|
SANDBOX_CONTAINER_NAME="${CONTAINER_NAME}-sandbox"
|
|
AGENT_PROXY_CONTAINER_NAME="${CONTAINER_NAME}-agent-proxy"
|
|
API_CONTAINER_NAME="${CONTAINER_NAME}-api"
|
|
AGENT_BACKEND_CONTAINER_NAME="${CONTAINER_NAME}-agent-backend"
|
|
NETWORK_NAME="${SSRF_PROXY_TEST_NETWORK:-dify-ssrf-proxy-test-$$}"
|
|
RUN_PUBLIC_CHECK="${SSRF_PROXY_TEST_PUBLIC_CHECK:-true}"
|
|
|
|
cleanup() {
|
|
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
docker rm -f "$SANDBOX_CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
docker rm -f "$AGENT_PROXY_CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
docker rm -f "$API_CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
docker rm -f "$AGENT_BACKEND_CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
docker network rm "$NETWORK_NAME" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
http_code_for() {
|
|
local proxy_url="$1"
|
|
local target_url="$2"
|
|
local output
|
|
|
|
output="$(
|
|
docker run \
|
|
--rm \
|
|
--network "$NETWORK_NAME" \
|
|
--env "http_proxy=$proxy_url" \
|
|
--env "https_proxy=$proxy_url" \
|
|
"$CLIENT_IMAGE" \
|
|
wget -S -O /dev/null -T 10 "$target_url" 2>&1 || true
|
|
)"
|
|
|
|
printf '%s\n' "$output" | awk '$1 ~ /^HTTP\// { code = $2 } END { print code }'
|
|
}
|
|
|
|
direct_http_code_for() {
|
|
local target_url="$1"
|
|
local output
|
|
|
|
output="$(
|
|
docker run \
|
|
--rm \
|
|
--network "$NETWORK_NAME" \
|
|
"$CLIENT_IMAGE" \
|
|
wget -S -O /dev/null -T 10 "$target_url" 2>&1 || true
|
|
)"
|
|
|
|
printf '%s\n' "$output" | awk '$1 ~ /^HTTP\// { code = $2 } END { print code }'
|
|
}
|
|
|
|
assert_private_target_blocked() {
|
|
local proxy_url="$1"
|
|
local target_url="$2"
|
|
local status_code
|
|
|
|
status_code="$(http_code_for "$proxy_url" "$target_url")"
|
|
if [[ "$status_code" != "403" ]]; then
|
|
echo "Expected $target_url to be blocked with HTTP 403, got ${status_code:-no response}."
|
|
docker logs "$CONTAINER_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
assert_public_target_allowed() {
|
|
local proxy_url="$1"
|
|
local target_url="$2"
|
|
local status_code
|
|
|
|
status_code="$(http_code_for "$proxy_url" "$target_url")"
|
|
if [[ ! "$status_code" =~ ^[234][0-9][0-9]$ || "$status_code" == "403" ]]; then
|
|
echo "Expected $target_url to remain reachable, got ${status_code:-no response}."
|
|
docker logs "$CONTAINER_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
assert_sandbox_bridge_allowed() {
|
|
local target_url="$1"
|
|
local status_code
|
|
|
|
status_code="$(direct_http_code_for "$target_url")"
|
|
if [[ ! "$status_code" =~ ^2[0-9][0-9]$ ]]; then
|
|
echo "Expected sandbox host bridge $target_url to remain reachable, got ${status_code:-no response}."
|
|
docker logs "$CONTAINER_NAME" >&2 || true
|
|
docker logs "$SANDBOX_CONTAINER_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
cleanup
|
|
docker network create "$NETWORK_NAME" >/dev/null
|
|
|
|
docker run \
|
|
--detach \
|
|
--name "$SANDBOX_CONTAINER_NAME" \
|
|
--network "$NETWORK_NAME" \
|
|
--network-alias sandbox \
|
|
"$CLIENT_IMAGE" \
|
|
sh -c "mkdir -p /www && echo ok > /www/health && httpd -f -p 8194 -h /www" \
|
|
>/dev/null
|
|
|
|
docker run \
|
|
--detach \
|
|
--name "$CONTAINER_NAME" \
|
|
--entrypoint sh \
|
|
--network "$NETWORK_NAME" \
|
|
--volume "$ROOT_DIR/docker/ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template:ro" \
|
|
--volume "$ROOT_DIR/docker/ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template:ro" \
|
|
--volume "$ROOT_DIR/docker/ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh:ro" \
|
|
--env HTTP_PORT=3128 \
|
|
--env COREDUMP_DIR=/var/spool/squid \
|
|
--env SSRF_SANDBOX_PROXY_PORT=8194 \
|
|
--env SSRF_SANDBOX_PROXY_HOST=sandbox \
|
|
--env "SSRF_PROXY_ALLOW_PRIVATE_IPS=${SSRF_PROXY_ALLOW_PRIVATE_IPS:-}" \
|
|
--env "SSRF_PROXY_ALLOW_PRIVATE_DOMAINS=${SSRF_PROXY_ALLOW_PRIVATE_DOMAINS:-}" \
|
|
"$IMAGE" \
|
|
-c "cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh" \
|
|
>/dev/null
|
|
|
|
proxy_url="http://$CONTAINER_NAME:3128"
|
|
for _ in {1..30}; do
|
|
probe_status="$(http_code_for "$proxy_url" "http://127.0.0.1:80/")"
|
|
if [[ -n "$probe_status" ]]; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
if [[ -z "${probe_status:-}" ]]; then
|
|
echo "Squid proxy did not respond to probes."
|
|
docker logs "$CONTAINER_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
assert_private_target_blocked "$proxy_url" "http://127.0.0.1:80/"
|
|
assert_private_target_blocked "$proxy_url" "http://0.1.2.3:80/"
|
|
assert_private_target_blocked "$proxy_url" "http://169.254.169.254/latest/meta-data/"
|
|
|
|
if [[ "$RUN_PUBLIC_CHECK" == "true" ]]; then
|
|
assert_public_target_allowed "$proxy_url" "http://example.com/"
|
|
fi
|
|
|
|
assert_sandbox_bridge_allowed "http://$CONTAINER_NAME:8194/health"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# agent_ssrf_proxy tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Mock api server: serves /files/* (200) and everything else (404).
|
|
docker run \
|
|
--detach \
|
|
--name "$API_CONTAINER_NAME" \
|
|
--network "$NETWORK_NAME" \
|
|
--network-alias api \
|
|
"$CLIENT_IMAGE" \
|
|
sh -c "mkdir -p /www/files && echo file-ok > /www/files/test && echo denied > /www/index.html && httpd -f -p 5001 -h /www" \
|
|
>/dev/null
|
|
|
|
# Mock agent_backend server: serves /agent-stub/* (200) and everything else (404).
|
|
docker run \
|
|
--detach \
|
|
--name "$AGENT_BACKEND_CONTAINER_NAME" \
|
|
--network "$NETWORK_NAME" \
|
|
--network-alias agent_backend \
|
|
"$CLIENT_IMAGE" \
|
|
sh -c "mkdir -p /www/agent-stub && echo stub-ok > /www/agent-stub/config && echo denied > /www/index.html && httpd -f -p 5050 -h /www" \
|
|
>/dev/null
|
|
|
|
docker run \
|
|
--detach \
|
|
--name "$AGENT_PROXY_CONTAINER_NAME" \
|
|
--entrypoint sh \
|
|
--network "$NETWORK_NAME" \
|
|
--volume "$ROOT_DIR/docker/ssrf_proxy/squid-agent.conf.template:/etc/squid/squid.conf.template:ro" \
|
|
--volume "$ROOT_DIR/docker/ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template:ro" \
|
|
--volume "$ROOT_DIR/docker/ssrf_proxy/docker-agent-entrypoint.sh:/docker-entrypoint-mount.sh:ro" \
|
|
--env HTTP_PORT=3128 \
|
|
--env COREDUMP_DIR=/var/spool/squid \
|
|
"$IMAGE" \
|
|
-c "cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh" \
|
|
>/dev/null
|
|
|
|
agent_proxy_url="http://$AGENT_PROXY_CONTAINER_NAME:3128"
|
|
for _ in {1..30}; do
|
|
agent_probe_status="$(http_code_for "$agent_proxy_url" "http://127.0.0.1:80/")"
|
|
if [[ -n "$agent_probe_status" ]]; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
if [[ -z "${agent_probe_status:-}" ]]; then
|
|
echo "Agent SSRF proxy did not respond to probes."
|
|
docker logs "$AGENT_PROXY_CONTAINER_NAME" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
# Private targets must be blocked.
|
|
assert_private_target_blocked "$agent_proxy_url" "http://127.0.0.1:80/"
|
|
assert_private_target_blocked "$agent_proxy_url" "http://169.254.169.254/latest/meta-data/"
|
|
|
|
# agent_backend /agent-stub/* must be allowed.
|
|
assert_public_target_allowed "$agent_proxy_url" "http://agent_backend:5050/agent-stub/config"
|
|
|
|
# agent_backend non-/agent-stub paths must be blocked (403 from Squid).
|
|
assert_private_target_blocked "$agent_proxy_url" "http://agent_backend:5050/index.html"
|
|
|
|
# api /files/* must be allowed.
|
|
assert_public_target_allowed "$agent_proxy_url" "http://api:5001/files/test"
|
|
|
|
# api non-/files paths must be blocked.
|
|
assert_private_target_blocked "$agent_proxy_url" "http://api:5001/index.html"
|
|
|
|
# External internet must be allowed.
|
|
if [[ "$RUN_PUBLIC_CHECK" == "true" ]]; then
|
|
assert_public_target_allowed "$agent_proxy_url" "http://example.com/"
|
|
fi
|
|
|
|
echo "All SSRF proxy tests passed."
|