chore: initial import of envoy test config

This commit is contained in:
Andrey A. committed 2026-02-11 12:37:12 +01:00
1 parent 21d7ac0518
commit 3525fe6159
6 files changed
+622 -1

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
# Envoy override for Kong
# Usage: docker compose -f docker-compose.yml -f docker-compose.envoy.yml up
services:
# Disable the original Kong service
kong:
profiles:
- disabled
# Envoy API gateway
api:
container_name: supabase-envoy
image: envoyproxy/envoy:v1.28.0
restart: unless-stopped
ports:
- ${KONG_HTTP_PORT}:8000/tcp
- ${KONG_HTTPS_PORT:-8443}:8443/tcp
- 9901:9901/tcp
volumes:
- ./volumes/api/envoy/envoy.yaml:/etc/envoy/envoy.yaml:ro
- ./volumes/api/envoy/cds.yaml:/etc/envoy/cds.yaml:ro
- ./volumes/api/envoy/lds.yaml.template:/etc/envoy/lds.yaml.template:ro
- ./volumes/api/envoy/docker-entrypoint.sh:/docker-entrypoint.sh:ro
depends_on:
analytics:
condition: service_healthy
environment:
ANON_KEY: ${ANON_KEY}
SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
DASHBOARD_USERNAME: ${DASHBOARD_USERNAME}
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}
entrypoint: ["/bin/sh", "/docker-entrypoint.sh"]
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9901/ready"]
interval: 10s
timeout: 5s
retries: 3
networks:
default:
aliases:
- kong
- envoy
+160
View File
@@ -0,0 +1,160 @@
resources:
- '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
name: auth
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: auth
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: auth
port_value: 9999
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 10000
max_pending_requests: 10000
max_requests: 10000
- '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
name: rest
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: rest
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: rest
port_value: 3000
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 10000
max_pending_requests: 10000
max_requests: 10000
- '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
name: realtime
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: realtime
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: realtime-dev.supabase-realtime
port_value: 4000
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 10000
max_pending_requests: 10000
max_requests: 10000
- '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
name: storage
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: storage
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: storage
port_value: 5000
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 10000
max_pending_requests: 10000
max_requests: 10000
- '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
name: functions
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: functions
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: functions
port_value: 9000
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 10000
max_pending_requests: 10000
max_requests: 10000
- '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
name: analytics
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: analytics
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: analytics
port_value: 4000
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 10000
max_pending_requests: 10000
max_requests: 10000
- '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
name: meta
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: meta
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: meta
port_value: 8080
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 10000
max_pending_requests: 10000
max_requests: 10000
- '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
name: studio
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: studio
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: studio
port_value: 3000
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 10000
max_pending_requests: 10000
max_requests: 10000
+21
View File
@@ -0,0 +1,21 @@
#!/bin/sh
set -e
# Generate base64 encoded basic auth credentials for dashboard
# Format: username:password -> base64
DASHBOARD_BASIC_AUTH=$(printf '%s' "${DASHBOARD_USERNAME}:${DASHBOARD_PASSWORD}" | base64 | tr -d '\n')
echo "Generating Envoy configuration..."
# Process the lds.yaml template with environment variables using sed
# Using | as delimiter since JWT tokens contain /
sed -e "s|\${ANON_KEY}|${ANON_KEY}|g" \
-e "s|\${SERVICE_ROLE_KEY}|${SERVICE_ROLE_KEY}|g" \
-e "s|\${DASHBOARD_BASIC_AUTH}|${DASHBOARD_BASIC_AUTH}|g" \
/etc/envoy/lds.yaml.template > /etc/envoy/lds.yaml
echo "Envoy configuration generated successfully"
echo "Starting Envoy..."
# Start Envoy
exec envoy -c /etc/envoy/envoy.yaml "$@"
+24
View File
@@ -0,0 +1,24 @@
dynamic_resources:
cds_config:
path_config_source:
path: /etc/envoy/cds.yaml
resource_api_version: V3
lds_config:
path_config_source:
path: /etc/envoy/lds.yaml
resource_api_version: V3
node:
cluster: supabase_cluster
id: supabase_node
overload_manager:
resource_monitors:
- name: envoy.resource_monitors.global_downstream_max_connections
typed_config:
'@type': >-
type.googleapis.com/envoy.extensions.resource_monitors.downstream_connections.v3.DownstreamConnectionsConfig
max_active_downstream_connections: 30000
admin:
address:
socket_address:
address: 0.0.0.0
port_value: 9901
+374
View File
@@ -0,0 +1,374 @@
resources:
- '@type': type.googleapis.com/envoy.config.listener.v3.Listener
name: http_listener
address:
socket_address:
address: 0.0.0.0
port_value: 8000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
upgrade_configs:
- upgrade_type: websocket
access_log:
- name: envoy.access_loggers.stdout
typed_config:
'@type': type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
log_format:
text_format_source:
inline_string: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT% - - [%START_TIME(%d/%b/%Y:%H:%M:%S %z)%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %BYTES_SENT% \"%REQ(REFERER)%\" \"%REQ(USER-AGENT)%\"\n"
http_filters:
# CORS filter
- name: envoy.filters.http.cors
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
# RBAC filter for API key validation
- name: envoy.filters.http.rbac
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC
rules:
action: DENY
policies:
# Deny requests without valid API key to protected routes
api_key_required:
permissions:
- and_rules:
rules:
# Match protected paths that need API key
- or_rules:
rules:
- url_path:
path:
prefix: /auth/v1/
- url_path:
path:
prefix: /rest/v1/
- url_path:
path:
prefix: /graphql/v1
- url_path:
path:
prefix: /realtime/v1/
- url_path:
path:
prefix: /pg/
# Exclude open auth routes
- not_rule:
or_rules:
rules:
- url_path:
path:
exact: /auth/v1/verify
- url_path:
path:
prefix: /auth/v1/verify?
- url_path:
path:
exact: /auth/v1/callback
- url_path:
path:
prefix: /auth/v1/callback?
- url_path:
path:
exact: /auth/v1/authorize
- url_path:
path:
prefix: /auth/v1/authorize?
principals:
# Deny if no valid API key
- not_id:
or_ids:
ids:
- header:
name: apikey
string_match:
exact: "${ANON_KEY}"
- header:
name: apikey
string_match:
exact: "${SERVICE_ROLE_KEY}"
- header:
name: Authorization
string_match:
prefix: "Bearer ${ANON_KEY}"
- header:
name: Authorization
string_match:
prefix: "Bearer ${SERVICE_ROLE_KEY}"
# /pg/ requires service_role only
pg_service_role_only:
permissions:
- url_path:
path:
prefix: /pg/
principals:
- not_id:
or_ids:
ids:
- header:
name: apikey
string_match:
exact: "${SERVICE_ROLE_KEY}"
- header:
name: Authorization
string_match:
prefix: "Bearer ${SERVICE_ROLE_KEY}"
# Lua filter for removing apikey from forwarded requests where hide_credentials is needed
- name: envoy.filters.http.lua
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
default_source_code:
inline_string: |
function envoy_on_request(request_handle)
end
source_codes:
hide_apikey:
inline_string: |
function envoy_on_request(request_handle)
request_handle:headers():remove("apikey")
-- Also strip apikey from query string
local path = request_handle:headers():get(":path")
if path then
path = path:gsub("&apikey=[^&]*", ""):gsub("%?apikey=[^&]*&", "?"):gsub("%?apikey=[^&]*$", "")
request_handle:headers():replace(":path", path)
end
end
add_graphql_header:
inline_string: |
function envoy_on_request(request_handle)
request_handle:headers():add("Content-Profile", "graphql_public")
request_handle:headers():remove("apikey")
local path = request_handle:headers():get(":path")
if path then
path = path:gsub("&apikey=[^&]*", ""):gsub("%?apikey=[^&]*&", "?"):gsub("%?apikey=[^&]*$", "")
request_handle:headers():replace(":path", path)
end
end
# Router filter
- name: envoy.filters.http.router
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: supabase_services
domains:
- "*"
typed_per_filter_config:
envoy.filters.http.cors:
'@type': type.googleapis.com/envoy.extensions.filters.http.cors.v3.CorsPolicy
allow_origin_string_match:
- safe_regex:
regex: ".*"
allow_methods: GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS,TRACE,CONNECT
allow_headers: "*"
expose_headers: "*"
max_age: "3600"
allow_credentials: true
routes:
# Block /api/mcp
- match:
prefix: /api/mcp
direct_response:
status: 403
body:
inline_string: "Access is forbidden."
# Block /mcp
- match:
prefix: /mcp
direct_response:
status: 403
body:
inline_string: "Access is forbidden."
# Open Auth routes (no API key required)
- match:
path: /auth/v1/verify
route:
cluster: auth
prefix_rewrite: /verify
timeout: 30s
typed_per_filter_config:
envoy.filters.http.rbac:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
- match:
prefix: /auth/v1/verify
route:
cluster: auth
regex_rewrite:
pattern:
regex: ^/auth/v1/verify
substitution: /verify
timeout: 30s
typed_per_filter_config:
envoy.filters.http.rbac:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
- match:
path: /auth/v1/callback
route:
cluster: auth
prefix_rewrite: /callback
timeout: 30s
typed_per_filter_config:
envoy.filters.http.rbac:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
- match:
prefix: /auth/v1/callback
route:
cluster: auth
regex_rewrite:
pattern:
regex: ^/auth/v1/callback
substitution: /callback
timeout: 30s
typed_per_filter_config:
envoy.filters.http.rbac:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
- match:
path: /auth/v1/authorize
route:
cluster: auth
prefix_rewrite: /authorize
timeout: 30s
typed_per_filter_config:
envoy.filters.http.rbac:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
- match:
prefix: /auth/v1/authorize
route:
cluster: auth
regex_rewrite:
pattern:
regex: ^/auth/v1/authorize
substitution: /authorize
timeout: 30s
typed_per_filter_config:
envoy.filters.http.rbac:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
# Secure Auth routes (API key required, credentials NOT hidden)
- match:
prefix: /auth/v1/
route:
cluster: auth
prefix_rewrite: /
timeout: 30s
# REST routes (API key required, credentials hidden)
- match:
prefix: /rest/v1/
route:
cluster: rest
prefix_rewrite: /
timeout: 120s
typed_per_filter_config:
envoy.filters.http.lua:
'@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
name: hide_apikey
# GraphQL routes (API key required, add Content-Profile header)
- match:
prefix: /graphql/v1
route:
cluster: rest
prefix_rewrite: /rpc/graphql
timeout: 120s
typed_per_filter_config:
envoy.filters.http.lua:
'@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
name: add_graphql_header
# Realtime WebSocket routes
- match:
prefix: /realtime/v1/
route:
cluster: realtime
prefix_rewrite: /socket/
timeout: 0s
upgrade_configs:
- upgrade_type: websocket
# Realtime REST API
- match:
prefix: /realtime/v1/api
route:
cluster: realtime
prefix_rewrite: /api
timeout: 30s
# Storage routes (no API key required - storage handles its own auth)
- match:
prefix: /storage/v1/
route:
cluster: storage
prefix_rewrite: /
timeout: 300s
typed_per_filter_config:
envoy.filters.http.rbac:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
# Edge Functions routes (no API key required - functions handle their own auth)
- match:
prefix: /functions/v1/
route:
cluster: functions
prefix_rewrite: /
timeout: 300s
typed_per_filter_config:
envoy.filters.http.rbac:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
# Analytics routes (no API key required)
- match:
prefix: /analytics/v1/
route:
cluster: analytics
prefix_rewrite: /
timeout: 30s
typed_per_filter_config:
envoy.filters.http.rbac:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
# pg-meta routes (service_role only)
- match:
prefix: /pg/
route:
cluster: meta
prefix_rewrite: /
timeout: 30s
# Studio/Dashboard (catch-all with basic auth via RBAC)
- match:
prefix: /
route:
cluster: studio
timeout: 30s
typed_per_filter_config:
envoy.filters.http.rbac:
'@type': type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
rbac:
rules:
action: DENY
policies:
require_basic_auth:
permissions:
- any: true
principals:
- not_id:
header:
name: authorization
string_match:
exact: "Basic ${DASHBOARD_BASIC_AUTH}"
+1 -1
View File
@@ -30,7 +30,7 @@ transforms:
inputs:
- project_logs
route:
kong: '.appname == "supabase-kong"'
kong: '.appname == "supabase-kong" || .appname == "supabase-envoy"'
auth: '.appname == "supabase-auth"'
rest: '.appname == "supabase-rest"'
realtime: '.appname == "realtime-dev.supabase-realtime"'