chore: update proxy config

This commit is contained in:
zhun
2026-04-15 02:48:56 +00:00
parent b4166db0d9
commit 6b1bf8e81d

View File

@@ -58,7 +58,8 @@ INTERNAL_NETWORK = "cybergym-internal"
DEFAULT_ALLOWLIST_PATH = Path(__file__).with_name("default_allowlist.txt") DEFAULT_ALLOWLIST_PATH = Path(__file__).with_name("default_allowlist.txt")
ALLOWLIST_CONTAINER_PATH = "/etc/squid/allowed_domains.txt" DOMAIN_ALLOWLIST_CONTAINER_PATH = "/etc/squid/allowed_domains.txt"
IP_ALLOWLIST_CONTAINER_PATH = "/etc/squid/allowed_ips.txt"
SQUID_CONF_TEMPLATE = """\ SQUID_CONF_TEMPLATE = """\
# --- CyberGym domain-allowlist proxy --- # --- CyberGym domain-allowlist proxy ---
@@ -67,16 +68,16 @@ acl SSL_ports port 443
acl Safe_ports port 80 443 {extra_ports} acl Safe_ports port 80 443 {extra_ports}
acl CONNECT method CONNECT acl CONNECT method CONNECT
# Allowed destinations — loaded from external file # Allowed destinations — loaded from external files
acl allowed_domains dstdomain "{allowlist_path}" acl allowed_domains dstdomain "{domain_allowlist_path}"
{ip_acls} {ip_acl}
# Rules # Rules
http_access deny !Safe_ports http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports http_access deny CONNECT !SSL_ports
http_access allow CONNECT allowed_domains http_access allow CONNECT allowed_domains
http_access allow allowed_domains http_access allow allowed_domains
{ip_rules} {ip_rule}
http_access deny all http_access deny all
http_port {port} http_port {port}
@@ -117,7 +118,9 @@ class ProxyManager:
def __init__( def __init__(
self, self,
allowlist_path: str | Path | None = None, allowlist_path: str | Path | None = None,
allowed_ips: list[str] | None = None, extra_domains: list[str] | None = None,
ip_allowlist_path: str | Path | None = None,
extra_ips: list[str] | None = None,
no_proxy: list[str] | None = None, no_proxy: list[str] | None = None,
proxy_image: str = PROXY_IMAGE, proxy_image: str = PROXY_IMAGE,
proxy_port: int = PROXY_PORT, proxy_port: int = PROXY_PORT,
@@ -127,7 +130,15 @@ class ProxyManager:
self.allowlist_path = Path(allowlist_path or DEFAULT_ALLOWLIST_PATH).resolve() self.allowlist_path = Path(allowlist_path or DEFAULT_ALLOWLIST_PATH).resolve()
if not self.allowlist_path.is_file(): if not self.allowlist_path.is_file():
raise FileNotFoundError(f"Allowlist file not found: {self.allowlist_path}") raise FileNotFoundError(f"Allowlist file not found: {self.allowlist_path}")
self.allowed_ips = allowed_ips or [] self.extra_domains = extra_domains or []
self.ip_allowlist_path = (
Path(ip_allowlist_path).resolve() if ip_allowlist_path else None
)
if self.ip_allowlist_path and not self.ip_allowlist_path.is_file():
raise FileNotFoundError(
f"IP allowlist file not found: {self.ip_allowlist_path}"
)
self.extra_ips = extra_ips or []
self.no_proxy = no_proxy or [] self.no_proxy = no_proxy or []
self.proxy_image = proxy_image self.proxy_image = proxy_image
self.proxy_port = proxy_port self.proxy_port = proxy_port
@@ -201,8 +212,8 @@ class ProxyManager:
gw = self.host_gateway gw = self.host_gateway
if gw not in self.no_proxy: if gw not in self.no_proxy:
self.no_proxy.append(gw) self.no_proxy.append(gw)
if gw not in self.allowed_ips: if gw not in self.extra_ips:
self.allowed_ips.append(gw) self.extra_ips.append(gw)
for local in ["localhost", "127.0.0.1"]: for local in ["localhost", "127.0.0.1"]:
if local not in self.no_proxy: if local not in self.no_proxy:
@@ -213,15 +224,15 @@ class ProxyManager:
def update(self) -> None: def update(self) -> None:
"""Restart the proxy container with the current configuration. """Restart the proxy container with the current configuration.
Useful after modifying the allowlist file on the host. The new Useful after modifying the allowlist files on the host. The new
container bind-mounts the (possibly updated) allowlist path. container gets fresh copies of all config files.
The internal network is preserved so other containers stay connected. The internal network is preserved so other containers stay connected.
""" """
self.stop() self.stop()
self._ensure_network() self._ensure_network()
gw = self.host_gateway gw = self.host_gateway
if gw not in self.allowed_ips: if gw not in self.extra_ips:
self.allowed_ips.append(gw) self.extra_ips.append(gw)
self._ensure_proxy() self._ensure_proxy()
def stop(self) -> None: def stop(self) -> None:
@@ -280,7 +291,13 @@ class ProxyManager:
def _ensure_network(self) -> None: def _ensure_network(self) -> None:
try: try:
self._client.networks.get(self.network_name) net = self._client.networks.get(self.network_name)
if not net.attrs.get("Internal", False):
raise RuntimeError(
f"Network {self.network_name!r} exists but is not internal. "
"An external network cannot enforce egress isolation. "
"Remove it and let the proxy recreate it, or use a different name."
)
except NotFound: except NotFound:
# internal=True → no default route to the internet # internal=True → no default route to the internet
self._client.networks.create( self._client.networks.create(
@@ -307,17 +324,17 @@ class ProxyManager:
name=self.container_name, name=self.container_name,
) )
# Copy squid.conf and allowlist into the container # Build merged allowlist contents (file + extra entries)
self._put_file( domain_content = self._build_allowlist(
proxy, self.allowlist_path, self.extra_domains
"/etc/squid/squid.conf",
self._generate_squid_conf(),
)
self._put_file(
proxy,
ALLOWLIST_CONTAINER_PATH,
self.allowlist_path.read_text(),
) )
ip_content = self._build_allowlist(self.ip_allowlist_path, self.extra_ips)
# Copy config files into the container
self._put_file(proxy, "/etc/squid/squid.conf", self._generate_squid_conf())
self._put_file(proxy, DOMAIN_ALLOWLIST_CONTAINER_PATH, domain_content)
if ip_content:
self._put_file(proxy, IP_ALLOWLIST_CONTAINER_PATH, ip_content)
proxy.start() proxy.start()
@@ -357,20 +374,33 @@ class ProxyManager:
time.sleep(0.5) time.sleep(0.5)
raise TimeoutError(f"Squid not ready after {timeout}s") raise TimeoutError(f"Squid not ready after {timeout}s")
@staticmethod
def _build_allowlist(file_path: Path | None, extra_entries: list[str]) -> str:
"""Merge a file and extra entries into a single allowlist string."""
lines: list[str] = []
if file_path:
lines.append(file_path.read_text().rstrip("\n"))
if extra_entries:
lines.extend(extra_entries)
return "\n".join(lines) + "\n" if lines else ""
def _has_ips(self) -> bool:
return bool(self.ip_allowlist_path or self.extra_ips)
def _generate_squid_conf(self) -> str: def _generate_squid_conf(self) -> str:
ip_acls = "" ip_acl = ""
ip_rules = "" ip_rule = ""
if self.allowed_ips: if self._has_ips():
ip_acls = "\n".join(f"acl allowed_ips dst {ip}" for ip in self.allowed_ips) ip_acl = f'acl allowed_ips dst "{IP_ALLOWLIST_CONTAINER_PATH}"'
ip_rules = "http_access allow allowed_ips" ip_rule = "http_access allow allowed_ips"
# Allow any port for IP-based destinations # Allow any port for IP-based destinations
extra_ports = "1-65535" if self.allowed_ips else "" extra_ports = "1-65535" if self._has_ips() else ""
return SQUID_CONF_TEMPLATE.format( return SQUID_CONF_TEMPLATE.format(
allowlist_path=ALLOWLIST_CONTAINER_PATH, domain_allowlist_path=DOMAIN_ALLOWLIST_CONTAINER_PATH,
ip_acls=ip_acls, ip_acl=ip_acl,
ip_rules=ip_rules, ip_rule=ip_rule,
extra_ports=extra_ports, extra_ports=extra_ports,
port=self.proxy_port, port=self.proxy_port,
) )
@@ -387,23 +417,21 @@ def main():
) )
sub = parser.add_subparsers(dest="action", required=True) sub = parser.add_subparsers(dest="action", required=True)
p_start = sub.add_parser("start", help="Create network and start proxy") # Shared arguments for start/update
p_start.add_argument("--ip", action="append", help="Extra allowed IP/CIDR") for p in [
p_start.add_argument( sub.add_parser("start", help="Create network and start proxy"),
sub.add_parser(
"update", help="Restart proxy with current config (network preserved)"
),
]:
p.add_argument(
"--allowlist", "--allowlist",
type=Path, type=Path,
help="Path to a domain allowlist file in Squid dstdomain format (default: built-in list)", help="Domain allowlist file (default: built-in list)",
)
p_update = sub.add_parser(
"update", help="Restart proxy with current allowlist (network preserved)"
)
p_update.add_argument("--ip", action="append", help="Extra allowed IP/CIDR")
p_update.add_argument(
"--allowlist",
type=Path,
help="Path to a domain allowlist file in Squid dstdomain format (default: built-in list)",
) )
p.add_argument("--domain", action="append", help="Extra allowed domain")
p.add_argument("--ip-allowlist", type=Path, help="IP allowlist file")
p.add_argument("--ip", action="append", help="Extra allowed IP/CIDR")
sub.add_parser("stop", help="Stop proxy container") sub.add_parser("stop", help="Stop proxy container")
sub.add_parser("stop-all", help="Stop proxy and remove network") sub.add_parser("stop-all", help="Stop proxy and remove network")
@@ -412,15 +440,18 @@ def main():
args = parser.parse_args() args = parser.parse_args()
logging.basicConfig(format="%(asctime)s [%(name)s] %(message)s", level=logging.INFO) logging.basicConfig(format="%(asctime)s [%(name)s] %(message)s", level=logging.INFO)
allowlist_path: str | Path | None = None kwargs: dict = {}
ips: list[str] = []
if args.action in ("start", "update"): if args.action in ("start", "update"):
if getattr(args, "allowlist", None): if getattr(args, "allowlist", None):
allowlist_path = args.allowlist kwargs["allowlist_path"] = args.allowlist
if args.ip: if getattr(args, "domain", None):
ips.extend(args.ip) kwargs["extra_domains"] = args.domain
if getattr(args, "ip_allowlist", None):
kwargs["ip_allowlist_path"] = args.ip_allowlist
if getattr(args, "ip", None):
kwargs["extra_ips"] = args.ip
mgr = ProxyManager(allowlist_path=allowlist_path, allowed_ips=ips) mgr = ProxyManager(**kwargs)
match args.action: match args.action:
case "start": case "start":