security updates

This commit is contained in:
2026-05-31 10:18:50 +02:00
parent 3b3f589d64
commit f313701e5e
14 changed files with 327 additions and 34 deletions
+4 -1
View File
@@ -1,12 +1,15 @@
name: bandwidth-worker-dev
services:
bandwidth-worker:
bandwidth-worker-dev:
build: .
image: bandwidth-worker-dev:1.0.0
container_name: bandwidth-worker-dev
restart: unless-stopped
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
user: "1000:1000"
env_file: .env
networks:
- dev_edge
+46 -2
View File
@@ -24,6 +24,10 @@ FRPS_PASS = os.environ["FRPS_DASHBOARD_PASS"]
SUPABASE_URL = os.environ.get("SUPABASE_URL", "http://supabase-kong:8000")
SERVICE_ROLE = os.environ["SUPABASE_SERVICE_ROLE_KEY"]
REDIS_URL = os.environ["REDIS_URL"]
# TTL for the edge active-state cache key (tunnel:active:<sub>) read by the
# Caddy forward_auth gate. Short so a deactivation propagates fast and a stale
# "1" self-heals within one poll cycle; refreshed every poll while active.
ACTIVE_TTL = int(os.environ.get("ACTIVE_STATE_TTL_SECONDS", "45"))
logging.basicConfig(
level=logging.INFO,
@@ -98,6 +102,13 @@ def enforce_quota(client: httpx.Client, subdomain: str, row: dict[str, Any], new
except Exception as e:
log.warning("redis auth-cache invalidate failed for %s: %s", subdomain, e)
# Flip the edge active-state key so the Caddy forward_auth gate denies the
# next request (within ~1 request) even on a still-connected tunnel.
try:
rds.set(f"tunnel:active:{subdomain}", "0", ex=ACTIVE_TTL)
except Exception as e:
log.warning("redis active-state deactivate failed for %s: %s", subdomain, e)
log.warning(
"QUOTA EXCEEDED: subdomain=%s used=%d quota=%d -> deactivated",
subdomain, new_used, quota,
@@ -145,16 +156,49 @@ def record_delta(client: httpx.Client, subdomain: str, delta: int) -> None:
enforce_quota(client, subdomain, row, new_used)
# Keep the edge active-state key fresh so the forward_auth gate has a hot,
# authoritative value (covers reactivation and TTL expiry of an active key).
quota = int(row.get("quota_bytes") or 0)
active_now = bool(row.get("is_active")) and (quota <= 0 or new_used < quota)
try:
rds.set(f"tunnel:active:{subdomain}", "1" if active_now else "0", ex=ACTIVE_TTL)
except Exception as e:
log.warning("redis active-state refresh failed for %s: %s", subdomain, e)
_schema_checked = False
def assert_api_schema(proxies: list[dict[str, Any]]) -> None:
"""Fail loud if the frps dashboard API stops returning the expected
camelCase traffic fields (e.g. after an frps upgrade), so the quota
accounting bug cannot silently return."""
global _schema_checked
if _schema_checked or not proxies:
return
sample = proxies[0]
if "todayTrafficIn" not in sample:
log.error(
"frps API schema mismatch: 'todayTrafficIn' missing from "
"proxy keys=%s; quota accounting is DISABLED until the field "
"names are fixed",
sorted(sample.keys()),
)
else:
log.info("frps API schema OK: todayTrafficIn present")
_schema_checked = True
def poll_once(client: httpx.Client) -> None:
proxies = fetch_proxies(client)
log.info("poll: %d proxies", len(proxies))
assert_api_schema(proxies)
for p in proxies:
name = p.get("name") or ""
if not name:
continue
traffic_in = int(p.get("today_traffic_in") or 0)
traffic_out = int(p.get("today_traffic_out") or 0)
traffic_in = int(p.get("todayTrafficIn") or 0)
traffic_out = int(p.get("todayTrafficOut") or 0)
total = traffic_in + traffic_out
prev = previous_total(name)
delta = total - prev
+2 -2
View File
@@ -1,6 +1,6 @@
name: frps-dev
services:
frps:
frps-dev:
image: snowdreamtech/frps:0.65.0
container_name: frps-dev
restart: unless-stopped
@@ -8,7 +8,7 @@ services:
- no-new-privileges:true
ports:
# Dev tunnel ingress (dev frpc clients connect here). Public port 7001.
- "7001:7001"
- "127.0.0.1:7001:7001"
# Dashboard/API port 7500 unpublished. bandwidth-worker-dev reaches it
# internally via frps-dev:7500 on the dev_edge network.
volumes:
+1 -1
View File
@@ -1,6 +1,6 @@
name: redis-dev
services:
redis:
redis-dev:
image: redis:7.2-alpine
container_name: redis-dev
restart: unless-stopped
+4 -1
View File
@@ -1,12 +1,15 @@
name: stripe-stub-dev
services:
stripe-stub:
stripe-stub-dev:
build: .
image: stripe-stub-dev:1.0.0
container_name: stripe-stub-dev
restart: unless-stopped
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
user: "1000:1000"
env_file: .env
networks:
- dev_edge
+30 -4
View File
@@ -1,13 +1,39 @@
# Dev override: attach kong + edge functions to the external "dev_edge" network
# so the shared Caddy and the dev frps can reach them by DNS name, and set the
# dev tunnel base domain for the on-demand-TLS authorizer + frps auth plugin.
#
# COLLISION-PROOFING (alias-residual elimination):
# * The dev service KEYS are now globally unique (kong-dev / functions-dev), so
# the auto service-name alias on the Caddy-shared "dev_edge" network is the
# UNIQUE *-dev name -- a bare logical name (kong/functions) can never resolve
# to a dev container from Caddy.
# * The bare names are re-exposed ONLY on the internal project "default" network
# (which Caddy is NOT attached to) so every in-stack consumer that still uses
# http://kong:8000 / http://functions:9000 (kong.yml, edge-fn source, studio /
# functions env, vector) keeps resolving with zero changes.
services:
kong:
networks: [default, dev_edge]
functions:
networks: [default, dev_edge]
kong-dev:
networks:
default:
aliases:
- kong
dev_edge:
aliases:
- kong-dev
functions-dev:
networks:
default:
aliases:
- functions
dev_edge:
aliases:
- functions-dev
ask-delegation: {}
environment:
TUNNEL_BASE_DOMAIN: dev.linumiq.net
networks:
dev_edge:
external: true
# On-demand-TLS ask delegation network (shared with prod edge authorizer).
ask-delegation:
external: true
+4 -3
View File
@@ -46,7 +46,7 @@ services:
# Uncomment to use Big Query backend for analytics
# NEXT_ANALYTICS_BACKEND_PROVIDER: bigquery
kong:
kong-dev:
container_name: supabase-dev-kong
image: kong:2.8.1
restart: unless-stopped
@@ -62,7 +62,7 @@ services:
KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
# https://github.com/supabase/cli/issues/14
KONG_DNS_ORDER: LAST,A,CNAME
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth,request-termination
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
SUPABASE_ANON_KEY: ${ANON_KEY}
@@ -299,7 +299,7 @@ services:
PG_META_DB_USER: supabase_admin
PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
functions:
functions-dev:
container_name: supabase-dev-edge-functions
image: supabase/edge-runtime:v1.58.3
restart: unless-stopped
@@ -316,6 +316,7 @@ services:
VERIFY_JWT: "${FUNCTIONS_VERIFY_JWT}"
REDIS_URL: ${REDIS_URL}
STRIPE_STUB_WEBHOOK_SECRET: ${STRIPE_STUB_WEBHOOK_SECRET}
AUTH_WEBHOOK_SECRET: ${AUTH_WEBHOOK_SECRET}
volumes:
- ./volumes/functions:/home/deno/functions:Z
command: