mirror of
https://github.com/langgenius/dify.git
synced 2026-07-27 15:08:35 +08:00
feat(agent): add a squid proxy for agent sandbox (#39544)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
989039db6e
commit
e1b55f54e6
@ -531,6 +531,14 @@ services:
|
||||
- ssrf_proxy_network
|
||||
|
||||
# Local sandbox for Dify Agent shell workspaces.
|
||||
# Network isolation: local_sandbox has NO direct route to `api`. Its only
|
||||
# networks are `agent_sandbox_network` (so agent_backend can reach it on 5004
|
||||
# for shellctl, and it can reach agent_backend directly) and
|
||||
# `local_sandbox_proxy_network` (so its egress is forced through
|
||||
# agent_ssrf_proxy).
|
||||
# All non-agent_backend/localhost traffic goes through the Squid forward proxy
|
||||
# on port 3128, which only allows agent_backend /agent-stub/ and the Dify API
|
||||
# /files/* endpoints (see ssrf_proxy/squid-agent.conf.template).
|
||||
local_sandbox:
|
||||
image: langgenius/dify-agent-local-sandbox:1.16.0
|
||||
restart: always
|
||||
@ -539,6 +547,9 @@ services:
|
||||
required: false
|
||||
environment:
|
||||
- SHELLCTL_AUTH_TOKEN=${DIFY_AGENT_SHELLCTL_AUTH_TOKEN:-}
|
||||
- HTTP_PROXY=http://agent_ssrf_proxy:3128
|
||||
- HTTPS_PROXY=http://agent_ssrf_proxy:3128
|
||||
- NO_PROXY=localhost,127.0.0.1
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5004/healthz"]
|
||||
interval: 30s
|
||||
@ -546,7 +557,8 @@ services:
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- default
|
||||
- agent_sandbox_network
|
||||
- local_sandbox_proxy_network
|
||||
|
||||
# plugin daemon
|
||||
plugin_daemon:
|
||||
@ -672,6 +684,33 @@ services:
|
||||
condition: service_started
|
||||
networks:
|
||||
- default
|
||||
# Shared internal network with local_sandbox so agent_backend can reach it
|
||||
# on port 5004 (shellctl entrypoint) while local_sandbox stays off `default`.
|
||||
- agent_sandbox_network
|
||||
|
||||
# Dedicated SSRF proxy for the dify-agent local_sandbox.
|
||||
agent_ssrf_proxy:
|
||||
image: ubuntu/squid:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- ./ssrf_proxy/squid-agent.conf.template:/etc/squid/squid.conf.template
|
||||
- ./ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template
|
||||
- ./ssrf_proxy/docker-agent-entrypoint.sh:/docker-entrypoint-mount.sh
|
||||
entrypoint:
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
"cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh",
|
||||
]
|
||||
environment:
|
||||
HTTP_PORT: ${SSRF_HTTP_PORT:-3128}
|
||||
COREDUMP_DIR: ${SSRF_COREDUMP_DIR:-/var/spool/squid}
|
||||
networks:
|
||||
# Needs to reach api and agent_backend as forward-proxy destinations.
|
||||
- default
|
||||
# Only agent_ssrf_proxy and local_sandbox share this internal network, so
|
||||
# the local_sandbox can reach Squid without gaining a direct route to `api`.
|
||||
- local_sandbox_proxy_network
|
||||
|
||||
# ssrf_proxy server
|
||||
# for more information, please refer to
|
||||
@ -681,6 +720,7 @@ services:
|
||||
restart: always
|
||||
volumes:
|
||||
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
|
||||
- ./ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template
|
||||
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
|
||||
entrypoint:
|
||||
[
|
||||
@ -1253,6 +1293,19 @@ networks:
|
||||
ssrf_proxy_network:
|
||||
driver: bridge
|
||||
internal: true
|
||||
# Internal network shared only by agent_ssrf_proxy and local_sandbox.
|
||||
local_sandbox_proxy_network:
|
||||
driver: bridge
|
||||
internal: true
|
||||
# shellctl control channel (agent_backend -> local_sandbox:5004).
|
||||
# sandbox can access agent backend through this network, this is
|
||||
# a known limitation.
|
||||
#
|
||||
# The agent runtime respects HTTP(S)_PROXY, but arbitrary code execution
|
||||
# is still possible through the shellctl channel.
|
||||
agent_sandbox_network:
|
||||
driver: bridge
|
||||
internal: true
|
||||
milvus:
|
||||
driver: bridge
|
||||
opensearch-net:
|
||||
|
||||
@ -199,6 +199,7 @@ services:
|
||||
restart: always
|
||||
volumes:
|
||||
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
|
||||
- ./ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template
|
||||
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
|
||||
entrypoint:
|
||||
[
|
||||
|
||||
@ -537,6 +537,14 @@ services:
|
||||
- ssrf_proxy_network
|
||||
|
||||
# Local sandbox for Dify Agent shell workspaces.
|
||||
# Network isolation: local_sandbox has NO direct route to `api`. Its only
|
||||
# networks are `agent_sandbox_network` (so agent_backend can reach it on 5004
|
||||
# for shellctl, and it can reach agent_backend directly) and
|
||||
# `local_sandbox_proxy_network` (so its egress is forced through
|
||||
# agent_ssrf_proxy).
|
||||
# All non-agent_backend/localhost traffic goes through the Squid forward proxy
|
||||
# on port 3128, which only allows agent_backend /agent-stub/ and the Dify API
|
||||
# /files/* endpoints (see ssrf_proxy/squid-agent.conf.template).
|
||||
local_sandbox:
|
||||
image: langgenius/dify-agent-local-sandbox:1.16.0
|
||||
restart: always
|
||||
@ -545,6 +553,9 @@ services:
|
||||
required: false
|
||||
environment:
|
||||
- SHELLCTL_AUTH_TOKEN=${DIFY_AGENT_SHELLCTL_AUTH_TOKEN:-}
|
||||
- HTTP_PROXY=http://agent_ssrf_proxy:3128
|
||||
- HTTPS_PROXY=http://agent_ssrf_proxy:3128
|
||||
- NO_PROXY=localhost,127.0.0.1
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5004/healthz"]
|
||||
interval: 30s
|
||||
@ -552,7 +563,8 @@ services:
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- default
|
||||
- agent_sandbox_network
|
||||
- local_sandbox_proxy_network
|
||||
|
||||
# plugin daemon
|
||||
plugin_daemon:
|
||||
@ -678,6 +690,33 @@ services:
|
||||
condition: service_started
|
||||
networks:
|
||||
- default
|
||||
# Shared internal network with local_sandbox so agent_backend can reach it
|
||||
# on port 5004 (shellctl entrypoint) while local_sandbox stays off `default`.
|
||||
- agent_sandbox_network
|
||||
|
||||
# Dedicated SSRF proxy for the dify-agent local_sandbox.
|
||||
agent_ssrf_proxy:
|
||||
image: ubuntu/squid:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- ./ssrf_proxy/squid-agent.conf.template:/etc/squid/squid.conf.template
|
||||
- ./ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template
|
||||
- ./ssrf_proxy/docker-agent-entrypoint.sh:/docker-entrypoint-mount.sh
|
||||
entrypoint:
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
"cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh",
|
||||
]
|
||||
environment:
|
||||
HTTP_PORT: ${SSRF_HTTP_PORT:-3128}
|
||||
COREDUMP_DIR: ${SSRF_COREDUMP_DIR:-/var/spool/squid}
|
||||
networks:
|
||||
# Needs to reach api and agent_backend as forward-proxy destinations.
|
||||
- default
|
||||
# Only agent_ssrf_proxy and local_sandbox share this internal network, so
|
||||
# the local_sandbox can reach Squid without gaining a direct route to `api`.
|
||||
- local_sandbox_proxy_network
|
||||
|
||||
# ssrf_proxy server
|
||||
# for more information, please refer to
|
||||
@ -687,6 +726,7 @@ services:
|
||||
restart: always
|
||||
volumes:
|
||||
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
|
||||
- ./ssrf_proxy/squid-common.conf.template:/etc/squid/dify_common.conf.template
|
||||
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
|
||||
entrypoint:
|
||||
[
|
||||
@ -1259,6 +1299,19 @@ networks:
|
||||
ssrf_proxy_network:
|
||||
driver: bridge
|
||||
internal: true
|
||||
# Internal network shared only by agent_ssrf_proxy and local_sandbox.
|
||||
local_sandbox_proxy_network:
|
||||
driver: bridge
|
||||
internal: true
|
||||
# shellctl control channel (agent_backend -> local_sandbox:5004).
|
||||
# sandbox can access agent backend through this network, this is
|
||||
# a known limitation.
|
||||
#
|
||||
# The agent runtime respects HTTP(S)_PROXY, but arbitrary code execution
|
||||
# is still possible through the shellctl channel.
|
||||
agent_sandbox_network:
|
||||
driver: bridge
|
||||
internal: true
|
||||
milvus:
|
||||
driver: bridge
|
||||
opensearch-net:
|
||||
|
||||
25
docker/ssrf_proxy/docker-agent-entrypoint.sh
Normal file
25
docker/ssrf_proxy/docker-agent-entrypoint.sh
Normal file
@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
tail -F /var/log/squid/access.log 2>/dev/null &
|
||||
tail -F /var/log/squid/error.log 2>/dev/null &
|
||||
tail -F /var/log/squid/store.log 2>/dev/null &
|
||||
tail -F /var/log/squid/cache.log 2>/dev/null &
|
||||
|
||||
expand_env() {
|
||||
awk '{
|
||||
while(match($0, /\${[A-Za-z_][A-Za-z_0-9]*}/)) {
|
||||
var = substr($0, RSTART+2, RLENGTH-3)
|
||||
val = ENVIRON[var]
|
||||
$0 = substr($0, 1, RSTART-1) val substr($0, RSTART+RLENGTH)
|
||||
}
|
||||
print
|
||||
}' "$1"
|
||||
}
|
||||
|
||||
echo "[ENTRYPOINT] replacing environment variables in the templates"
|
||||
expand_env /etc/squid/squid.conf.template > /etc/squid/squid.conf
|
||||
expand_env /etc/squid/dify_common.conf.template > /etc/squid/dify_common.conf
|
||||
|
||||
/usr/sbin/squid -Nz
|
||||
echo "[ENTRYPOINT] starting squid"
|
||||
/usr/sbin/squid -f /etc/squid/squid.conf -NYC 1
|
||||
@ -74,16 +74,22 @@ if [ -n "${SSRF_SANDBOX_PROXY_PORT:-}" ]; then
|
||||
} >> "$SANDBOX_PROXY_CONF"
|
||||
fi
|
||||
|
||||
# Replace environment variables in the template and output to the squid.conf
|
||||
echo "[ENTRYPOINT] replacing environment variables in the template"
|
||||
awk '{
|
||||
while(match($0, /\${[A-Za-z_][A-Za-z_0-9]*}/)) {
|
||||
var = substr($0, RSTART+2, RLENGTH-3)
|
||||
val = ENVIRON[var]
|
||||
$0 = substr($0, 1, RSTART-1) val substr($0, RSTART+RLENGTH)
|
||||
}
|
||||
print
|
||||
}' /etc/squid/squid.conf.template > /etc/squid/squid.conf
|
||||
# Replace environment variables in a template file.
|
||||
expand_env() {
|
||||
awk '{
|
||||
while(match($0, /\${[A-Za-z_][A-Za-z_0-9]*}/)) {
|
||||
var = substr($0, RSTART+2, RLENGTH-3)
|
||||
val = ENVIRON[var]
|
||||
$0 = substr($0, 1, RSTART-1) val substr($0, RSTART+RLENGTH)
|
||||
}
|
||||
print
|
||||
}' "$1"
|
||||
}
|
||||
|
||||
# Replace environment variables in the templates and output to squid.conf
|
||||
echo "[ENTRYPOINT] replacing environment variables in the templates"
|
||||
expand_env /etc/squid/squid.conf.template > /etc/squid/squid.conf
|
||||
expand_env /etc/squid/dify_common.conf.template > /etc/squid/dify_common.conf
|
||||
|
||||
/usr/sbin/squid -Nz
|
||||
echo "[ENTRYPOINT] starting squid"
|
||||
|
||||
21
docker/ssrf_proxy/squid-agent.conf.template
Normal file
21
docker/ssrf_proxy/squid-agent.conf.template
Normal file
@ -0,0 +1,21 @@
|
||||
# Dedicated Squid config for the dify-agent local_sandbox SSRF proxy.
|
||||
# All traffic on this proxy is restricted to:
|
||||
# - agent_backend /agent-stub/* endpoints
|
||||
# - Dify API /files/* endpoints (signed upload/download URLs)
|
||||
# External internet is allowed; all other private-network destinations are denied.
|
||||
|
||||
include /etc/squid/dify_common.conf
|
||||
|
||||
acl dst_agent_backend dstdomain agent_backend
|
||||
acl dst_dify_api dstdomain api
|
||||
acl path_files urlpath_regex -i ^/files/
|
||||
acl path_agent_stub urlpath_regex -i ^/agent-stub/
|
||||
|
||||
http_port ${HTTP_PORT}
|
||||
|
||||
http_access deny !Safe_ports
|
||||
http_access deny CONNECT !SSL_ports
|
||||
http_access allow dst_agent_backend path_agent_stub
|
||||
http_access allow dst_dify_api path_files
|
||||
http_access deny to_private_networks
|
||||
http_access allow all
|
||||
90
docker/ssrf_proxy/squid-common.conf.template
Normal file
90
docker/ssrf_proxy/squid-common.conf.template
Normal file
@ -0,0 +1,90 @@
|
||||
# Shared Squid configuration used by both ssrf_proxy and agent_ssrf_proxy.
|
||||
|
||||
################################## ACL Definitions ################################
|
||||
acl client_localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN)
|
||||
acl client_localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN)
|
||||
acl client_localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN)
|
||||
acl client_localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
|
||||
acl client_localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN)
|
||||
acl client_localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN)
|
||||
acl client_localnet src fc00::/7 # RFC 4193 local private network range
|
||||
acl client_localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines
|
||||
|
||||
acl to_private_networks dst 0.0.0.0/8
|
||||
acl to_private_networks dst 10.0.0.0/8
|
||||
acl to_private_networks dst 100.64.0.0/10
|
||||
acl to_private_networks dst 127.0.0.0/8
|
||||
acl to_private_networks dst 169.254.0.0/16
|
||||
acl to_private_networks dst 172.16.0.0/12
|
||||
acl to_private_networks dst 192.168.0.0/16
|
||||
acl to_private_networks dst 224.0.0.0/4
|
||||
acl to_private_networks dst 240.0.0.0/4
|
||||
acl to_private_networks dst ::/128
|
||||
acl to_private_networks dst ::1/128
|
||||
acl to_private_networks dst ::ffff:0:0/96 # IPv4-mapped
|
||||
acl to_private_networks dst ::/96 # deprecated IPv4-compatible
|
||||
acl to_private_networks dst fc00::/7
|
||||
acl to_private_networks dst fe80::/10
|
||||
|
||||
acl SSL_ports port 443
|
||||
acl Safe_ports port 80 # http
|
||||
acl Safe_ports port 21 # ftp
|
||||
acl Safe_ports port 443 # https
|
||||
acl Safe_ports port 70 # gopher
|
||||
acl Safe_ports port 210 # wais
|
||||
acl Safe_ports port 1025-65535 # unregistered ports
|
||||
acl Safe_ports port 280 # http-mgmt
|
||||
acl Safe_ports port 488 # gss-http
|
||||
acl Safe_ports port 591 # filemaker
|
||||
acl Safe_ports port 777 # multiling http
|
||||
acl CONNECT method CONNECT
|
||||
|
||||
################################## Common Parameters ################################
|
||||
|
||||
tcp_outgoing_address 0.0.0.0
|
||||
|
||||
################################## Proxy Server ################################
|
||||
coredump_dir ${COREDUMP_DIR}
|
||||
refresh_pattern ^ftp: 1440 20% 10080
|
||||
refresh_pattern ^gopher: 1440 0% 1440
|
||||
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
|
||||
refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
|
||||
refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims
|
||||
refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
|
||||
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
|
||||
refresh_pattern . 0 20% 4320
|
||||
|
||||
################################## Request Buffer ################################
|
||||
client_request_buffer_max_size 100 MB
|
||||
|
||||
################################## Performance & Concurrency ###############################
|
||||
max_filedescriptors 65536
|
||||
connect_timeout 30 seconds
|
||||
request_timeout 2 minutes
|
||||
read_timeout 2 minutes
|
||||
client_lifetime 5 minutes
|
||||
shutdown_lifetime 30 seconds
|
||||
|
||||
server_persistent_connections on
|
||||
client_persistent_connections on
|
||||
persistent_request_timeout 30 seconds
|
||||
pconn_timeout 1 minute
|
||||
|
||||
client_db on
|
||||
server_idle_pconn_timeout 2 minutes
|
||||
client_idle_pconn_timeout 2 minutes
|
||||
|
||||
quick_abort_min 16 KB
|
||||
quick_abort_max 16 MB
|
||||
quick_abort_pct 95
|
||||
|
||||
memory_cache_mode disk
|
||||
cache_mem 256 MB
|
||||
maximum_object_size_in_memory 512 KB
|
||||
|
||||
dns_timeout 30 seconds
|
||||
dns_retransmit_interval 5 seconds
|
||||
|
||||
logformat dify_log %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
|
||||
access_log daemon:/var/log/squid/access.log dify_log
|
||||
logfile_rotate 10
|
||||
@ -1,39 +1,5 @@
|
||||
acl client_localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN)
|
||||
acl client_localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN)
|
||||
acl client_localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN)
|
||||
acl client_localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
|
||||
acl client_localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN)
|
||||
acl client_localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN)
|
||||
acl client_localnet src fc00::/7 # RFC 4193 local private network range
|
||||
acl client_localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines
|
||||
acl to_private_networks dst 0.0.0.0/8
|
||||
acl to_private_networks dst 10.0.0.0/8
|
||||
acl to_private_networks dst 100.64.0.0/10
|
||||
acl to_private_networks dst 127.0.0.0/8
|
||||
acl to_private_networks dst 169.254.0.0/16
|
||||
acl to_private_networks dst 172.16.0.0/12
|
||||
acl to_private_networks dst 192.168.0.0/16
|
||||
acl to_private_networks dst 224.0.0.0/4
|
||||
acl to_private_networks dst 240.0.0.0/4
|
||||
acl to_private_networks dst ::/128
|
||||
acl to_private_networks dst ::1/128
|
||||
acl to_private_networks dst ::ffff:0:0/96 # IPv4-mapped
|
||||
acl to_private_networks dst ::/96 # deprecated IPv4-compatible
|
||||
acl to_private_networks dst fc00::/7
|
||||
acl to_private_networks dst fe80::/10
|
||||
acl SSL_ports port 443
|
||||
# acl SSL_ports port 1025-65535 # Enable the configuration to resolve this issue: https://github.com/langgenius/dify/issues/12792
|
||||
acl Safe_ports port 80 # http
|
||||
acl Safe_ports port 21 # ftp
|
||||
acl Safe_ports port 443 # https
|
||||
acl Safe_ports port 70 # gopher
|
||||
acl Safe_ports port 210 # wais
|
||||
acl Safe_ports port 1025-65535 # unregistered ports
|
||||
acl Safe_ports port 280 # http-mgmt
|
||||
acl Safe_ports port 488 # gss-http
|
||||
acl Safe_ports port 591 # filemaker
|
||||
acl Safe_ports port 777 # multiling http
|
||||
acl CONNECT method CONNECT
|
||||
include /etc/squid/dify_common.conf
|
||||
|
||||
acl allowed_domains dstdomain .marketplace.dify.ai
|
||||
|
||||
http_port ${HTTP_PORT}
|
||||
@ -49,73 +15,3 @@ http_access allow allowed_domains
|
||||
http_access allow client_localnet
|
||||
http_access allow localhost
|
||||
http_access deny all
|
||||
tcp_outgoing_address 0.0.0.0
|
||||
|
||||
################################## Proxy Server ################################
|
||||
coredump_dir ${COREDUMP_DIR}
|
||||
refresh_pattern ^ftp: 1440 20% 10080
|
||||
refresh_pattern ^gopher: 1440 0% 1440
|
||||
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
|
||||
refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
|
||||
refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims
|
||||
refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
|
||||
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
|
||||
refresh_pattern . 0 20% 4320
|
||||
|
||||
|
||||
# cache_dir ufs /var/spool/squid 100 16 256
|
||||
# upstream proxy, set to your own upstream proxy IP to avoid SSRF attacks
|
||||
# cache_peer 172.1.1.1 parent 3128 0 no-query no-digest no-netdb-exchange default
|
||||
|
||||
################################## Request Buffer ################################
|
||||
|
||||
# Unless the option's size is increased, an error will occur when uploading more than two files.
|
||||
client_request_buffer_max_size 100 MB
|
||||
|
||||
################################## Performance & Concurrency ###############################
|
||||
# Increase file descriptor limit for high concurrency
|
||||
max_filedescriptors 65536
|
||||
|
||||
# Timeout configurations for image requests
|
||||
connect_timeout 30 seconds
|
||||
request_timeout 2 minutes
|
||||
read_timeout 2 minutes
|
||||
client_lifetime 5 minutes
|
||||
shutdown_lifetime 30 seconds
|
||||
|
||||
# Persistent connections - improve performance for multiple requests
|
||||
server_persistent_connections on
|
||||
client_persistent_connections on
|
||||
persistent_request_timeout 30 seconds
|
||||
pconn_timeout 1 minute
|
||||
|
||||
# Connection pool and concurrency limits
|
||||
client_db on
|
||||
server_idle_pconn_timeout 2 minutes
|
||||
client_idle_pconn_timeout 2 minutes
|
||||
|
||||
# Quick abort settings - don't abort requests that are mostly done
|
||||
quick_abort_min 16 KB
|
||||
quick_abort_max 16 MB
|
||||
quick_abort_pct 95
|
||||
|
||||
# Memory and cache optimization
|
||||
memory_cache_mode disk
|
||||
cache_mem 256 MB
|
||||
maximum_object_size_in_memory 512 KB
|
||||
|
||||
# DNS resolver settings for better performance
|
||||
dns_timeout 30 seconds
|
||||
dns_retransmit_interval 5 seconds
|
||||
# By default, Squid uses the system's configured DNS resolvers.
|
||||
# If you need to override them, set dns_nameservers to appropriate servers
|
||||
# for your environment (for example, internal/corporate DNS). The following
|
||||
# is an example using public DNS and SHOULD be customized before use:
|
||||
# dns_nameservers 8.8.8.8 8.8.4.4
|
||||
|
||||
# Logging format for better debugging
|
||||
logformat dify_log %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
|
||||
access_log daemon:/var/log/squid/access.log dify_log
|
||||
|
||||
# Access log to track concurrent requests and timeouts
|
||||
logfile_rotate 10
|
||||
|
||||
@ -6,12 +6,18 @@ 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
|
||||
}
|
||||
|
||||
@ -106,6 +112,7 @@ docker run \
|
||||
--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 \
|
||||
@ -141,3 +148,79 @@ if [[ "$RUN_PUBLIC_CHECK" == "true" ]]; then
|
||||
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."
|
||||
|
||||
Loading…
Reference in New Issue
Block a user