4a1d949e41
完整 Web 管理平台,涵盖: P0 生产加固: - 会话超时(空闲+绝对) + CSRF 防护 + 登录失败限流 - HTTPS/TLS 证书管理(自签生成/上传/删除/过期提醒) - SSL Bump (HTTPS 透明代理) 可视化配置 - 配置文件 diff 预览 + 二次确认才写入 - 服务控制二次确认 + 操作期间按钮锁定 P1 运维能力: - 日志轮转管理(logrotate) + 日志状态监控 - 告警规则(5xx/命中率/磁盘/客户端流量) - 多 Squid 实例管理 + 一键切换 - squidclient mgr 实时性能指标 - 流量异常检测(突增/大文件/高频小包/新客户端) P2 日志分析增强: - 日志持久化到 SQLite + 历史时间范围查询 - 导出 CSV/JSON(日志/KPI/异常/审计) - GeoIP 地图(Leaflet + ip-api.com 离线可选 GeoLite2) - 每客户端 24h 趋势图 - 自定义 Squid logformat 解析器 P3 体验锦上添花: - 暗/亮主题切换(cookie 持久化) - WebSSH 终端(xterm.js + paramiko) - 完整审计日志 + 用户管理 技术栈: Flask 3.0 + SQLAlchemy 2.0 + SQLite + Chart.js + Leaflet 总代码量: ~16500 行 (15 Python 模块 + 34 模板 + CSS) 路由数: 73
707 lines
24 KiB
Python
707 lines
24 KiB
Python
"""Squid configuration (squid.conf) parser and generator.
|
|
|
|
Strategy:
|
|
- Parse the existing squid.conf into a list of directives with comments
|
|
- Expose structured getters for sections (network, cache, ACLs, rules, auth, ...)
|
|
- Allow web UI to update specific directives
|
|
- Generate a complete, well-formed squid.conf from the structured config
|
|
- Validate before applying (run `squid -k parse` if available)
|
|
|
|
Directive model:
|
|
- Each directive is a tuple (key, [args...], comment)
|
|
- Directives are grouped into named sections for the UI
|
|
|
|
Note: Squid supports many directives. We cover the most common ones used in
|
|
production deployments. A "raw editor" tab is always available for anything we
|
|
don't model explicitly.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
|
|
@dataclass
|
|
class Directive:
|
|
"""A single squid.conf directive line, possibly with a comment."""
|
|
key: str
|
|
args: list[str]
|
|
comment: str = ""
|
|
# raw original line (for round-trip preservation)
|
|
raw: str = ""
|
|
|
|
def render(self) -> str:
|
|
if self.raw and not self.args and not self.comment:
|
|
return self.raw
|
|
line = self.key
|
|
if self.args:
|
|
line += " " + " ".join(self.args)
|
|
if self.comment:
|
|
line += " #" + self.comment
|
|
return line
|
|
|
|
|
|
@dataclass
|
|
class Acl:
|
|
"""An ACL entry: acl <name> <type> <value>..."""
|
|
name: str
|
|
type: str
|
|
values: list[str]
|
|
comment: str = ""
|
|
|
|
def render(self) -> str:
|
|
parts = ["acl", self.name, self.type] + self.values
|
|
line = " ".join(parts)
|
|
if self.comment:
|
|
line += " #" + self.comment
|
|
return line
|
|
|
|
|
|
@dataclass
|
|
class Rule:
|
|
"""An access rule: http_access <allow|deny> [!]acl..."""
|
|
action: str # allow / deny
|
|
acls: list[str] # e.g. ["!localhost", "allowed_net"]
|
|
comment: str = ""
|
|
directive: str = "http_access" # http_access, https_access, icp_access, ...
|
|
|
|
def render(self) -> str:
|
|
parts = [self.directive, self.action] + self.acls
|
|
line = " ".join(parts)
|
|
if self.comment:
|
|
line += " #" + self.comment
|
|
return line
|
|
|
|
|
|
@dataclass
|
|
class CacheDir:
|
|
"""A cache_dir directive: cache_dir <type> <path> <size> <L1> <L2> [options]"""
|
|
type: str = "ufs"
|
|
path: str = "/var/spool/squid"
|
|
size_mb: int = 100
|
|
l1: int = 16
|
|
l2: int = 256
|
|
options: list[str] = field(default_factory=list)
|
|
|
|
def render(self) -> str:
|
|
parts = ["cache_dir", self.type, self.path, str(self.size_mb), str(self.l1), str(self.l2)]
|
|
parts.extend(self.options)
|
|
return " ".join(parts)
|
|
|
|
|
|
@dataclass
|
|
class RefreshPattern:
|
|
"""refresh_pattern [-i] regex min percent max [{options}]"""
|
|
case_insensitive: bool = False
|
|
regex: str = "."
|
|
min_minutes: int = 0
|
|
percent: int = 20
|
|
max_minutes: int = 4320
|
|
options: list[str] = field(default_factory=list)
|
|
|
|
def render(self) -> str:
|
|
parts = ["refresh_pattern"]
|
|
if self.case_insensitive:
|
|
parts.append("-i")
|
|
parts.extend([self.regex, str(self.min_minutes), f"{self.percent}%", str(self.max_minutes)])
|
|
if self.options:
|
|
parts.append("{" + " ".join(self.options) + "}")
|
|
return " ".join(parts)
|
|
|
|
|
|
@dataclass
|
|
class AuthParam:
|
|
"""auth_param <scheme> <param> <value>..."""
|
|
scheme: str = "basic"
|
|
program: str = "/usr/lib/squid/basic_ncsa_auth /etc/squid/passwd"
|
|
children: int = 5
|
|
realm: str = "Squid Proxy"
|
|
credentialsttl: int = 2
|
|
# also stored as raw param dict for other schemes
|
|
extra: dict = field(default_factory=dict)
|
|
|
|
def render_all(self) -> list[str]:
|
|
lines = []
|
|
lines.append(f"auth_param {self.scheme} program {self.program}")
|
|
lines.append(f"auth_param {self.scheme} children {self.children}")
|
|
lines.append(f"auth_param {self.scheme} realm {self.realm}")
|
|
lines.append(f"auth_param {self.scheme} credentialsttl {self.credentialsttl} hours")
|
|
for k, v in self.extra.items():
|
|
lines.append(f"auth_param {self.scheme} {k} {v}")
|
|
return lines
|
|
|
|
|
|
@dataclass
|
|
class CachePeer:
|
|
"""cache_peer <host> <type> <http_port> <icp_port> [options]"""
|
|
host: str = ""
|
|
type: str = "parent"
|
|
http_port: int = 3128
|
|
icp_port: int = 0
|
|
options: list[str] = field(default_factory=list)
|
|
|
|
def render(self) -> str:
|
|
parts = ["cache_peer", self.host, self.type, str(self.http_port), str(self.icp_port)]
|
|
parts.extend(self.options)
|
|
return " ".join(parts)
|
|
|
|
|
|
@dataclass
|
|
class SslBumpRule:
|
|
"""ssl_bump directive: ssl_bump <action> [!]acl ...
|
|
action: peek, splice, bump, terminate, client-first, server-first, none
|
|
"""
|
|
action: str = "peek"
|
|
acls: list[str] = field(default_factory=list) # ACL names + optional ! negation
|
|
step: int = 1 # rule order
|
|
comment: str = ""
|
|
|
|
def render(self) -> str:
|
|
parts = ["ssl_bump", self.action] + self.acls
|
|
line = " ".join(parts)
|
|
if self.comment:
|
|
line += " #" + self.comment
|
|
return line
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Parsing
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def parse_conf(text: str):
|
|
"""Parse a squid.conf string into structured groups.
|
|
|
|
Returns dict with keys: directives (raw), acls, rules, cache_dirs,
|
|
refresh_patterns, auth_params, cache_peers, ssl_bump_rules.
|
|
"""
|
|
acls = []
|
|
rules = []
|
|
cache_dirs = []
|
|
refresh_patterns = []
|
|
cache_peers = []
|
|
auth_lines = [] # raw auth_param lines, parsed into AuthParam later
|
|
ssl_bump_rules = [] # ssl_bump directives
|
|
|
|
# map of directive -> list of (args, comment) for simple single-value directives
|
|
simple: dict[str, list[tuple[list[str], str]]] = {}
|
|
|
|
for raw in text.splitlines():
|
|
line = raw.strip()
|
|
if not line:
|
|
continue
|
|
if line.startswith("#"):
|
|
continue
|
|
|
|
# split off trailing comment
|
|
comment = ""
|
|
if " #" in line:
|
|
line, _, comment = line.partition(" #")
|
|
line = line.rstrip()
|
|
comment = comment.strip()
|
|
|
|
tokens = line.split()
|
|
if not tokens:
|
|
continue
|
|
key = tokens[0]
|
|
args = tokens[1:]
|
|
|
|
if key == "acl":
|
|
if len(args) >= 3:
|
|
acls.append(Acl(
|
|
name=args[0], type=args[1], values=args[2:],
|
|
comment=comment,
|
|
))
|
|
continue
|
|
|
|
if key in ("http_access", "http_access2", "https_access",
|
|
"icp_access", "htcp_access", "miss_access",
|
|
"snmp_access", "ident_lookup_access",
|
|
"always_direct", "never_direct",
|
|
"reply_body_max_size", "reply_access"):
|
|
if args:
|
|
rules.append(Rule(
|
|
action=args[0], acls=args[1:], comment=comment,
|
|
directive=key,
|
|
))
|
|
continue
|
|
|
|
if key == "cache_dir":
|
|
if len(args) >= 5:
|
|
cache_dirs.append(CacheDir(
|
|
type=args[0], path=args[1],
|
|
size_mb=int(args[2]) if args[2].isdigit() else 100,
|
|
l1=int(args[3]) if args[3].isdigit() else 16,
|
|
l2=int(args[4]) if args[4].isdigit() else 256,
|
|
options=args[5:],
|
|
))
|
|
continue
|
|
|
|
if key == "refresh_pattern":
|
|
rp = RefreshPattern()
|
|
idx = 0
|
|
if idx < len(args) and args[idx] == "-i":
|
|
rp.case_insensitive = True
|
|
idx += 1
|
|
if idx < len(args):
|
|
rp.regex = args[idx]; idx += 1
|
|
if idx < len(args) and args[idx].lstrip("-").isdigit():
|
|
rp.min_minutes = int(args[idx]); idx += 1
|
|
if idx < len(args) and args[idx].endswith("%"):
|
|
try:
|
|
rp.percent = int(args[idx].rstrip("%"))
|
|
except ValueError:
|
|
pass
|
|
idx += 1
|
|
if idx < len(args) and args[idx].lstrip("-").isdigit():
|
|
rp.max_minutes = int(args[idx]); idx += 1
|
|
rp.options = args[idx:]
|
|
refresh_patterns.append(rp)
|
|
continue
|
|
|
|
if key == "cache_peer":
|
|
if len(args) >= 4:
|
|
cache_peers.append(CachePeer(
|
|
host=args[0], type=args[1],
|
|
http_port=int(args[2]) if args[2].isdigit() else 3128,
|
|
icp_port=int(args[3]) if args[3].isdigit() else 0,
|
|
options=args[4:],
|
|
))
|
|
continue
|
|
|
|
if key == "ssl_bump":
|
|
# ssl_bump <action> [!]acl...
|
|
if args:
|
|
action = args[0]
|
|
acls_for_rule = args[1:]
|
|
step = len(ssl_bump_rules) + 1
|
|
ssl_bump_rules.append(SslBumpRule(
|
|
action=action,
|
|
acls=acls_for_rule,
|
|
step=step,
|
|
comment=comment,
|
|
))
|
|
continue
|
|
|
|
if key == "auth_param":
|
|
auth_lines.append((args, comment))
|
|
continue
|
|
|
|
simple.setdefault(key, []).append((args, comment))
|
|
|
|
# parse auth_params into structured form (one per scheme)
|
|
auth_params = parse_auth_lines(auth_lines)
|
|
|
|
return {
|
|
"acls": acls,
|
|
"rules": rules,
|
|
"cache_dirs": cache_dirs,
|
|
"refresh_patterns": refresh_patterns,
|
|
"cache_peers": cache_peers,
|
|
"auth_params": auth_params,
|
|
"ssl_bump_rules": ssl_bump_rules,
|
|
"simple": simple, # raw simple directives, list of (args, comment)
|
|
}
|
|
|
|
|
|
def parse_auth_lines(lines: list[tuple[list[str], str]]) -> list[AuthParam]:
|
|
"""Group auth_param lines by scheme."""
|
|
by_scheme: dict[str, AuthParam] = {}
|
|
for args, comment in lines:
|
|
if len(args) < 2:
|
|
continue
|
|
scheme = args[0]
|
|
param = args[1]
|
|
value = " ".join(args[2:])
|
|
if scheme not in by_scheme:
|
|
ap = AuthParam(scheme=scheme)
|
|
by_scheme[scheme] = ap
|
|
ap = by_scheme[scheme]
|
|
if param == "program":
|
|
ap.program = value
|
|
elif param == "children":
|
|
try:
|
|
ap.children = int(args[2])
|
|
except (ValueError, IndexError):
|
|
pass
|
|
elif param == "realm":
|
|
ap.realm = value
|
|
elif param == "credentialsttl":
|
|
try:
|
|
ap.credentialsttl = int(args[2])
|
|
except (ValueError, IndexError):
|
|
pass
|
|
else:
|
|
ap.extra[param] = value
|
|
return list(by_scheme.values())
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Generation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# default simple directives the UI manages explicitly.
|
|
DEFAULT_SIMPLE = {
|
|
# network
|
|
"http_port": [["3128"], ""],
|
|
"https_port": [],
|
|
"icp_port": [["3130"], ""],
|
|
"snmp_port": [],
|
|
"visible_hostname": [["localhost"], ""],
|
|
"unique_hostname": [],
|
|
"hostname_aliases": [],
|
|
# cache
|
|
"cache_mem": [["256", "MB"], ""],
|
|
"maximum_object_size": [["4096", "KB"], ""],
|
|
"minimum_object_size": [["0", "KB"], ""],
|
|
"maximum_object_size_in_memory": [["512", "KB"], ""],
|
|
"cache_replacement_policy": [["lru"], ""],
|
|
"memory_replacement_policy": [["lru"], ""],
|
|
"cache_swap_low": [["90"], ""],
|
|
"cache_swap_high": [["95"], ""],
|
|
# logging
|
|
"access_log": [["/var/log/squid/access.log"], ""],
|
|
"cache_log": [["/var/log/squid/cache.log"], ""],
|
|
"cache_store_log": [["/var/log/squid/store.log"], ""],
|
|
"pid_filename": [["/var/run/squid.pid"], ""],
|
|
"logformat": [],
|
|
"debug_options": [],
|
|
"log_ip_on_direct": [["on"], ""],
|
|
"buffered_logs": [["on"], ""],
|
|
# dns
|
|
"dns_nameservers": [],
|
|
"dns_timeout": [["30", "seconds"], ""],
|
|
"hosts_file": [["/etc/hosts"], ""],
|
|
"dns_defnames": [["off"], ""],
|
|
# SSL / TLS (for ssl-bump)
|
|
"sslcrtd_program": [["security_file_certgen", "-s", "/var/lib/squid/ssl_db", "-M", "4", "MB"], ""],
|
|
"sslcrtd_children": [["5", "startup=1", "idle=1"], ""],
|
|
"sslproxy_ca_certfile": [],
|
|
"sslproxy_ca_keyfile": [],
|
|
"tls_outgoing_version": [],
|
|
# timeouts
|
|
"connect_timeout": [["60", "seconds"], ""],
|
|
"read_timeout": [["15", "minutes"], ""],
|
|
"client_lifetime": [["1", "day"], ""],
|
|
"shutdown_lifetime": [["30", "seconds"], ""],
|
|
# admin
|
|
"cache_mgr": [["root"], ""],
|
|
"err_html_language": [["en"], ""],
|
|
"ftp_user": [],
|
|
}
|
|
|
|
|
|
def generate_conf(struct: dict, raw_extra: str = "") -> str:
|
|
"""Generate a complete squid.conf string from the structured config."""
|
|
lines: list[str] = []
|
|
|
|
lines.append("# ============================================================")
|
|
lines.append("# squid.conf - managed by Squid Web Manager")
|
|
lines.append("# WARNING: this file is regenerated by the web UI.")
|
|
lines.append("# Manual edits may be lost - use the Raw Editor tab")
|
|
lines.append("# if you need to add directives not covered by the UI.")
|
|
lines.append("# ============================================================")
|
|
lines.append("")
|
|
|
|
# --- Network ---
|
|
lines.append("# ---- Network ----")
|
|
for key in ("http_port", "https_port", "icp_port", "snmp_port",
|
|
"visible_hostname", "unique_hostname", "hostname_aliases"):
|
|
for args, comment in struct["simple"].get(key, []):
|
|
lines.append(_render_simple(key, args, comment))
|
|
lines.append("")
|
|
|
|
# --- Cache ---
|
|
lines.append("# ---- Cache ----")
|
|
for key in ("cache_mem", "maximum_object_size", "minimum_object_size",
|
|
"maximum_object_size_in_memory",
|
|
"cache_replacement_policy", "memory_replacement_policy",
|
|
"cache_swap_low", "cache_swap_high"):
|
|
for args, comment in struct["simple"].get(key, []):
|
|
lines.append(_render_simple(key, args, comment))
|
|
for cd in struct["cache_dirs"]:
|
|
lines.append(cd.render())
|
|
lines.append("")
|
|
|
|
# --- Auth ---
|
|
if struct["auth_params"]:
|
|
lines.append("# ---- Authentication ----")
|
|
for ap in struct["auth_params"]:
|
|
for ln in ap.render_all():
|
|
lines.append(ln)
|
|
lines.append("")
|
|
|
|
# --- ACLs ---
|
|
lines.append("# ---- ACLs ----")
|
|
for acl in struct["acls"]:
|
|
lines.append(acl.render())
|
|
lines.append("")
|
|
|
|
# --- Refresh patterns ---
|
|
if struct["refresh_patterns"]:
|
|
lines.append("# ---- Refresh patterns ----")
|
|
for rp in struct["refresh_patterns"]:
|
|
lines.append(rp.render())
|
|
lines.append("")
|
|
|
|
# --- Cache peers ---
|
|
if struct["cache_peers"]:
|
|
lines.append("# ---- Cache peers ----")
|
|
for cp in struct["cache_peers"]:
|
|
lines.append(cp.render())
|
|
lines.append("")
|
|
|
|
# --- Access rules ---
|
|
lines.append("# ---- Access rules ----")
|
|
for r in struct["rules"]:
|
|
lines.append(r.render())
|
|
lines.append("")
|
|
|
|
# --- Logging ---
|
|
lines.append("# ---- Logging ----")
|
|
for key in ("access_log", "cache_log", "cache_store_log",
|
|
"pid_filename", "logformat", "debug_options",
|
|
"log_ip_on_direct", "buffered_logs"):
|
|
for args, comment in struct["simple"].get(key, []):
|
|
lines.append(_render_simple(key, args, comment))
|
|
lines.append("")
|
|
|
|
# --- DNS ---
|
|
lines.append("# ---- DNS ----")
|
|
for key in ("dns_nameservers", "dns_timeout", "hosts_file", "dns_defnames"):
|
|
for args, comment in struct["simple"].get(key, []):
|
|
lines.append(_render_simple(key, args, comment))
|
|
lines.append("")
|
|
|
|
# --- SSL/TLS ---
|
|
ssl_keys = ("sslcrtd_program", "sslcrtd_children", "sslproxy_ca_certfile",
|
|
"sslproxy_ca_keyfile", "tls_outgoing_version")
|
|
if any(struct["simple"].get(k) for k in ssl_keys) or struct.get("ssl_bump_rules"):
|
|
lines.append("# ---- SSL/TLS ----")
|
|
for key in ssl_keys:
|
|
for args, comment in struct["simple"].get(key, []):
|
|
lines.append(_render_simple(key, args, comment))
|
|
for rule in struct.get("ssl_bump_rules", []):
|
|
lines.append(rule.render())
|
|
lines.append("")
|
|
|
|
# --- Timeouts ---
|
|
lines.append("# ---- Timeouts ----")
|
|
for key in ("connect_timeout", "read_timeout", "client_lifetime",
|
|
"shutdown_lifetime"):
|
|
for args, comment in struct["simple"].get(key, []):
|
|
lines.append(_render_simple(key, args, comment))
|
|
lines.append("")
|
|
|
|
# --- Admin ---
|
|
lines.append("# ---- Admin ----")
|
|
for key in ("cache_mgr", "err_html_language", "ftp_user"):
|
|
for args, comment in struct["simple"].get(key, []):
|
|
lines.append(_render_simple(key, args, comment))
|
|
lines.append("")
|
|
|
|
# --- raw extra directives ---
|
|
if raw_extra.strip():
|
|
lines.append("# ---- Raw directives (manual) ----")
|
|
for ln in raw_extra.splitlines():
|
|
if ln.strip():
|
|
lines.append(ln.strip())
|
|
lines.append("")
|
|
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def _render_simple(key: str, args: list[str], comment: str = "") -> str:
|
|
line = key + (" " + " ".join(args) if args else "")
|
|
if comment:
|
|
line += " #" + comment
|
|
return line
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Validation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def validate_conf(text: str, squid_binary: str = "squid") -> tuple[bool, str]:
|
|
"""Run `squid -k parse` on the given config text.
|
|
|
|
Returns (ok, message). Falls back to syntax-only check if squid isn't
|
|
installed.
|
|
"""
|
|
if not squid_binary or not _which(squid_binary):
|
|
# no squid binary available - just check that we can parse it
|
|
try:
|
|
parse_conf(text)
|
|
return True, "Squid binary not found - parsed structure successfully " \
|
|
"(skipped runtime syntax check)."
|
|
except Exception as e:
|
|
return False, f"Parse error: {e}"
|
|
|
|
tmp_path = "/tmp/squid_conf_check.conf"
|
|
try:
|
|
with open(tmp_path, "w") as f:
|
|
f.write(text)
|
|
except OSError as e:
|
|
return False, f"Cannot write temp file: {e}"
|
|
|
|
try:
|
|
r = subprocess.run(
|
|
[squid_binary, "-k", "parse", "-f", tmp_path],
|
|
capture_output=True, text=True, timeout=10,
|
|
)
|
|
except subprocess.TimeoutExpired:
|
|
return False, "squid -k parse timed out"
|
|
except FileNotFoundError:
|
|
return False, "squid binary not found"
|
|
# squid -k parse exits 0 on success. Errors are written to stdout.
|
|
out = (r.stdout or "").strip()
|
|
err = (r.stderr or "").strip()
|
|
msg = out + ("\n" + err if err else "")
|
|
# squid typically prints "WARNING:" lines for non-fatal issues.
|
|
if r.returncode == 0:
|
|
return True, msg or "Configuration OK"
|
|
return False, msg or f"squid -k parse failed (exit {r.returncode})"
|
|
|
|
|
|
def _which(cmd: str) -> Optional[str]:
|
|
"""Lightweight which() that doesn't depend on shutil.which."""
|
|
for path in os.environ.get("PATH", "").split(":"):
|
|
if not path:
|
|
continue
|
|
candidate = os.path.join(path, cmd)
|
|
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
|
return candidate
|
|
return None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Defaults (used to bootstrap a brand new install)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def default_struct() -> dict:
|
|
"""Return a default structured config for a fresh install."""
|
|
simple = {k: [v] if isinstance(v, list) and v and isinstance(v[0], list)
|
|
else (v if isinstance(v, list) else [])
|
|
for k, v in DEFAULT_SIMPLE.items()}
|
|
# DEFAULT_SIMPLE values are already lists of [args, comment]
|
|
# but they are stored as [args_list, comment] tuples - normalize.
|
|
simple = {}
|
|
for k, v in DEFAULT_SIMPLE.items():
|
|
# v is [[args...], comment] or [] (empty)
|
|
if isinstance(v, list) and len(v) == 2 and isinstance(v[0], list):
|
|
simple[k] = [(v[0], v[1])]
|
|
else:
|
|
simple[k] = []
|
|
# Provide a sensible default sslcrtd_program bootstrap so freshly-deployed
|
|
# SSL Bump setups have a working cert generator out of the box.
|
|
# (Empty list would render "sslcrtd_program" with no args - leave it.)
|
|
# Default is intentionally commented out in DEFAULT_SIMPLE above
|
|
# (sslcrtd_program: []). Users override via the SSL Bump UI.
|
|
return {
|
|
"acls": [
|
|
Acl(name="localhost", type="src", values=["127.0.0.1/32"]),
|
|
Acl(name="to_localhost", type="dst", values=["127.0.0.0/8"]),
|
|
Acl(name="safeports", type="port", values=["80", "443", "21", "70", "80-1024"]),
|
|
Acl(name="sslports", type="port", values=["443", "563"]),
|
|
Acl(name="CONNECT", type="method", values=["CONNECT"]),
|
|
Acl(name="all", type="src", values=["0.0.0.0/0"]),
|
|
Acl(name="manager", type="proto", values=["cache_object"]),
|
|
],
|
|
"rules": [
|
|
Rule(action="deny", acls=["manager"], directive="http_access"),
|
|
Rule(action="deny", acls=["!Safe_ports"], directive="http_access"),
|
|
Rule(action="deny", acls=["!SSL_ports"], directive="http_access"),
|
|
Rule(action="allow", acls=["localhost"], directive="http_access"),
|
|
Rule(action="deny", acls=["all"], directive="http_access"),
|
|
],
|
|
"cache_dirs": [
|
|
CacheDir(type="ufs", path="/var/spool/squid",
|
|
size_mb=100, l1=16, l2=256)
|
|
],
|
|
"refresh_patterns": [
|
|
RefreshPattern(regex="^ftp:", min_minutes=1440, percent=20, max_minutes=10080),
|
|
RefreshPattern(regex="^gopher:", min_minutes=1440, percent=0, max_minutes=1440),
|
|
RefreshPattern(case_insensitive=True, regex=" cgi-bin",
|
|
min_minutes=0, percent=0, max_minutes=0),
|
|
RefreshPattern(regex=".", min_minutes=0, percent=20, max_minutes=4320),
|
|
],
|
|
"cache_peers": [],
|
|
"auth_params": [],
|
|
"ssl_bump_rules": [],
|
|
"simple": simple,
|
|
}
|
|
|
|
|
|
ACL_TYPES = [
|
|
("src", "Source IP/CIDR (src 192.168.1.0/24)"),
|
|
("dst", "Destination IP/CIDR"),
|
|
("dstdomain", "Destination domain (matches *.example.com)"),
|
|
("dstdom_regex", "Destination domain regex"),
|
|
("srcdomain", "Source domain"),
|
|
("srcdom_regex", "Source domain regex"),
|
|
("port", "Destination port"),
|
|
("method", "HTTP method (GET, POST, CONNECT, ...)"),
|
|
("proto", "Protocol (http, https, ftp, ...)"),
|
|
("time", "Time of day/week (SMTWHFA/HH:MM-HH:MM)"),
|
|
("url_regex", "URL regex"),
|
|
("urlpath_regex", "URL path regex"),
|
|
("url_login", "URL login part regex"),
|
|
("browser", "User-Agent regex"),
|
|
("referer_regex", "Referer regex"),
|
|
("ident", "User ident string"),
|
|
("ident_regex", "User ident regex"),
|
|
("proxy_auth", "Authenticated user / REQUIRED"),
|
|
("proxy_auth_regex", "Authenticated user regex"),
|
|
("maxconn", "Max connections per client"),
|
|
("max_user_ip", "Max IPs per user"),
|
|
("rep_mime_type", "Reply MIME type regex"),
|
|
("req_header", "Request header"),
|
|
("rep_header", "Reply header"),
|
|
("localip", "Local IP"),
|
|
("localnet", "Local network"),
|
|
("peername", "Peer name"),
|
|
("hierarchy_type", "Hierarchy type"),
|
|
]
|
|
|
|
RULE_DIRECTIVES = [
|
|
"http_access",
|
|
"http_access2",
|
|
"https_access",
|
|
"icp_access",
|
|
"htcp_access",
|
|
"miss_access",
|
|
"snmp_access",
|
|
"ident_lookup_access",
|
|
"reply_access",
|
|
"adaptation_access",
|
|
]
|
|
|
|
|
|
# Valid ssl_bump actions (Squid docs - https://www.squid-cache.org/Doc/config/ssl_bump/)
|
|
SSL_BUMP_ACTIONS = [
|
|
"peek",
|
|
"splice",
|
|
"bump",
|
|
"terminate",
|
|
"client-first",
|
|
"server-first",
|
|
"none",
|
|
]
|
|
|
|
|
|
SSL_BUMP_DEFAULT_EXAMPLE = (
|
|
"# Suggested starter set (rule order matters - top to bottom):\n"
|
|
"# ssl_bump peek step1 # peek at TLS client hello (step 1)\n"
|
|
"# ssl_bump splice all # otherwise pass-through tunnel\n"
|
|
"#\n"
|
|
"# Required prereqs before SSL Bump will work:\n"
|
|
"# 1) sslcrtd_program/children pointing at security_file_certgen\n"
|
|
"# and an initialised ssl_db directory\n"
|
|
"# 2) sslproxy_ca_certfile / sslproxy_ca_keyfile pointing at a CA\n"
|
|
"# that your client browsers trust (or you distribute the cert\n"
|
|
"# and instruct users to install it)\n"
|
|
"# 3) https_port configured with ssl-bump option"
|
|
)
|