mirror of
https://github.com/langgenius/dify.git
synced 2026-01-08 07:14:14 +00:00
Compare commits
18 Commits
fix/mcp-ca
...
chore/ssrf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed6ac97854 | ||
|
|
0b74b82394 | ||
|
|
d01931dd52 | ||
|
|
4ea43f93ae | ||
|
|
44c5f7ec5c | ||
|
|
895b847204 | ||
|
|
4d184c98de | ||
|
|
5ea168f03b | ||
|
|
b7c87245a3 | ||
|
|
6a54980824 | ||
|
|
42110a8217 | ||
|
|
fb36069f1c | ||
|
|
1e971bd20d | ||
|
|
621ede0f7b | ||
|
|
99ee64c864 | ||
|
|
1a49febc02 | ||
|
|
9e2b6325f3 | ||
|
|
23c97ec7f7 |
3
.github/workflows/api-tests.yml
vendored
3
.github/workflows/api-tests.yml
vendored
@@ -67,6 +67,9 @@ jobs:
|
|||||||
cp docker/.env.example docker/.env
|
cp docker/.env.example docker/.env
|
||||||
cp docker/middleware.env.example docker/middleware.env
|
cp docker/middleware.env.example docker/middleware.env
|
||||||
|
|
||||||
|
- name: Setup SSRF Proxy for Testing
|
||||||
|
run: sh docker/ssrf_proxy/setup-testing.sh
|
||||||
|
|
||||||
- name: Expose Service Ports
|
- name: Expose Service Ports
|
||||||
run: sh .github/workflows/expose_service_ports.sh
|
run: sh .github/workflows/expose_service_ports.sh
|
||||||
|
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -228,10 +228,14 @@ web/public/fallback-*.js
|
|||||||
api/.env.backup
|
api/.env.backup
|
||||||
/clickzetta
|
/clickzetta
|
||||||
|
|
||||||
|
# SSRF Proxy - ignore the conf.d directory that's created for testing/local overrides
|
||||||
|
docker/ssrf_proxy/conf.d/
|
||||||
|
|
||||||
# Benchmark
|
# Benchmark
|
||||||
scripts/stress-test/setup/config/
|
scripts/stress-test/setup/config/
|
||||||
scripts/stress-test/reports/
|
scripts/stress-test/reports/
|
||||||
|
|
||||||
# mcp
|
# mcp
|
||||||
.playwright-mcp/
|
.playwright-mcp/
|
||||||
.serena/
|
.serena/
|
||||||
|
|
||||||
|
|||||||
175
api/tests/integration_tests/ssrf_proxy/TEST_CASES_README.md
Normal file
175
api/tests/integration_tests/ssrf_proxy/TEST_CASES_README.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# SSRF Proxy Test Cases
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The SSRF proxy test suite uses YAML files to define test cases, making them easier to maintain and extend without modifying code. These tests validate the SSRF proxy configuration in `docker/ssrf_proxy/`.
|
||||||
|
|
||||||
|
## Location
|
||||||
|
|
||||||
|
These tests are located in `api/tests/integration_tests/ssrf_proxy/` because they require the Python environment from the API project.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic Testing
|
||||||
|
|
||||||
|
From the `api/` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Or from the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd api && uv run python tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### List Available Tests
|
||||||
|
|
||||||
|
View all test cases without running them:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py --list-tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Custom Test File
|
||||||
|
|
||||||
|
Run tests from a specific YAML file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py --test-file test_cases_extended.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Mode Testing
|
||||||
|
|
||||||
|
**WARNING: Development mode DISABLES all SSRF protections! Only use in development environments!**
|
||||||
|
|
||||||
|
Test the development mode configuration (used by docker-compose.middleware.yaml):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py --dev-mode
|
||||||
|
```
|
||||||
|
|
||||||
|
Development mode:
|
||||||
|
|
||||||
|
- Mounts `conf.d.dev/` configuration that allows ALL requests
|
||||||
|
- Uses `test_cases_dev_mode.yaml` by default (all tests expect ALLOW)
|
||||||
|
- Verifies that private networks, cloud metadata, and non-standard ports are accessible
|
||||||
|
- Should NEVER be used in production environments
|
||||||
|
|
||||||
|
### Command Line Options
|
||||||
|
|
||||||
|
- `--host HOST`: Proxy host (default: localhost)
|
||||||
|
- `--port PORT`: Proxy port (default: 3128)
|
||||||
|
- `--no-container`: Don't start container (assume proxy is already running)
|
||||||
|
- `--save-results`: Save test results to JSON file
|
||||||
|
- `--test-file FILE`: Path to YAML file containing test cases
|
||||||
|
- `--list-tests`: List all test cases without running them
|
||||||
|
- `--dev-mode`: Run in development mode (DISABLES all SSRF protections - DO NOT use in production!)
|
||||||
|
|
||||||
|
## YAML Test Case Format
|
||||||
|
|
||||||
|
Test cases are organized by categories in YAML files:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
test_categories:
|
||||||
|
category_key:
|
||||||
|
name: "Category Display Name"
|
||||||
|
description: "Category description"
|
||||||
|
test_cases:
|
||||||
|
- name: "Test Case Name"
|
||||||
|
url: "http://example.com"
|
||||||
|
expected_blocked: false # true if should be blocked, false if allowed
|
||||||
|
description: "Optional test description"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Test Files
|
||||||
|
|
||||||
|
1. **test_cases.yaml** - Standard test suite with essential test cases (default)
|
||||||
|
1. **test_cases_extended.yaml** - Extended test suite with additional edge cases and scenarios
|
||||||
|
1. **test_cases_dev_mode.yaml** - Development mode test suite (all requests should be allowed)
|
||||||
|
|
||||||
|
All files are located in `api/tests/integration_tests/ssrf_proxy/`
|
||||||
|
|
||||||
|
## Categories
|
||||||
|
|
||||||
|
### Standard Categories
|
||||||
|
|
||||||
|
- **Private Networks**: Tests for blocking private IP ranges and loopback addresses
|
||||||
|
- **Cloud Metadata**: Tests for blocking cloud provider metadata endpoints
|
||||||
|
- **Public Internet**: Tests for allowing legitimate public internet access
|
||||||
|
- **Port Restrictions**: Tests for port-based access control
|
||||||
|
|
||||||
|
### Extended Categories (in test_cases_extended.yaml)
|
||||||
|
|
||||||
|
- **IPv6 Tests**: Tests for IPv6 address handling
|
||||||
|
- **Special Cases**: Edge cases like decimal/octal/hex IP notation
|
||||||
|
|
||||||
|
## Adding New Test Cases
|
||||||
|
|
||||||
|
1. Edit the YAML file (or create a new one)
|
||||||
|
1. Add test cases under appropriate categories
|
||||||
|
1. Run with `--test-file` option if using a custom file
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
test_categories:
|
||||||
|
custom_tests:
|
||||||
|
name: "Custom Tests"
|
||||||
|
description: "My custom test cases"
|
||||||
|
test_cases:
|
||||||
|
- name: "Custom Test 1"
|
||||||
|
url: "http://test.example.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Testing custom domain"
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Gets Tested
|
||||||
|
|
||||||
|
The tests validate the SSRF proxy configuration files in `docker/ssrf_proxy/`:
|
||||||
|
|
||||||
|
- `squid.conf.template` - Squid proxy configuration
|
||||||
|
- `docker-entrypoint.sh` - Container initialization script
|
||||||
|
- `conf.d/` - Additional configuration files (if present)
|
||||||
|
- `conf.d.dev/` - Development mode configuration (when using --dev-mode)
|
||||||
|
|
||||||
|
## Development Mode Configuration
|
||||||
|
|
||||||
|
Development mode provides a zero-configuration environment for local development:
|
||||||
|
|
||||||
|
- Mounts `conf.d.dev/` instead of `conf.d/`
|
||||||
|
- Allows ALL requests including private networks and cloud metadata
|
||||||
|
- Enables access to any port
|
||||||
|
- Disables all SSRF protections
|
||||||
|
|
||||||
|
### Using Development Mode with Docker Compose
|
||||||
|
|
||||||
|
From the main Dify repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use the development overlay
|
||||||
|
docker-compose -f docker-compose.middleware.yaml -f docker/ssrf_proxy/docker-compose.dev.yaml up ssrf_proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually mount the development configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name ssrf-proxy-dev \
|
||||||
|
-p 3128:3128 \
|
||||||
|
-v ./docker/ssrf_proxy/conf.d.dev:/etc/squid/conf.d:ro \
|
||||||
|
# ... other volumes
|
||||||
|
ubuntu/squid:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
**CRITICAL**: Never use this configuration in production!
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- **Maintainability**: Test cases can be updated without code changes
|
||||||
|
- **Extensibility**: Easy to add new test cases or categories
|
||||||
|
- **Clarity**: YAML format is human-readable and self-documenting
|
||||||
|
- **Flexibility**: Multiple test files for different scenarios
|
||||||
|
- **Fallback**: Code includes default test cases if YAML loading fails
|
||||||
|
- **Integration**: Properly integrated with the API project's Python environment
|
||||||
1
api/tests/integration_tests/ssrf_proxy/__init__.py
Normal file
1
api/tests/integration_tests/ssrf_proxy/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""SSRF Proxy Integration Tests"""
|
||||||
129
api/tests/integration_tests/ssrf_proxy/test_cases.yaml
Normal file
129
api/tests/integration_tests/ssrf_proxy/test_cases.yaml
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# SSRF Proxy Test Cases Configuration
|
||||||
|
# This file defines all test cases for the SSRF proxy
|
||||||
|
# Each test case validates whether the proxy correctly blocks or allows requests
|
||||||
|
|
||||||
|
test_categories:
|
||||||
|
private_networks:
|
||||||
|
name: "Private Networks"
|
||||||
|
description: "Tests for blocking private IP ranges and loopback addresses"
|
||||||
|
test_cases:
|
||||||
|
- name: "Loopback (127.0.0.1)"
|
||||||
|
url: "http://127.0.0.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "IPv4 loopback address"
|
||||||
|
|
||||||
|
- name: "Localhost"
|
||||||
|
url: "http://localhost"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Localhost hostname"
|
||||||
|
|
||||||
|
- name: "Private 10.x.x.x"
|
||||||
|
url: "http://10.0.0.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "RFC 1918 private network"
|
||||||
|
|
||||||
|
- name: "Private 172.16.x.x"
|
||||||
|
url: "http://172.16.0.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "RFC 1918 private network"
|
||||||
|
|
||||||
|
- name: "Private 192.168.x.x"
|
||||||
|
url: "http://192.168.1.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "RFC 1918 private network"
|
||||||
|
|
||||||
|
- name: "Link-local"
|
||||||
|
url: "http://169.254.1.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Link-local address"
|
||||||
|
|
||||||
|
- name: "This network"
|
||||||
|
url: "http://0.0.0.0"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "'This' network address"
|
||||||
|
|
||||||
|
cloud_metadata:
|
||||||
|
name: "Cloud Metadata"
|
||||||
|
description: "Tests for blocking cloud provider metadata endpoints"
|
||||||
|
test_cases:
|
||||||
|
- name: "AWS Metadata"
|
||||||
|
url: "http://169.254.169.254/latest/meta-data/"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "AWS EC2 metadata endpoint"
|
||||||
|
|
||||||
|
- name: "Azure Metadata"
|
||||||
|
url: "http://169.254.169.254/metadata/instance"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Azure metadata endpoint"
|
||||||
|
|
||||||
|
# Note: metadata.google.internal is not included as it may resolve to public IPs
|
||||||
|
|
||||||
|
public_internet:
|
||||||
|
name: "Public Internet"
|
||||||
|
description: "Tests for allowing legitimate public internet access"
|
||||||
|
test_cases:
|
||||||
|
- name: "Example.com"
|
||||||
|
url: "http://example.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Public website"
|
||||||
|
|
||||||
|
- name: "Google HTTPS"
|
||||||
|
url: "https://www.google.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "HTTPS public website"
|
||||||
|
|
||||||
|
- name: "HTTPBin API"
|
||||||
|
url: "http://httpbin.org/get"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Public API endpoint"
|
||||||
|
|
||||||
|
- name: "GitHub API"
|
||||||
|
url: "https://api.github.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Public API over HTTPS"
|
||||||
|
|
||||||
|
port_restrictions:
|
||||||
|
name: "Port Restrictions"
|
||||||
|
description: "Tests for port-based access control"
|
||||||
|
test_cases:
|
||||||
|
- name: "HTTP Port 80"
|
||||||
|
url: "http://example.com:80"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Standard HTTP port"
|
||||||
|
|
||||||
|
- name: "HTTPS Port 443"
|
||||||
|
url: "http://example.com:443"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Standard HTTPS port"
|
||||||
|
|
||||||
|
- name: "Port 8080"
|
||||||
|
url: "http://example.com:8080"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Non-standard port"
|
||||||
|
|
||||||
|
- name: "Port 3000"
|
||||||
|
url: "http://example.com:3000"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Development port"
|
||||||
|
|
||||||
|
- name: "SSH Port 22"
|
||||||
|
url: "http://example.com:22"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "SSH port"
|
||||||
|
|
||||||
|
- name: "MySQL Port 3306"
|
||||||
|
url: "http://example.com:3306"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Database port"
|
||||||
|
|
||||||
|
# Additional test configurations can be added here
|
||||||
|
# For example:
|
||||||
|
#
|
||||||
|
# ipv6_tests:
|
||||||
|
# name: "IPv6 Tests"
|
||||||
|
# description: "Tests for IPv6 address handling"
|
||||||
|
# test_cases:
|
||||||
|
# - name: "IPv6 Loopback"
|
||||||
|
# url: "http://[::1]"
|
||||||
|
# expected_blocked: true
|
||||||
|
# description: "IPv6 loopback address"
|
||||||
168
api/tests/integration_tests/ssrf_proxy/test_cases_dev_mode.yaml
Normal file
168
api/tests/integration_tests/ssrf_proxy/test_cases_dev_mode.yaml
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# Development Mode Test Cases for SSRF Proxy
|
||||||
|
# These test cases verify that development mode correctly disables all SSRF protections
|
||||||
|
# WARNING: All requests should be ALLOWED in development mode
|
||||||
|
|
||||||
|
test_categories:
|
||||||
|
private_networks:
|
||||||
|
name: "Private Networks (Dev Mode)"
|
||||||
|
description: "In dev mode, private networks should be ALLOWED"
|
||||||
|
test_cases:
|
||||||
|
- name: "Loopback (127.0.0.1)"
|
||||||
|
url: "http://127.0.0.1"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "IPv4 loopback - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Localhost"
|
||||||
|
url: "http://localhost"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Localhost hostname - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Private 10.x.x.x"
|
||||||
|
url: "http://10.0.0.1"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "RFC 1918 private network - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Private 172.16.x.x"
|
||||||
|
url: "http://172.16.0.1"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "RFC 1918 private network - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Private 192.168.x.x"
|
||||||
|
url: "http://192.168.1.1"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "RFC 1918 private network - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Link-local"
|
||||||
|
url: "http://169.254.1.1"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Link-local address - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "This network"
|
||||||
|
url: "http://0.0.0.0"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "'This' network address - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
cloud_metadata:
|
||||||
|
name: "Cloud Metadata (Dev Mode)"
|
||||||
|
description: "In dev mode, cloud metadata endpoints should be ALLOWED"
|
||||||
|
test_cases:
|
||||||
|
- name: "AWS Metadata"
|
||||||
|
url: "http://169.254.169.254/latest/meta-data/"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "AWS EC2 metadata - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Azure Metadata"
|
||||||
|
url: "http://169.254.169.254/metadata/instance"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Azure metadata - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
non_standard_ports:
|
||||||
|
name: "Non-Standard Ports (Dev Mode)"
|
||||||
|
description: "In dev mode, all ports should be ALLOWED"
|
||||||
|
test_cases:
|
||||||
|
- name: "Port 8080"
|
||||||
|
url: "http://example.com:8080"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Alternative HTTP port - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Port 3000"
|
||||||
|
url: "http://example.com:3000"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Node.js development port - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "SSH Port 22"
|
||||||
|
url: "http://example.com:22"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "SSH port - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Database Port 3306"
|
||||||
|
url: "http://example.com:3306"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "MySQL port - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Database Port 5432"
|
||||||
|
url: "http://example.com:5432"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "PostgreSQL port - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Redis Port 6379"
|
||||||
|
url: "http://example.com:6379"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Redis port - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "MongoDB Port 27017"
|
||||||
|
url: "http://example.com:27017"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "MongoDB port - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "High Port 12345"
|
||||||
|
url: "http://example.com:12345"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Random high port - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
localhost_ports:
|
||||||
|
name: "Localhost with Various Ports (Dev Mode)"
|
||||||
|
description: "In dev mode, localhost with any port should be ALLOWED"
|
||||||
|
test_cases:
|
||||||
|
- name: "Localhost:8080"
|
||||||
|
url: "http://localhost:8080"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Localhost with port 8080 - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Localhost:3000"
|
||||||
|
url: "http://localhost:3000"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Localhost with port 3000 - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "127.0.0.1:9200"
|
||||||
|
url: "http://127.0.0.1:9200"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Loopback with Elasticsearch port - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "127.0.0.1:5001"
|
||||||
|
url: "http://127.0.0.1:5001"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "Loopback with API port - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
public_internet:
|
||||||
|
name: "Public Internet (Dev Mode)"
|
||||||
|
description: "Public internet should still work in dev mode"
|
||||||
|
test_cases:
|
||||||
|
- name: "Example.com"
|
||||||
|
url: "http://example.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Public website - always allowed"
|
||||||
|
|
||||||
|
- name: "Google HTTPS"
|
||||||
|
url: "https://www.google.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "HTTPS public website - always allowed"
|
||||||
|
|
||||||
|
- name: "GitHub API"
|
||||||
|
url: "https://api.github.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Public API over HTTPS - always allowed"
|
||||||
|
|
||||||
|
special_cases:
|
||||||
|
name: "Special Cases (Dev Mode)"
|
||||||
|
description: "Edge cases that should all be allowed in dev mode"
|
||||||
|
test_cases:
|
||||||
|
- name: "Decimal IP notation"
|
||||||
|
url: "http://2130706433"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "127.0.0.1 in decimal - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "Private network in subdomain"
|
||||||
|
url: "http://192-168-1-1.example.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Domain that looks like private IP - always allowed as it resolves externally"
|
||||||
|
|
||||||
|
- name: "IPv6 Loopback"
|
||||||
|
url: "http://[::1]"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "IPv6 loopback - normally blocked, allowed in dev mode"
|
||||||
|
|
||||||
|
- name: "IPv6 Link-local"
|
||||||
|
url: "http://[fe80::1]"
|
||||||
|
expected_blocked: false # ALLOWED in dev mode
|
||||||
|
description: "IPv6 link-local - normally blocked, allowed in dev mode"
|
||||||
219
api/tests/integration_tests/ssrf_proxy/test_cases_extended.yaml
Normal file
219
api/tests/integration_tests/ssrf_proxy/test_cases_extended.yaml
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# Extended SSRF Proxy Test Cases Configuration
|
||||||
|
# This file contains additional test cases for comprehensive testing
|
||||||
|
# Use with: python test_ssrf_proxy.py --test-file test_cases_extended.yaml
|
||||||
|
|
||||||
|
test_categories:
|
||||||
|
# Standard test cases
|
||||||
|
private_networks:
|
||||||
|
name: "Private Networks"
|
||||||
|
description: "Tests for blocking private IP ranges and loopback addresses"
|
||||||
|
test_cases:
|
||||||
|
- name: "Loopback (127.0.0.1)"
|
||||||
|
url: "http://127.0.0.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "IPv4 loopback address"
|
||||||
|
|
||||||
|
- name: "Localhost"
|
||||||
|
url: "http://localhost"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Localhost hostname"
|
||||||
|
|
||||||
|
- name: "Private 10.x.x.x"
|
||||||
|
url: "http://10.0.0.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "RFC 1918 private network"
|
||||||
|
|
||||||
|
- name: "Private 172.16.x.x"
|
||||||
|
url: "http://172.16.0.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "RFC 1918 private network"
|
||||||
|
|
||||||
|
- name: "Private 192.168.x.x"
|
||||||
|
url: "http://192.168.1.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "RFC 1918 private network"
|
||||||
|
|
||||||
|
- name: "Link-local"
|
||||||
|
url: "http://169.254.1.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Link-local address"
|
||||||
|
|
||||||
|
- name: "This network"
|
||||||
|
url: "http://0.0.0.0"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "'This' network address"
|
||||||
|
|
||||||
|
cloud_metadata:
|
||||||
|
name: "Cloud Metadata"
|
||||||
|
description: "Tests for blocking cloud provider metadata endpoints"
|
||||||
|
test_cases:
|
||||||
|
- name: "AWS Metadata"
|
||||||
|
url: "http://169.254.169.254/latest/meta-data/"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "AWS EC2 metadata endpoint"
|
||||||
|
|
||||||
|
- name: "Azure Metadata"
|
||||||
|
url: "http://169.254.169.254/metadata/instance"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Azure metadata endpoint"
|
||||||
|
|
||||||
|
- name: "DigitalOcean Metadata"
|
||||||
|
url: "http://169.254.169.254/metadata/v1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "DigitalOcean metadata endpoint"
|
||||||
|
|
||||||
|
- name: "Oracle Cloud Metadata"
|
||||||
|
url: "http://169.254.169.254/opc/v1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Oracle Cloud metadata endpoint"
|
||||||
|
|
||||||
|
public_internet:
|
||||||
|
name: "Public Internet"
|
||||||
|
description: "Tests for allowing legitimate public internet access"
|
||||||
|
test_cases:
|
||||||
|
- name: "Example.com"
|
||||||
|
url: "http://example.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Public website"
|
||||||
|
|
||||||
|
- name: "Google HTTPS"
|
||||||
|
url: "https://www.google.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "HTTPS public website"
|
||||||
|
|
||||||
|
- name: "HTTPBin API"
|
||||||
|
url: "http://httpbin.org/get"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Public API endpoint"
|
||||||
|
|
||||||
|
- name: "GitHub API"
|
||||||
|
url: "https://api.github.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Public API over HTTPS"
|
||||||
|
|
||||||
|
- name: "OpenAI API"
|
||||||
|
url: "https://api.openai.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "OpenAI API endpoint"
|
||||||
|
|
||||||
|
- name: "Anthropic API"
|
||||||
|
url: "https://api.anthropic.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Anthropic API endpoint"
|
||||||
|
|
||||||
|
port_restrictions:
|
||||||
|
name: "Port Restrictions"
|
||||||
|
description: "Tests for port-based access control"
|
||||||
|
test_cases:
|
||||||
|
- name: "HTTP Port 80"
|
||||||
|
url: "http://example.com:80"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Standard HTTP port"
|
||||||
|
|
||||||
|
- name: "HTTPS Port 443"
|
||||||
|
url: "http://example.com:443"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Standard HTTPS port"
|
||||||
|
|
||||||
|
- name: "Port 8080"
|
||||||
|
url: "http://example.com:8080"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Alternative HTTP port"
|
||||||
|
|
||||||
|
- name: "Port 3000"
|
||||||
|
url: "http://example.com:3000"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Node.js development port"
|
||||||
|
|
||||||
|
- name: "SSH Port 22"
|
||||||
|
url: "http://example.com:22"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "SSH port"
|
||||||
|
|
||||||
|
- name: "Telnet Port 23"
|
||||||
|
url: "http://example.com:23"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Telnet port"
|
||||||
|
|
||||||
|
- name: "SMTP Port 25"
|
||||||
|
url: "http://example.com:25"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "SMTP mail port"
|
||||||
|
|
||||||
|
- name: "MySQL Port 3306"
|
||||||
|
url: "http://example.com:3306"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "MySQL database port"
|
||||||
|
|
||||||
|
- name: "PostgreSQL Port 5432"
|
||||||
|
url: "http://example.com:5432"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "PostgreSQL database port"
|
||||||
|
|
||||||
|
- name: "Redis Port 6379"
|
||||||
|
url: "http://example.com:6379"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Redis port"
|
||||||
|
|
||||||
|
- name: "MongoDB Port 27017"
|
||||||
|
url: "http://example.com:27017"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "MongoDB port"
|
||||||
|
|
||||||
|
ipv6_tests:
|
||||||
|
name: "IPv6 Tests"
|
||||||
|
description: "Tests for IPv6 address handling"
|
||||||
|
test_cases:
|
||||||
|
- name: "IPv6 Loopback"
|
||||||
|
url: "http://[::1]"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "IPv6 loopback address"
|
||||||
|
|
||||||
|
- name: "IPv6 All zeros"
|
||||||
|
url: "http://[::]"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "IPv6 all zeros address"
|
||||||
|
|
||||||
|
- name: "IPv6 Link-local"
|
||||||
|
url: "http://[fe80::1]"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "IPv6 link-local address"
|
||||||
|
|
||||||
|
- name: "IPv6 Unique local"
|
||||||
|
url: "http://[fc00::1]"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "IPv6 unique local address"
|
||||||
|
|
||||||
|
special_cases:
|
||||||
|
name: "Special Cases"
|
||||||
|
description: "Edge cases and special scenarios"
|
||||||
|
test_cases:
|
||||||
|
- name: "Decimal IP notation"
|
||||||
|
url: "http://2130706433"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "127.0.0.1 in decimal notation"
|
||||||
|
|
||||||
|
- name: "Octal IP notation"
|
||||||
|
url: "http://0177.0.0.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "127.0.0.1 with octal notation"
|
||||||
|
|
||||||
|
- name: "Hex IP notation"
|
||||||
|
url: "http://0x7f.0.0.1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "127.0.0.1 with hex notation"
|
||||||
|
|
||||||
|
- name: "Mixed notation"
|
||||||
|
url: "http://0x7f.0.0.0x1"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "127.0.0.1 with mixed hex notation"
|
||||||
|
|
||||||
|
- name: "Localhost with port"
|
||||||
|
url: "http://localhost:8080"
|
||||||
|
expected_blocked: true
|
||||||
|
description: "Localhost with non-standard port"
|
||||||
|
|
||||||
|
- name: "Domain with private IP"
|
||||||
|
url: "http://192-168-1-1.example.com"
|
||||||
|
expected_blocked: false
|
||||||
|
description: "Domain that looks like private IP (should resolve)"
|
||||||
482
api/tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py
Executable file
482
api/tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py
Executable file
@@ -0,0 +1,482 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
SSRF Proxy Test Suite
|
||||||
|
|
||||||
|
This script tests the SSRF proxy configuration to ensure it blocks
|
||||||
|
private networks while allowing public internet access.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from typing import final
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
# Color codes for terminal output
|
||||||
|
class Colors:
|
||||||
|
RED: str = "\033[0;31m"
|
||||||
|
GREEN: str = "\033[0;32m"
|
||||||
|
YELLOW: str = "\033[1;33m"
|
||||||
|
BLUE: str = "\033[0;34m"
|
||||||
|
NC: str = "\033[0m" # No Color
|
||||||
|
|
||||||
|
|
||||||
|
class TestResult(Enum):
|
||||||
|
PASSED = "passed"
|
||||||
|
FAILED = "failed"
|
||||||
|
SKIPPED = "skipped"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TestCase:
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
expected_blocked: bool
|
||||||
|
category: str
|
||||||
|
description: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class SSRFProxyTester:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
proxy_host: str = "localhost",
|
||||||
|
proxy_port: int = 3128,
|
||||||
|
test_file: str | None = None,
|
||||||
|
dev_mode: bool = False,
|
||||||
|
):
|
||||||
|
self.proxy_host = proxy_host
|
||||||
|
self.proxy_port = proxy_port
|
||||||
|
self.proxy_url = f"http://{proxy_host}:{proxy_port}"
|
||||||
|
self.container_name = "ssrf-proxy-test-dev" if dev_mode else "ssrf-proxy-test"
|
||||||
|
self.image = "ubuntu/squid:latest"
|
||||||
|
self.results: list[dict[str, object]] = []
|
||||||
|
self.dev_mode = dev_mode
|
||||||
|
# Use dev mode test cases by default when in dev mode
|
||||||
|
if dev_mode and test_file is None:
|
||||||
|
self.test_file = "test_cases_dev_mode.yaml"
|
||||||
|
else:
|
||||||
|
self.test_file = test_file or "test_cases.yaml"
|
||||||
|
|
||||||
|
def start_proxy_container(self) -> bool:
|
||||||
|
"""Start the SSRF proxy container"""
|
||||||
|
mode_str = " (DEVELOPMENT MODE)" if self.dev_mode else ""
|
||||||
|
print(f"{Colors.YELLOW}Starting SSRF proxy container{mode_str}...{Colors.NC}")
|
||||||
|
if self.dev_mode:
|
||||||
|
print(f"{Colors.RED}WARNING: Development mode DISABLES all SSRF protections!{Colors.NC}")
|
||||||
|
|
||||||
|
# Stop and remove existing container if exists
|
||||||
|
_ = subprocess.run(["docker", "stop", self.container_name], capture_output=True, text=True)
|
||||||
|
_ = subprocess.run(["docker", "rm", self.container_name], capture_output=True, text=True)
|
||||||
|
|
||||||
|
# Get directories for mounting config files
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# Docker config files are in docker/ssrf_proxy relative to project root
|
||||||
|
project_root = os.path.abspath(os.path.join(script_dir, "..", "..", "..", ".."))
|
||||||
|
docker_config_dir = os.path.join(project_root, "docker", "ssrf_proxy")
|
||||||
|
|
||||||
|
# Choose configuration template based on mode
|
||||||
|
if self.dev_mode:
|
||||||
|
config_template = "squid.conf.dev.template"
|
||||||
|
else:
|
||||||
|
config_template = "squid.conf.template"
|
||||||
|
|
||||||
|
# Start container
|
||||||
|
cmd = [
|
||||||
|
"docker",
|
||||||
|
"run",
|
||||||
|
"-d",
|
||||||
|
"--name",
|
||||||
|
self.container_name,
|
||||||
|
"-p",
|
||||||
|
f"{self.proxy_port}:{self.proxy_port}",
|
||||||
|
"-p",
|
||||||
|
"8194:8194",
|
||||||
|
"-v",
|
||||||
|
f"{docker_config_dir}/{config_template}:/etc/squid/squid.conf.template:ro",
|
||||||
|
"-v",
|
||||||
|
f"{docker_config_dir}/docker-entrypoint.sh:/docker-entrypoint-mount.sh:ro",
|
||||||
|
"-e",
|
||||||
|
f"HTTP_PORT={self.proxy_port}",
|
||||||
|
"-e",
|
||||||
|
"COREDUMP_DIR=/var/spool/squid",
|
||||||
|
"-e",
|
||||||
|
"REVERSE_PROXY_PORT=8194",
|
||||||
|
"-e",
|
||||||
|
"SANDBOX_HOST=sandbox",
|
||||||
|
"-e",
|
||||||
|
"SANDBOX_PORT=8194",
|
||||||
|
"--entrypoint",
|
||||||
|
"sh",
|
||||||
|
self.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", # noqa: E501
|
||||||
|
]
|
||||||
|
|
||||||
|
# Mount configuration directory (only in normal mode)
|
||||||
|
# In dev mode, the dev template already allows everything
|
||||||
|
if not self.dev_mode:
|
||||||
|
# Normal mode: mount regular conf.d if it exists
|
||||||
|
conf_d_path = f"{docker_config_dir}/conf.d"
|
||||||
|
if os.path.exists(conf_d_path) and os.listdir(conf_d_path):
|
||||||
|
cmd.insert(-3, "-v")
|
||||||
|
cmd.insert(-3, f"{conf_d_path}:/etc/squid/conf.d:ro")
|
||||||
|
else:
|
||||||
|
print(f"{Colors.YELLOW}Using development mode configuration (all SSRF protections disabled){Colors.NC}")
|
||||||
|
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"{Colors.RED}Failed to start container: {result.stderr}{Colors.NC}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Wait for proxy to start
|
||||||
|
print(f"{Colors.YELLOW}Waiting for proxy to start...{Colors.NC}")
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
# Check if container is running
|
||||||
|
result = subprocess.run(
|
||||||
|
["docker", "ps", "--filter", f"name={self.container_name}"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.container_name not in result.stdout:
|
||||||
|
print(f"{Colors.RED}Container failed to start!{Colors.NC}")
|
||||||
|
logs = subprocess.run(["docker", "logs", self.container_name], capture_output=True, text=True)
|
||||||
|
print(logs.stdout)
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"{Colors.GREEN}Proxy started successfully!{Colors.NC}\n")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def stop_proxy_container(self):
|
||||||
|
"""Stop and remove the proxy container"""
|
||||||
|
_ = subprocess.run(["docker", "stop", self.container_name], capture_output=True, text=True)
|
||||||
|
_ = subprocess.run(["docker", "rm", self.container_name], capture_output=True, text=True)
|
||||||
|
|
||||||
|
def test_url(self, test_case: TestCase) -> TestResult:
|
||||||
|
"""Test a single URL through the proxy"""
|
||||||
|
# Configure proxy for urllib
|
||||||
|
proxy_handler = urllib.request.ProxyHandler({"http": self.proxy_url, "https": self.proxy_url})
|
||||||
|
opener = urllib.request.build_opener(proxy_handler)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Make request through proxy
|
||||||
|
request = urllib.request.Request(test_case.url)
|
||||||
|
with opener.open(request, timeout=5):
|
||||||
|
# If we got a response, the request was allowed
|
||||||
|
is_blocked = False
|
||||||
|
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
# HTTP errors like 403 from proxy mean blocked
|
||||||
|
if e.code in [403, 407]:
|
||||||
|
is_blocked = True
|
||||||
|
else:
|
||||||
|
# Other HTTP errors mean the request went through
|
||||||
|
is_blocked = False
|
||||||
|
except (urllib.error.URLError, OSError, TimeoutError) as e:
|
||||||
|
# In dev mode, connection errors to 169.254.x.x addresses are expected
|
||||||
|
# These addresses don't exist locally, so timeout is normal
|
||||||
|
# The proxy allowed the request, but the destination is unreachable
|
||||||
|
if self.dev_mode and "169.254" in test_case.url:
|
||||||
|
# In dev mode, if we're testing 169.254.x.x addresses,
|
||||||
|
# a timeout means the proxy allowed it (not blocked)
|
||||||
|
is_blocked = False
|
||||||
|
else:
|
||||||
|
# In normal mode, or for other addresses, connection errors mean blocked
|
||||||
|
is_blocked = True
|
||||||
|
except Exception as e:
|
||||||
|
# Unexpected error
|
||||||
|
print(f"{Colors.YELLOW}Warning: Unexpected error testing {test_case.url}: {e}{Colors.NC}")
|
||||||
|
return TestResult.SKIPPED
|
||||||
|
|
||||||
|
# Check if result matches expectation
|
||||||
|
if is_blocked == test_case.expected_blocked:
|
||||||
|
return TestResult.PASSED
|
||||||
|
else:
|
||||||
|
return TestResult.FAILED
|
||||||
|
|
||||||
|
def run_test(self, test_case: TestCase):
|
||||||
|
"""Run a single test and record result"""
|
||||||
|
result = self.test_url(test_case)
|
||||||
|
|
||||||
|
# Print result
|
||||||
|
if result == TestResult.PASSED:
|
||||||
|
symbol = f"{Colors.GREEN}✓{Colors.NC}"
|
||||||
|
elif result == TestResult.FAILED:
|
||||||
|
symbol = f"{Colors.RED}✗{Colors.NC}"
|
||||||
|
else:
|
||||||
|
symbol = f"{Colors.YELLOW}⊘{Colors.NC}"
|
||||||
|
|
||||||
|
status = "blocked" if test_case.expected_blocked else "allowed"
|
||||||
|
print(f" {symbol} {test_case.name} (should be {status})")
|
||||||
|
|
||||||
|
# Record result
|
||||||
|
self.results.append(
|
||||||
|
{
|
||||||
|
"name": test_case.name,
|
||||||
|
"category": test_case.category,
|
||||||
|
"url": test_case.url,
|
||||||
|
"expected_blocked": test_case.expected_blocked,
|
||||||
|
"result": result.value,
|
||||||
|
"description": test_case.description,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def run_all_tests(self):
|
||||||
|
"""Run all test cases"""
|
||||||
|
test_cases = self.get_test_cases()
|
||||||
|
|
||||||
|
print("=" * 50)
|
||||||
|
if self.dev_mode:
|
||||||
|
print(" SSRF Proxy Test Suite (DEV MODE)")
|
||||||
|
print("=" * 50)
|
||||||
|
print(f"{Colors.RED}WARNING: Testing with SSRF protections DISABLED!{Colors.NC}")
|
||||||
|
print(f"{Colors.YELLOW}All requests should be ALLOWED in dev mode.{Colors.NC}")
|
||||||
|
else:
|
||||||
|
print(" SSRF Proxy Test Suite")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Group tests by category
|
||||||
|
categories: dict[str, list[TestCase]] = {}
|
||||||
|
for test in test_cases:
|
||||||
|
if test.category not in categories:
|
||||||
|
categories[test.category] = []
|
||||||
|
categories[test.category].append(test)
|
||||||
|
|
||||||
|
# Run tests by category
|
||||||
|
for category, tests in categories.items():
|
||||||
|
print(f"\n{Colors.YELLOW}{category}:{Colors.NC}")
|
||||||
|
for test in tests:
|
||||||
|
self.run_test(test)
|
||||||
|
|
||||||
|
def load_test_cases_from_yaml(self, yaml_file: str = "test_cases.yaml") -> list[TestCase]:
|
||||||
|
"""Load test cases from YAML configuration file"""
|
||||||
|
try:
|
||||||
|
# Try to load from YAML file
|
||||||
|
yaml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), yaml_file)
|
||||||
|
|
||||||
|
with open(yaml_path) as f:
|
||||||
|
config = yaml.safe_load(f) # pyright: ignore[reportAny]
|
||||||
|
|
||||||
|
test_cases: list[TestCase] = []
|
||||||
|
|
||||||
|
# Parse test categories and cases from YAML
|
||||||
|
test_categories = config.get("test_categories", {}) # pyright: ignore[reportAny]
|
||||||
|
for category_key, category_data in test_categories.items(): # pyright: ignore[reportAny]
|
||||||
|
category_name: str = str(category_data.get("name", category_key)) # pyright: ignore[reportAny]
|
||||||
|
|
||||||
|
test_cases_list = category_data.get("test_cases", []) # pyright: ignore[reportAny]
|
||||||
|
for test_data in test_cases_list: # pyright: ignore[reportAny]
|
||||||
|
test_case = TestCase(
|
||||||
|
name=str(test_data["name"]), # pyright: ignore[reportAny]
|
||||||
|
url=str(test_data["url"]), # pyright: ignore[reportAny]
|
||||||
|
expected_blocked=bool(test_data["expected_blocked"]), # pyright: ignore[reportAny]
|
||||||
|
category=category_name,
|
||||||
|
description=str(test_data.get("description", "")), # pyright: ignore[reportAny]
|
||||||
|
)
|
||||||
|
test_cases.append(test_case)
|
||||||
|
|
||||||
|
if test_cases:
|
||||||
|
print(f"{Colors.BLUE}Loaded {len(test_cases)} test cases from {yaml_file}{Colors.NC}")
|
||||||
|
return test_cases
|
||||||
|
else:
|
||||||
|
print(f"{Colors.YELLOW}No test cases found in {yaml_file}, using defaults{Colors.NC}")
|
||||||
|
return self.get_default_test_cases()
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"{Colors.YELLOW}Test case file {yaml_file} not found, using defaults{Colors.NC}")
|
||||||
|
return self.get_default_test_cases()
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
print(f"{Colors.YELLOW}Error parsing {yaml_file}: {e}, using defaults{Colors.NC}")
|
||||||
|
return self.get_default_test_cases()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Colors.YELLOW}Unexpected error loading {yaml_file}: {e}, using defaults{Colors.NC}")
|
||||||
|
return self.get_default_test_cases()
|
||||||
|
|
||||||
|
def get_default_test_cases(self) -> list[TestCase]:
|
||||||
|
"""Fallback test cases if YAML loading fails"""
|
||||||
|
return [
|
||||||
|
# Essential test cases as fallback
|
||||||
|
TestCase("Loopback", "http://127.0.0.1", True, "Private Networks", "IPv4 loopback"),
|
||||||
|
TestCase("Private Network", "http://192.168.1.1", True, "Private Networks", "RFC 1918"),
|
||||||
|
TestCase("AWS Metadata", "http://169.254.169.254", True, "Cloud Metadata", "AWS metadata"),
|
||||||
|
TestCase("Public Site", "http://example.com", False, "Public Internet", "Public website"),
|
||||||
|
TestCase("Port 8080", "http://example.com:8080", True, "Port Restrictions", "Non-standard port"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_test_cases(self) -> list[TestCase]:
|
||||||
|
"""Get all test cases from YAML or defaults"""
|
||||||
|
return self.load_test_cases_from_yaml(self.test_file)
|
||||||
|
|
||||||
|
def print_summary(self):
|
||||||
|
"""Print test results summary"""
|
||||||
|
passed = sum(1 for r in self.results if r["result"] == "passed")
|
||||||
|
failed = sum(1 for r in self.results if r["result"] == "failed")
|
||||||
|
skipped = sum(1 for r in self.results if r["result"] == "skipped")
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print(" Test Summary")
|
||||||
|
print("=" * 50)
|
||||||
|
print(f"Tests Passed: {Colors.GREEN}{passed}{Colors.NC}")
|
||||||
|
print(f"Tests Failed: {Colors.RED}{failed}{Colors.NC}")
|
||||||
|
if skipped > 0:
|
||||||
|
print(f"Tests Skipped: {Colors.YELLOW}{skipped}{Colors.NC}")
|
||||||
|
|
||||||
|
if failed == 0:
|
||||||
|
if hasattr(self, "dev_mode") and self.dev_mode:
|
||||||
|
print(f"\n{Colors.GREEN}✓ All tests passed! Development mode is working correctly.{Colors.NC}")
|
||||||
|
print(
|
||||||
|
f"{Colors.YELLOW}Remember: Dev mode DISABLES all SSRF protections - "
|
||||||
|
f"use only for development!{Colors.NC}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f"\n{Colors.GREEN}✓ All tests passed! SSRF proxy is configured correctly.{Colors.NC}")
|
||||||
|
else:
|
||||||
|
if hasattr(self, "dev_mode") and self.dev_mode:
|
||||||
|
print(f"\n{Colors.RED}✗ Some tests failed. Dev mode should allow ALL requests!{Colors.NC}")
|
||||||
|
else:
|
||||||
|
print(f"\n{Colors.RED}✗ Some tests failed. Please review the configuration.{Colors.NC}")
|
||||||
|
print("\nFailed tests:")
|
||||||
|
for r in self.results:
|
||||||
|
if r["result"] == "failed":
|
||||||
|
status = "should be blocked" if r["expected_blocked"] else "should be allowed"
|
||||||
|
print(f" - {r['name']} ({status}): {r['url']}")
|
||||||
|
|
||||||
|
return failed == 0
|
||||||
|
|
||||||
|
def save_results(self, filename: str = "test_results.json"):
|
||||||
|
"""Save test results to JSON file"""
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
json.dump(
|
||||||
|
{
|
||||||
|
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
"proxy_url": self.proxy_url,
|
||||||
|
"results": self.results,
|
||||||
|
},
|
||||||
|
f,
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
print(f"\nResults saved to {filename}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
@dataclass
|
||||||
|
class Args:
|
||||||
|
host: str = "localhost"
|
||||||
|
port: int = 3128
|
||||||
|
no_container: bool = False
|
||||||
|
save_results: bool = False
|
||||||
|
test_file: str | None = None
|
||||||
|
list_tests: bool = False
|
||||||
|
dev_mode: bool = False
|
||||||
|
|
||||||
|
def parse_args() -> Args:
|
||||||
|
parser = argparse.ArgumentParser(description="Test SSRF Proxy Configuration")
|
||||||
|
_ = parser.add_argument("--host", type=str, default="localhost", help="Proxy host (default: localhost)")
|
||||||
|
_ = parser.add_argument("--port", type=int, default=3128, help="Proxy port (default: 3128)")
|
||||||
|
_ = parser.add_argument(
|
||||||
|
"--no-container",
|
||||||
|
action="store_true",
|
||||||
|
help="Don't start container (assume proxy is already running)",
|
||||||
|
)
|
||||||
|
_ = parser.add_argument("--save-results", action="store_true", help="Save test results to JSON file")
|
||||||
|
_ = parser.add_argument(
|
||||||
|
"--test-file", type=str, help="Path to YAML file containing test cases (default: test_cases.yaml)"
|
||||||
|
)
|
||||||
|
_ = parser.add_argument("--list-tests", action="store_true", help="List all test cases without running them")
|
||||||
|
_ = parser.add_argument(
|
||||||
|
"--dev-mode",
|
||||||
|
action="store_true",
|
||||||
|
help="Run in development mode (DISABLES all SSRF protections - DO NOT use in production!)",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse arguments - argparse.Namespace has Any-typed attributes
|
||||||
|
# This is a known limitation of argparse in Python's type system
|
||||||
|
namespace = parser.parse_args()
|
||||||
|
|
||||||
|
# Convert namespace attributes to properly typed values
|
||||||
|
# argparse guarantees these attributes exist with the correct types
|
||||||
|
# based on our argument definitions, but the type system cannot verify this
|
||||||
|
return Args(
|
||||||
|
host=str(namespace.host), # pyright: ignore[reportAny]
|
||||||
|
port=int(namespace.port), # pyright: ignore[reportAny]
|
||||||
|
no_container=bool(namespace.no_container), # pyright: ignore[reportAny]
|
||||||
|
save_results=bool(namespace.save_results), # pyright: ignore[reportAny]
|
||||||
|
test_file=namespace.test_file or None, # pyright: ignore[reportAny]
|
||||||
|
list_tests=bool(namespace.list_tests), # pyright: ignore[reportAny]
|
||||||
|
dev_mode=bool(namespace.dev_mode), # pyright: ignore[reportAny]
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
tester = SSRFProxyTester(args.host, args.port, args.test_file, args.dev_mode)
|
||||||
|
|
||||||
|
# If --list-tests flag is set, just list the tests and exit
|
||||||
|
if args.list_tests:
|
||||||
|
test_cases = tester.get_test_cases()
|
||||||
|
mode_str = " (DEVELOPMENT MODE)" if args.dev_mode else ""
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print(f" Available Test Cases{mode_str}")
|
||||||
|
print("=" * 50)
|
||||||
|
if args.dev_mode:
|
||||||
|
print(f"\n{Colors.RED}WARNING: Dev mode test cases expect ALL requests to be ALLOWED!{Colors.NC}")
|
||||||
|
|
||||||
|
# Group by category for display
|
||||||
|
categories: dict[str, list[TestCase]] = {}
|
||||||
|
for test in test_cases:
|
||||||
|
if test.category not in categories:
|
||||||
|
categories[test.category] = []
|
||||||
|
categories[test.category].append(test)
|
||||||
|
|
||||||
|
for category, tests in categories.items():
|
||||||
|
print(f"\n{Colors.YELLOW}{category}:{Colors.NC}")
|
||||||
|
for test in tests:
|
||||||
|
blocked_status = "BLOCK" if test.expected_blocked else "ALLOW"
|
||||||
|
color = Colors.RED if test.expected_blocked else Colors.GREEN
|
||||||
|
print(f" {color}[{blocked_status}]{Colors.NC} {test.name}")
|
||||||
|
if test.description:
|
||||||
|
print(f" {test.description}")
|
||||||
|
print(f" URL: {test.url}")
|
||||||
|
|
||||||
|
print(f"\nTotal: {len(test_cases)} test cases")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Start container unless --no-container flag is set
|
||||||
|
if not args.no_container:
|
||||||
|
if not tester.start_proxy_container():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
tester.run_all_tests()
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
success = tester.print_summary()
|
||||||
|
|
||||||
|
# Save results if requested
|
||||||
|
if args.save_results:
|
||||||
|
tester.save_results()
|
||||||
|
|
||||||
|
# Exit with appropriate code
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
if not args.no_container:
|
||||||
|
print(f"\n{Colors.YELLOW}Cleaning up...{Colors.NC}")
|
||||||
|
tester.stop_proxy_container()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -245,12 +245,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
|
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
|
||||||
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
|
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
|
||||||
entrypoint:
|
# Optional: Mount custom config directory for additional rules
|
||||||
[
|
# Uncomment the line below and create conf.d directory with custom .conf files
|
||||||
"sh",
|
# - ./ssrf_proxy/conf.d:/etc/squid/conf.d:ro
|
||||||
"-c",
|
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" ]
|
||||||
"cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh",
|
|
||||||
]
|
|
||||||
environment:
|
environment:
|
||||||
# pls clearly modify the squid env vars to fit your network environment.
|
# pls clearly modify the squid env vars to fit your network environment.
|
||||||
HTTP_PORT: ${SSRF_HTTP_PORT:-3128}
|
HTTP_PORT: ${SSRF_HTTP_PORT:-3128}
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
|
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
|
||||||
|
- ./ssrf_proxy/squid.conf.dev.template:/etc/squid/squid.conf.dev.template
|
||||||
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
|
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
|
||||||
entrypoint:
|
entrypoint:
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -842,12 +842,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
|
- ./ssrf_proxy/squid.conf.template:/etc/squid/squid.conf.template
|
||||||
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
|
- ./ssrf_proxy/docker-entrypoint.sh:/docker-entrypoint-mount.sh
|
||||||
entrypoint:
|
# Optional: Mount custom config directory for additional rules
|
||||||
[
|
# Uncomment the line below and create conf.d directory with custom .conf files
|
||||||
"sh",
|
# - ./ssrf_proxy/conf.d:/etc/squid/conf.d:ro
|
||||||
"-c",
|
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" ]
|
||||||
"cp /docker-entrypoint-mount.sh /docker-entrypoint.sh && sed -i 's/\r$$//' /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh && /docker-entrypoint.sh",
|
|
||||||
]
|
|
||||||
environment:
|
environment:
|
||||||
# pls clearly modify the squid env vars to fit your network environment.
|
# pls clearly modify the squid env vars to fit your network environment.
|
||||||
HTTP_PORT: ${SSRF_HTTP_PORT:-3128}
|
HTTP_PORT: ${SSRF_HTTP_PORT:-3128}
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ SSRF_HTTP_PORT=3128
|
|||||||
SSRF_COREDUMP_DIR=/var/spool/squid
|
SSRF_COREDUMP_DIR=/var/spool/squid
|
||||||
SSRF_REVERSE_PROXY_PORT=8194
|
SSRF_REVERSE_PROXY_PORT=8194
|
||||||
SSRF_SANDBOX_HOST=sandbox
|
SSRF_SANDBOX_HOST=sandbox
|
||||||
|
# Development mode switch - set to true to disable all SSRF protections
|
||||||
|
# WARNING: This allows access to localhost, private networks, and all ports!
|
||||||
|
# Only use this in development environments, NEVER in production!
|
||||||
|
SSRF_PROXY_DEV_MODE=false
|
||||||
|
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
# Environment Variables for weaviate Service
|
# Environment Variables for weaviate Service
|
||||||
|
|||||||
204
docker/ssrf_proxy/README.md
Normal file
204
docker/ssrf_proxy/README.md
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
# SSRF Proxy Configuration
|
||||||
|
|
||||||
|
This directory contains the Squid proxy configuration used to prevent Server-Side Request Forgery (SSRF) attacks in Dify.
|
||||||
|
|
||||||
|
## Security by Default
|
||||||
|
|
||||||
|
The default configuration (`squid.conf.template`) prevents SSRF attacks while allowing normal internet access:
|
||||||
|
|
||||||
|
- **Blocks all private/internal networks** (RFC 1918, loopback, link-local, etc.)
|
||||||
|
- **Only allows HTTP (80) and HTTPS (443) ports**
|
||||||
|
- **Allows all public internet resources** (operates as a blacklist for private networks)
|
||||||
|
- **Additional restrictions can be added** via custom configurations in `/etc/squid/conf.d/`
|
||||||
|
|
||||||
|
## Customizing the Configuration
|
||||||
|
|
||||||
|
### For Development/Local Environments
|
||||||
|
|
||||||
|
To allow additional domains or relax restrictions for your local environment:
|
||||||
|
|
||||||
|
1. Create a `conf.d` directory in your deployment
|
||||||
|
1. Copy example configurations from `conf.d.example/` and modify as needed
|
||||||
|
1. Mount the config files to `/etc/squid/conf.d/` in the container
|
||||||
|
|
||||||
|
### Example: Docker Compose
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
ssrf-proxy:
|
||||||
|
volumes:
|
||||||
|
- ./my-proxy-configs:/etc/squid/conf.d:ro
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Kubernetes ConfigMap
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: squid-custom-config
|
||||||
|
data:
|
||||||
|
20-allow-external-domains.conf: |
|
||||||
|
acl allowed_external dstdomain .example.com
|
||||||
|
http_access allow allowed_external
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: ssrf-proxy
|
||||||
|
volumeMounts:
|
||||||
|
- name: custom-config
|
||||||
|
mountPath: /etc/squid/conf.d
|
||||||
|
volumes:
|
||||||
|
- name: custom-config
|
||||||
|
configMap:
|
||||||
|
name: squid-custom-config
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Example Configurations
|
||||||
|
|
||||||
|
The `conf.d.example/` directory contains example configurations:
|
||||||
|
|
||||||
|
- **00-testing-environment.conf.example**: Configuration for CI/testing environments (NOT for production)
|
||||||
|
- **10-allow-internal-services.conf.example**: Allow internal services (use with caution!)
|
||||||
|
- **20-allow-external-domains.conf.example**: Allow specific external domains
|
||||||
|
- **30-allow-additional-ports.conf.example**: Allow additional ports
|
||||||
|
- **40-restrict-to-allowlist.conf.example**: Convert to whitelist mode (block all except allowed)
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
⚠️ **WARNING**: Relaxing these restrictions can expose your system to SSRF attacks!
|
||||||
|
|
||||||
|
- **Never allow access to private networks in production** unless absolutely necessary
|
||||||
|
- **Carefully review any domains you whitelist** to ensure they cannot be used for SSRF
|
||||||
|
- **Avoid allowing high port ranges** (1025-65535) as they can bypass security restrictions
|
||||||
|
- **Monitor proxy logs** for suspicious activity
|
||||||
|
|
||||||
|
## Default Blocked Networks
|
||||||
|
|
||||||
|
The following networks are blocked by default to prevent SSRF:
|
||||||
|
|
||||||
|
- `0.0.0.0/8` - "This" network
|
||||||
|
- `10.0.0.0/8` - Private network (RFC 1918)
|
||||||
|
- `127.0.0.0/8` - Loopback
|
||||||
|
- `169.254.0.0/16` - Link-local (RFC 3927)
|
||||||
|
- `172.16.0.0/12` - Private network (RFC 1918)
|
||||||
|
- `192.168.0.0/16` - Private network (RFC 1918)
|
||||||
|
- `224.0.0.0/4` - Multicast
|
||||||
|
- `fc00::/7` - IPv6 unique local addresses
|
||||||
|
- `fe80::/10` - IPv6 link-local
|
||||||
|
- `::1/128` - IPv6 loopback
|
||||||
|
|
||||||
|
## Development Mode
|
||||||
|
|
||||||
|
⚠️ **WARNING: Development mode DISABLES all SSRF protections! Only use in development environments!**
|
||||||
|
|
||||||
|
Development mode provides a zero-configuration environment that:
|
||||||
|
|
||||||
|
- Allows access to ALL private networks and localhost
|
||||||
|
- Allows access to cloud metadata endpoints
|
||||||
|
- Allows connections to any port
|
||||||
|
- Disables all SSRF protections for easier development
|
||||||
|
|
||||||
|
### Using Development Mode
|
||||||
|
|
||||||
|
#### Option 1: Environment Variable (Recommended)
|
||||||
|
|
||||||
|
Simply set the `SSRF_PROXY_DEV_MODE` environment variable to `true`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In your .env or middleware.env file
|
||||||
|
SSRF_PROXY_DEV_MODE=true
|
||||||
|
|
||||||
|
# Then start normally
|
||||||
|
docker-compose -f docker-compose.middleware.yaml up ssrf_proxy
|
||||||
|
```
|
||||||
|
|
||||||
|
Or set it directly in docker-compose:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
ssrf_proxy:
|
||||||
|
environment:
|
||||||
|
SSRF_PROXY_DEV_MODE: true
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Note about Docker Networking:**
|
||||||
|
|
||||||
|
When accessing services on your host machine from within Docker containers:
|
||||||
|
|
||||||
|
- Do NOT use `127.0.0.1` or `localhost` (these refer to the container itself)
|
||||||
|
- Instead use:
|
||||||
|
- `host.docker.internal:port` (recommended, works on Mac/Windows/Linux with Docker 20.10+)
|
||||||
|
- Your host machine's actual IP address
|
||||||
|
- On Linux: the Docker bridge gateway (usually `172.17.0.1`)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Wrong (won't work from inside container):
|
||||||
|
http://127.0.0.1:1234
|
||||||
|
|
||||||
|
# Correct (will work):
|
||||||
|
http://host.docker.internal:1234
|
||||||
|
```
|
||||||
|
|
||||||
|
The development mode uses `squid.conf.dev.template` which allows all connections.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Comprehensive integration tests are available to validate the SSRF proxy configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run from the api/ directory
|
||||||
|
cd ../../api
|
||||||
|
uv run python tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py
|
||||||
|
|
||||||
|
# List available test cases
|
||||||
|
uv run python tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py --list-tests
|
||||||
|
|
||||||
|
# Use extended test suite
|
||||||
|
uv run python tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py --test-file test_cases_extended.yaml
|
||||||
|
|
||||||
|
# Test development mode (all requests should be allowed)
|
||||||
|
uv run python tests/integration_tests/ssrf_proxy/test_ssrf_proxy.py --dev-mode
|
||||||
|
```
|
||||||
|
|
||||||
|
The test suite validates:
|
||||||
|
|
||||||
|
- Blocking of private networks and loopback addresses
|
||||||
|
- Blocking of cloud metadata endpoints
|
||||||
|
- Allowing of public internet resources
|
||||||
|
- Port restriction enforcement
|
||||||
|
|
||||||
|
See `api/tests/integration_tests/ssrf_proxy/TEST_CASES_README.md` for detailed testing documentation.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If your application needs to access a service that's being blocked:
|
||||||
|
|
||||||
|
1. Check the Squid logs to identify what's being blocked
|
||||||
|
1. Create a custom configuration in `/etc/squid/conf.d/`
|
||||||
|
1. Only allow the minimum necessary access
|
||||||
|
1. Test thoroughly to ensure security is maintained
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
docker/ssrf_proxy/
|
||||||
|
├── squid.conf.template # SSRF protection configuration
|
||||||
|
├── docker-entrypoint.sh # Container entrypoint script
|
||||||
|
├── conf.d.example/ # Example override configurations
|
||||||
|
│ ├── 00-testing-environment.conf.example
|
||||||
|
│ ├── 10-allow-internal-services.conf.example
|
||||||
|
│ ├── 20-allow-external-domains.conf.example
|
||||||
|
│ ├── 30-allow-additional-ports.conf.example
|
||||||
|
│ └── 40-restrict-to-allowlist.conf.example
|
||||||
|
├── conf.d.dev/ # Development mode configuration
|
||||||
|
│ └── 00-development-mode.conf # Disables all SSRF protections
|
||||||
|
├── docker-compose.dev.yaml # Docker Compose overlay for dev mode
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Configuration for CI/Testing Environment
|
||||||
|
# Copy this file to /etc/squid/conf.d/00-testing-environment.conf when running tests
|
||||||
|
# WARNING: This configuration is ONLY for testing and should NOT be used in production
|
||||||
|
|
||||||
|
# Allow access to sandbox service for integration tests
|
||||||
|
acl sandbox_service dst sandbox
|
||||||
|
http_access allow sandbox_service
|
||||||
|
|
||||||
|
# Allow access to Docker internal networks for testing
|
||||||
|
# This is needed when services communicate within Docker networks
|
||||||
|
acl docker_internal dst 172.16.0.0/12
|
||||||
|
http_access allow docker_internal
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Example: Allow access to internal services (USE WITH CAUTION!)
|
||||||
|
# Copy this file to /etc/squid/conf.d/20-allow-internal-services.conf to enable
|
||||||
|
# WARNING: This reduces SSRF protection. Only use if you understand the security implications.
|
||||||
|
|
||||||
|
# Example: Allow specific internal service
|
||||||
|
# acl internal_api_service dst 10.0.1.100
|
||||||
|
# http_access allow internal_api_service
|
||||||
|
|
||||||
|
# Example: Allow Docker network (172.17.0.0/16 is Docker's default bridge network)
|
||||||
|
# acl docker_network dst 172.17.0.0/16
|
||||||
|
# http_access allow docker_network
|
||||||
|
|
||||||
|
# Example: Allow localhost access (DANGEROUS - can bypass SSRF protection)
|
||||||
|
# acl localhost_dst dst 127.0.0.1
|
||||||
|
# http_access allow localhost_dst
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Example: Allow access to specific external domains
|
||||||
|
# Copy this file to /etc/squid/conf.d/30-allow-external-domains.conf to enable
|
||||||
|
|
||||||
|
# Allow specific domains for API integrations
|
||||||
|
# acl allowed_apis dstdomain .api.openai.com .anthropic.com .googleapis.com
|
||||||
|
# http_access allow allowed_apis
|
||||||
|
|
||||||
|
# Allow webhook endpoints
|
||||||
|
# acl webhook_endpoints dstdomain .webhook.site .zapier.com
|
||||||
|
# http_access allow webhook_endpoints
|
||||||
|
|
||||||
|
# Allow storage services
|
||||||
|
# acl storage_services dstdomain .s3.amazonaws.com .blob.core.windows.net .storage.googleapis.com
|
||||||
|
# http_access allow storage_services
|
||||||
|
|
||||||
|
# Allow by specific IP address (use with caution)
|
||||||
|
# acl trusted_ip dst 203.0.113.10
|
||||||
|
# http_access allow trusted_ip
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Example: Allow additional ports for specific protocols
|
||||||
|
# Copy this file to /etc/squid/conf.d/40-allow-additional-ports.conf to enable
|
||||||
|
# WARNING: Opening additional ports can increase security risks
|
||||||
|
|
||||||
|
# Allow additional safe ports
|
||||||
|
# acl Safe_ports port 8080 # http-alt
|
||||||
|
# acl Safe_ports port 8443 # https-alt
|
||||||
|
# acl Safe_ports port 3000 # common development port
|
||||||
|
# acl Safe_ports port 5000 # common API port
|
||||||
|
|
||||||
|
# Allow additional SSL ports for CONNECT method
|
||||||
|
# acl SSL_ports port 8443 # https-alt
|
||||||
|
# acl SSL_ports port 3443 # custom ssl
|
||||||
|
|
||||||
|
# Allow high ports (1025-65535) - DANGEROUS! Can be used to bypass restrictions
|
||||||
|
# acl Safe_ports port 1025-65535
|
||||||
|
# acl SSL_ports port 1025-65535
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Example: Convert proxy to whitelist mode (strict mode)
|
||||||
|
# Copy this file to /etc/squid/conf.d/40-restrict-to-allowlist.conf to enable
|
||||||
|
# WARNING: This will block ALL internet access except explicitly allowed domains
|
||||||
|
#
|
||||||
|
# This changes the default behavior from blacklist (block private, allow public)
|
||||||
|
# to whitelist (block everything, allow specific domains only)
|
||||||
|
|
||||||
|
# First, insert specific allowed domains BEFORE the final "allow all" rule
|
||||||
|
# The include statement is processed sequentially, so rules here take precedence
|
||||||
|
|
||||||
|
# Example: Only allow specific services
|
||||||
|
# acl allowed_services dstdomain .openai.com .anthropic.com .google.com
|
||||||
|
# http_access allow allowed_services
|
||||||
|
|
||||||
|
# Example: Allow Dify marketplace
|
||||||
|
# acl allowed_marketplace dstdomain .marketplace.dify.ai
|
||||||
|
# http_access allow allowed_marketplace
|
||||||
|
|
||||||
|
# Then deny all other requests (converting to whitelist mode)
|
||||||
|
# This rule will override the default "allow all" at the end
|
||||||
|
# Uncomment the following line to enable strict whitelist mode:
|
||||||
|
# http_access deny all
|
||||||
@@ -26,8 +26,26 @@ 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/store.log 2>/dev/null &
|
||||||
tail -F /var/log/squid/cache.log 2>/dev/null &
|
tail -F /var/log/squid/cache.log 2>/dev/null &
|
||||||
|
|
||||||
|
# Select the appropriate template based on DEV_MODE
|
||||||
|
echo "[ENTRYPOINT] SSRF_PROXY_DEV_MODE is set to: '${SSRF_PROXY_DEV_MODE}'"
|
||||||
|
if [ "${SSRF_PROXY_DEV_MODE}" = "true" ] || [ "${SSRF_PROXY_DEV_MODE}" = "True" ] || [ "${SSRF_PROXY_DEV_MODE}" = "TRUE" ] || [ "${SSRF_PROXY_DEV_MODE}" = "1" ]; then
|
||||||
|
echo "[ENTRYPOINT] WARNING: Development mode is ENABLED! All SSRF protections are DISABLED!"
|
||||||
|
echo "[ENTRYPOINT] This allows access to localhost, private networks, and all ports."
|
||||||
|
echo "[ENTRYPOINT] DO NOT USE IN PRODUCTION!"
|
||||||
|
TEMPLATE_FILE="/etc/squid/squid.conf.dev.template"
|
||||||
|
else
|
||||||
|
echo "[ENTRYPOINT] Using production configuration with SSRF protections enabled"
|
||||||
|
TEMPLATE_FILE="/etc/squid/squid.conf.template"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if the selected template exists
|
||||||
|
if [ ! -f "$TEMPLATE_FILE" ]; then
|
||||||
|
echo "[ENTRYPOINT] ERROR: Template file $TEMPLATE_FILE not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Replace environment variables in the template and output to the squid.conf
|
# Replace environment variables in the template and output to the squid.conf
|
||||||
echo "[ENTRYPOINT] replacing environment variables in the template"
|
echo "[ENTRYPOINT] replacing environment variables in the template: $TEMPLATE_FILE"
|
||||||
awk '{
|
awk '{
|
||||||
while(match($0, /\${[A-Za-z_][A-Za-z_0-9]*}/)) {
|
while(match($0, /\${[A-Za-z_][A-Za-z_0-9]*}/)) {
|
||||||
var = substr($0, RSTART+2, RLENGTH-3)
|
var = substr($0, RSTART+2, RLENGTH-3)
|
||||||
@@ -35,7 +53,24 @@ awk '{
|
|||||||
$0 = substr($0, 1, RSTART-1) val substr($0, RSTART+RLENGTH)
|
$0 = substr($0, 1, RSTART-1) val substr($0, RSTART+RLENGTH)
|
||||||
}
|
}
|
||||||
print
|
print
|
||||||
}' /etc/squid/squid.conf.template > /etc/squid/squid.conf
|
}' "$TEMPLATE_FILE" > /etc/squid/squid.conf
|
||||||
|
|
||||||
|
# Log first few lines of generated config for debugging
|
||||||
|
echo "[ENTRYPOINT] First 30 lines of generated squid.conf:"
|
||||||
|
head -n 30 /etc/squid/squid.conf
|
||||||
|
|
||||||
|
# Create an empty conf.d directory if it doesn't exist
|
||||||
|
if [ ! -d /etc/squid/conf.d ]; then
|
||||||
|
echo "[ENTRYPOINT] creating /etc/squid/conf.d directory"
|
||||||
|
mkdir -p /etc/squid/conf.d
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If conf.d directory is empty, create a placeholder file to prevent include errors
|
||||||
|
# Only needed for production template which has the include directive
|
||||||
|
if [ "${SSRF_PROXY_DEV_MODE}" != "true" ] && [ -z "$(ls -A /etc/squid/conf.d/*.conf 2>/dev/null)" ]; then
|
||||||
|
echo "[ENTRYPOINT] conf.d directory is empty, creating placeholder"
|
||||||
|
echo "# Placeholder file to prevent include errors" > /etc/squid/conf.d/placeholder.conf
|
||||||
|
fi
|
||||||
|
|
||||||
/usr/sbin/squid -Nz
|
/usr/sbin/squid -Nz
|
||||||
echo "[ENTRYPOINT] starting squid"
|
echo "[ENTRYPOINT] starting squid"
|
||||||
|
|||||||
29
docker/ssrf_proxy/setup-testing.sh
Executable file
29
docker/ssrf_proxy/setup-testing.sh
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Setup script for SSRF proxy in testing/CI environments
|
||||||
|
# This script creates the necessary configuration to allow sandbox access during tests
|
||||||
|
|
||||||
|
echo "Setting up SSRF proxy for testing environment..."
|
||||||
|
|
||||||
|
# Create conf.d directory if it doesn't exist
|
||||||
|
mkdir -p "$(dirname "$0")/conf.d"
|
||||||
|
|
||||||
|
# Copy testing configuration
|
||||||
|
cat > "$(dirname "$0")/conf.d/00-testing-environment.conf" << 'EOF'
|
||||||
|
# CI/Testing Environment Configuration
|
||||||
|
# This configuration is automatically generated for testing
|
||||||
|
# DO NOT USE IN PRODUCTION
|
||||||
|
|
||||||
|
# Allow access to sandbox service for integration tests
|
||||||
|
acl sandbox_service dst sandbox
|
||||||
|
http_access allow sandbox_service
|
||||||
|
|
||||||
|
# Allow access to Docker internal networks for testing
|
||||||
|
acl docker_internal dst 172.16.0.0/12
|
||||||
|
http_access allow docker_internal
|
||||||
|
|
||||||
|
# Allow localhost connections for testing
|
||||||
|
acl test_localhost dst 127.0.0.1 ::1
|
||||||
|
http_access allow test_localhost
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "SSRF proxy testing configuration created successfully."
|
||||||
30
docker/ssrf_proxy/squid.conf.dev.template
Normal file
30
docker/ssrf_proxy/squid.conf.dev.template
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
################################## DEVELOPMENT MODE CONFIGURATION ##################################
|
||||||
|
# WARNING: This configuration DISABLES all SSRF protections!
|
||||||
|
# Only use this in development environments. NEVER use in production!
|
||||||
|
|
||||||
|
# Allow all requests - put this FIRST before any other rules
|
||||||
|
http_access allow all
|
||||||
|
|
||||||
|
################################## Proxy Server Configuration ##################################
|
||||||
|
http_port ${HTTP_PORT}
|
||||||
|
coredump_dir ${COREDUMP_DIR}
|
||||||
|
|
||||||
|
# Refresh patterns
|
||||||
|
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
|
||||||
|
|
||||||
|
################################## Reverse Proxy To Sandbox ##################################
|
||||||
|
http_port ${REVERSE_PROXY_PORT} accel vhost
|
||||||
|
cache_peer ${SANDBOX_HOST} parent ${SANDBOX_PORT} 0 no-query originserver
|
||||||
|
|
||||||
|
# Buffer size for file uploads
|
||||||
|
client_request_buffer_max_size 100 MB
|
||||||
|
|
||||||
|
# Debug logging for development
|
||||||
|
debug_options ALL,1
|
||||||
@@ -1,37 +1,79 @@
|
|||||||
acl localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN)
|
################################## SSRF Protection Configuration ##################################
|
||||||
acl localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN)
|
# This configuration prevents SSRF attacks by blocking access to private/internal networks
|
||||||
acl localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN)
|
# while allowing normal access to public internet resources.
|
||||||
acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
|
# To add additional restrictions or allowances, create config files in /etc/squid/conf.d/
|
||||||
acl localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN)
|
|
||||||
acl localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN)
|
################################## Security ACLs ##################################
|
||||||
acl localnet src fc00::/7 # RFC 4193 local private network range
|
# Define private/local networks that should be BLOCKED by default
|
||||||
acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines
|
acl private_networks dst 0.0.0.0/8 # "This" network
|
||||||
|
acl private_networks dst 10.0.0.0/8 # RFC 1918 private network
|
||||||
|
acl private_networks dst 100.64.0.0/10 # RFC 6598 shared address space
|
||||||
|
acl private_networks dst 127.0.0.0/8 # Loopback
|
||||||
|
acl private_networks dst 169.254.0.0/16 # RFC 3927 link-local
|
||||||
|
acl private_networks dst 172.16.0.0/12 # RFC 1918 private network
|
||||||
|
acl private_networks dst 192.168.0.0/16 # RFC 1918 private network
|
||||||
|
acl private_networks dst 224.0.0.0/4 # Multicast
|
||||||
|
acl private_networks dst 240.0.0.0/4 # Reserved for future use
|
||||||
|
acl private_networks dst 255.255.255.255/32 # Broadcast
|
||||||
|
acl private_networks dst fc00::/7 # IPv6 unique local addresses
|
||||||
|
acl private_networks dst fe80::/10 # IPv6 link-local addresses
|
||||||
|
acl private_networks dst ::1/128 # IPv6 loopback
|
||||||
|
acl private_networks dst ff00::/8 # IPv6 multicast
|
||||||
|
|
||||||
|
# Define localhost source
|
||||||
|
acl localhost src 127.0.0.1/32 ::1
|
||||||
|
|
||||||
|
# Define localnet ACL for compatibility with debian.conf (if present in ubuntu/squid image)
|
||||||
|
acl localnet src 10.0.0.0/8
|
||||||
|
acl localnet src 172.16.0.0/12
|
||||||
|
acl localnet src 192.168.0.0/16
|
||||||
|
|
||||||
|
# Define ports
|
||||||
acl SSL_ports port 443
|
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 80 # http
|
acl Safe_ports port 443 # https
|
||||||
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
|
acl CONNECT method CONNECT
|
||||||
acl allowed_domains dstdomain .marketplace.dify.ai
|
|
||||||
http_access allow allowed_domains
|
################################## Access Control Rules ##################################
|
||||||
|
# IMPORTANT: Order matters! First matching rule wins.
|
||||||
|
|
||||||
|
# Special rule for reverse proxy port (sandbox access)
|
||||||
|
# This must come FIRST to bypass other restrictions for sandbox connectivity
|
||||||
|
acl reverse_proxy_port myport ${REVERSE_PROXY_PORT}
|
||||||
|
http_access allow reverse_proxy_port
|
||||||
|
|
||||||
|
# DENY access to all private/local networks - prevents SSRF attacks
|
||||||
|
http_access deny private_networks
|
||||||
|
|
||||||
|
# DENY non-safe ports
|
||||||
http_access deny !Safe_ports
|
http_access deny !Safe_ports
|
||||||
|
|
||||||
|
# DENY CONNECT to non-SSL ports
|
||||||
http_access deny CONNECT !SSL_ports
|
http_access deny CONNECT !SSL_ports
|
||||||
|
|
||||||
|
# Allow manager access from localhost only
|
||||||
http_access allow localhost manager
|
http_access allow localhost manager
|
||||||
http_access deny manager
|
http_access deny manager
|
||||||
http_access allow localhost
|
|
||||||
include /etc/squid/conf.d/*.conf
|
|
||||||
http_access deny all
|
|
||||||
|
|
||||||
################################## Proxy Server ################################
|
# Note: We don't have a blanket "allow localhost" rule to prevent bypassing SSRF protection
|
||||||
|
# Localhost connections will still be subject to the same restrictions as other clients
|
||||||
|
|
||||||
|
# User overrides in /etc/squid/conf.d/*.conf should be placed here
|
||||||
|
# These can be used to add additional restrictions or allowances
|
||||||
|
# Note: debian.conf may be present by default in the ubuntu/squid image
|
||||||
|
# The include directive uses a script to handle optional includes
|
||||||
|
include /etc/squid/conf.d/*.conf
|
||||||
|
|
||||||
|
# Allow all other requests (public internet resources)
|
||||||
|
# This makes the proxy work as a blacklist (blocking private networks)
|
||||||
|
# rather than a whitelist (blocking everything except allowed)
|
||||||
|
http_access allow all
|
||||||
|
|
||||||
|
################################## Proxy Server Configuration ##################################
|
||||||
http_port ${HTTP_PORT}
|
http_port ${HTTP_PORT}
|
||||||
coredump_dir ${COREDUMP_DIR}
|
coredump_dir ${COREDUMP_DIR}
|
||||||
|
|
||||||
|
# Refresh patterns
|
||||||
refresh_pattern ^ftp: 1440 20% 10080
|
refresh_pattern ^ftp: 1440 20% 10080
|
||||||
refresh_pattern ^gopher: 1440 0% 1440
|
refresh_pattern ^gopher: 1440 0% 1440
|
||||||
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
|
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
|
||||||
@@ -41,16 +83,14 @@ refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
|
|||||||
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
|
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
|
||||||
refresh_pattern . 0 20% 4320
|
refresh_pattern . 0 20% 4320
|
||||||
|
|
||||||
|
# Upstream proxy configuration (uncomment and configure if needed)
|
||||||
|
# cache_peer <upstream_proxy_ip> parent 3128 0 no-query no-digest no-netdb-exchange default
|
||||||
|
|
||||||
# cache_dir ufs /var/spool/squid 100 16 256
|
################################## Reverse Proxy To Sandbox ##################################
|
||||||
# upstream proxy, set to your own upstream proxy IP to avoid SSRF attacks
|
# This configuration allows the sandbox to be accessed through the reverse proxy
|
||||||
# cache_peer 172.1.1.1 parent 3128 0 no-query no-digest no-netdb-exchange default
|
# The reverse proxy port is separate from the main proxy port and has different rules
|
||||||
|
|
||||||
################################## Reverse Proxy To Sandbox ################################
|
|
||||||
http_port ${REVERSE_PROXY_PORT} accel vhost
|
http_port ${REVERSE_PROXY_PORT} accel vhost
|
||||||
cache_peer ${SANDBOX_HOST} parent ${SANDBOX_PORT} 0 no-query originserver
|
cache_peer ${SANDBOX_HOST} parent ${SANDBOX_PORT} 0 no-query originserver
|
||||||
acl src_all src all
|
|
||||||
http_access allow src_all
|
|
||||||
|
|
||||||
# Unless the option's size is increased, an error will occur when uploading more than two files.
|
# Buffer size for file uploads
|
||||||
client_request_buffer_max_size 100 MB
|
client_request_buffer_max_size 100 MB
|
||||||
Reference in New Issue
Block a user