fix: is_local_request rejects non-RFC1918 cell subnets; helper image hardcoded
Two bugs triggered when ip_range is set to a subnet outside 172.16.0.0/12 (e.g. 172.0.0.0/24): 1. is_local_request() used ip.is_private which returns False for 172.0.x.x, causing Caddy reverse-proxy requests to get 403 on the containers endpoint. Fix: also accept IPs in the configured cell-network subnet. 2. apply_pending_config() hardcoded 'pic_api:latest' as the helper container image. docker-compose v1 builds pic_api:latest (underscore) but compose v2+ builds pic-api:latest (hyphen). On a v2 install the helper would fail to start silently, leaving the network unreconstructed after an ip_range change. Fix: read the actual image tag from cell-api's own container metadata. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+33
-23
@@ -320,31 +320,35 @@ health_monitor_thread = threading.Thread(target=health_monitor_loop, daemon=True
|
||||
health_monitor_thread.start()
|
||||
|
||||
def is_local_request():
|
||||
# Allow requests from localhost, Docker networks, and internal IPs
|
||||
remote_addr = request.remote_addr
|
||||
forwarded_for = request.headers.get('X-Forwarded-For', '')
|
||||
|
||||
# Check direct remote address
|
||||
if remote_addr in ('127.0.0.1', '::1', 'localhost'):
|
||||
return True
|
||||
|
||||
# Check forwarded address (for reverse proxy scenarios)
|
||||
if forwarded_for:
|
||||
forwarded_ips = [ip.strip() for ip in forwarded_for.split(',')]
|
||||
for ip in forwarded_ips:
|
||||
if ip in ('127.0.0.1', '::1', 'localhost'):
|
||||
return True
|
||||
|
||||
# Allow Docker internal networks (172.x.x.x, 192.168.x.x, 10.x.x.x)
|
||||
if remote_addr:
|
||||
|
||||
def _allowed(addr):
|
||||
if not addr:
|
||||
return False
|
||||
if addr in ('127.0.0.1', '::1', 'localhost'):
|
||||
return True
|
||||
try:
|
||||
import ipaddress
|
||||
ip = ipaddress.ip_address(remote_addr)
|
||||
import ipaddress as _ipa
|
||||
ip = _ipa.ip_address(addr)
|
||||
if ip.is_private or ip.is_loopback:
|
||||
return True
|
||||
except:
|
||||
# Also allow IPs in the configured cell-network, which may fall outside
|
||||
# RFC-1918 (e.g. 172.0.0.0/24 is not in 172.16.0.0/12).
|
||||
cell_net = config_manager.configs.get('_identity', {}).get(
|
||||
'ip_range', os.environ.get('CELL_IP_RANGE', '172.20.0.0/16'))
|
||||
if ip in _ipa.ip_network(cell_net, strict=False):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
if _allowed(remote_addr):
|
||||
return True
|
||||
if forwarded_for:
|
||||
for addr in forwarded_for.split(','):
|
||||
if _allowed(addr.strip()):
|
||||
return True
|
||||
return False
|
||||
|
||||
@app.route('/health', methods=['GET'])
|
||||
@@ -692,13 +696,19 @@ def apply_pending_config():
|
||||
if not pending.get('needs_restart'):
|
||||
return jsonify({'message': 'No pending changes to apply'})
|
||||
|
||||
# Get project working dir from our own container labels (set by docker-compose)
|
||||
# Get project working dir and image name from our own container labels
|
||||
project_dir = '/home/roof/pic'
|
||||
api_image = 'pic_api:latest' # fallback (docker-compose v1 naming)
|
||||
try:
|
||||
import docker as _docker_sdk
|
||||
_client = _docker_sdk.from_env()
|
||||
_self = _client.containers.get('cell-api')
|
||||
project_dir = _self.labels.get('com.docker.compose.project.working_dir', project_dir)
|
||||
# Use the actual image tag so the helper works regardless of compose version
|
||||
# (docker-compose v1 builds pic_api:latest, compose v2+ builds pic-api:latest)
|
||||
tags = _self.image.tags
|
||||
if tags:
|
||||
api_image = tags[0]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -717,8 +727,8 @@ def apply_pending_config():
|
||||
if '*' in containers:
|
||||
# All-services restart: `docker compose down` or `up -d` may stop/recreate the
|
||||
# API container itself, killing this background thread mid-operation.
|
||||
# Spawn an independent helper container using pic_api:latest that has docker CLI
|
||||
# and survives cell-api being stopped/recreated.
|
||||
# Spawn an independent helper container (same image as cell-api) that has docker
|
||||
# CLI and survives cell-api being stopped/recreated.
|
||||
if needs_network_recreate:
|
||||
helper_script = (
|
||||
f'sleep 2'
|
||||
@@ -741,7 +751,7 @@ def apply_pending_config():
|
||||
'-v', '/var/run/docker.sock:/var/run/docker.sock',
|
||||
'-v', f'{project_dir}:{project_dir}',
|
||||
'--entrypoint', 'sh',
|
||||
'pic_api:latest',
|
||||
api_image,
|
||||
'-c', helper_script],
|
||||
close_fds=True,
|
||||
stdout=_subprocess.DEVNULL,
|
||||
|
||||
Reference in New Issue
Block a user