Add docker compose services (tailscale-mcp + nginx-claude-filter) behind a claude-funnel profile that expose the login-flow MCP server via Tailscale Funnel with IP-based access control: - /mcp endpoint restricted to Claude AI outbound IPs (160.79.104.0/21) - /oauth/*, /.well-known/*, /app paths open to all IPs (user login flow) - All other paths return 404 Also add favicon.png served at /favicon.ico for connector directory discovery (Google favicon service). Usage: docker compose --profile login-flow --profile claude-funnel up -d Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
89 lines
2.9 KiB
Nginx Configuration File
89 lines
2.9 KiB
Nginx Configuration File
# Claude AI IP Filter for Tailscale Funnel
|
|
#
|
|
# Routes MCP transport to Claude AI IPs only, while allowing
|
|
# OAuth/auth endpoints from any IP (needed for user login flow).
|
|
#
|
|
# Pattern: homelab-argocd/atlantis/templates/nginx-webhook-config.yaml
|
|
|
|
worker_processes auto;
|
|
pid /var/run/nginx.pid;
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
}
|
|
|
|
http {
|
|
include /etc/nginx/mime.types;
|
|
default_type application/octet-stream;
|
|
|
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|
'$status $body_bytes_sent "$http_referer" '
|
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
|
|
access_log /var/log/nginx/access.log main;
|
|
error_log /var/log/nginx/error.log warn;
|
|
|
|
sendfile on;
|
|
keepalive_timeout 65;
|
|
|
|
# Map Claude AI outbound IPs (must be at http level)
|
|
geo $remote_addr $is_claude_ai {
|
|
default 0;
|
|
|
|
# Anthropic Claude AI Outbound IP Range
|
|
# Source: https://docs.claude.com/en/api/ip-addresses
|
|
# Last updated: 2026-03-29
|
|
|
|
# IPv4 range
|
|
160.79.104.0/21 1; # Claude AI
|
|
}
|
|
|
|
server {
|
|
listen 8080;
|
|
server_name _;
|
|
|
|
# Trust Tailscale proxy for real IP extraction
|
|
real_ip_header X-Forwarded-For;
|
|
set_real_ip_from 100.64.0.0/10; # Tailscale CGNAT range
|
|
set_real_ip_from 10.0.0.0/8; # Docker internal networks
|
|
set_real_ip_from 172.16.0.0/12; # Docker bridge networks
|
|
real_ip_recursive on;
|
|
|
|
# OAuth/auth endpoints + favicon - allow ALL IPs (user browser needs access for login flow)
|
|
location ~ ^/(oauth|\.well-known|app|favicon\.ico)(/|$) {
|
|
proxy_pass http://mcp-login-flow:8004;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto https;
|
|
}
|
|
|
|
# MCP transport endpoint - Claude AI IPs ONLY
|
|
# login-flow uses streamable-http transport (no /sse needed)
|
|
location /mcp {
|
|
if ($is_claude_ai = 0) {
|
|
return 403 '{"error": "Access denied - IP not in Claude AI range", "source_ip": "$remote_addr"}\n';
|
|
}
|
|
|
|
proxy_pass http://mcp-login-flow:8004;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto https;
|
|
|
|
# Streamable HTTP support (long-lived connections)
|
|
proxy_buffering off;
|
|
proxy_cache off;
|
|
proxy_read_timeout 86400s;
|
|
proxy_send_timeout 86400s;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Connection '';
|
|
}
|
|
|
|
# Default - deny everything else
|
|
location / {
|
|
return 404 '{"error": "Not found"}\n';
|
|
}
|
|
}
|
|
}
|