Skip to content

Security

元气码农少女酱 edited this page Aug 20, 2026 · 8 revisions

安全

IPClick 的服务端会代替调用方请求任意 URL。这句话决定了它的威胁模型: 一台配置不当的 IPClick 就是一个开放的内网跳板。

四条防线,各管一件事,不能互相替代

防线 回答的问题
SSRF 防护 能打到哪儿
鉴权 谁能用
TLS 链路上能不能被看见/改掉
机密管理 凭据会不会漏出去

1. SSRF 防护

[SECURITY]
allowed_schemes = ["http", "https"]
block_metadata_endpoints = true    # 默认开
block_private_networks = false     # 默认关
allowlist = []

block_metadata_endpoints(默认开)

拦截云厂商实例元数据地址(169.254.169.254 等)。这是 SSRF 最典型的目标—— 一次请求就能拿到实例的 IAM 凭据。没有正当的代理用途,建议保持开启。

block_private_networks(默认关)

拦截回环 / 私网 / 保留地址。

默认关是为了兼容"本机代理本机服务"的用法(开发、内网工具链)。 部署到不可信网络时必须改成 true

[SECURITY]
block_private_networks = true
allowlist = ["10.0.0.5", "internal-api.corp"]   # 确实要放行的

不开的话,任何能调你服务的人都能让它去请求 http://192.168.1.1/http://localhost:6379/(Redis 协议在 HTTP 之上也能被利用)等等。

allowed_schemes

只允许 http / httpsfile://gopher://dict:// 这些是 SSRF 的经典放大器。 不在白名单里的协议会得到 ValidationError在发出去之前就被拦掉。

automation_script 会绕过这一整套

页面里的 JS 自己发的请求,走的是浏览器的网络栈,[SECURITY] 的 URL 策略对它 完全不起作用

[BROWSER]
allow_scripts = false    # 默认关,保持这样

打开它等于把 SSRF 防线让开一整条。只有在确认调用方全部可信时才打开。

2. 鉴权

# .env
IPCLICK_AUTH_TOKEN=<一串够长的随机>
Downloader(host="10.0.0.1", token="<同一串>")

留空 = 不鉴权,服务端启动时会打显著告警:

未配置鉴权令牌,任何能连到本端口的调用方都可以使用本服务。

监听在非回环地址而不鉴权,等于把这台机器的出口 IP 和内网可达性送给任何人。

令牌轮换

环境变量只接受单个令牌。轮换期间要新旧并存的话,只能写在配置里:

[SECURITY]
auth_token = ["新令牌", "旧令牌"]
allow_secrets_in_config = true    # 否则每次启动都会被点名

轮换完记得删掉旧的、改回环境变量。

集群鉴权

集群内部用共享密钥派生每台的令牌,见集群

token = HMAC-SHA256(secret, "ipclick-node:" + node_id)

每台令牌不同(拿到 B 的不能调 C),但只需要分发一个密钥。

健康检查是免鉴权的

grpc.health.v1 不需要令牌——K8s 探针和服务网格没法带凭据。它只返回 SERVING / NOT_SERVING,不泄露任何信息。不想暴露就 [MONITOR].health_check = false

3. 传输层加密

[SECURITY.tls]
enabled = false
cert_file = ""
key_file = ""
ca_file = ""
require_client_cert = false
server_name_override = ""

默认关闭(明文)以兼容旧部署。但要清楚:令牌鉴权保护的是"谁能用", 明文链路上令牌本身会被同网段原样嗅探到。 公网或不可信网络务必打开。

服务端

[SECURITY.tls]
enabled = true
cert_file = "/etc/ipclick/server.crt"
key_file = "/etc/ipclick/server.key"

mTLS(双向验证)

[SECURITY.tls]
enabled = true
cert_file = "/etc/ipclick/server.crt"
key_file = "/etc/ipclick/server.key"
ca_file = "/etc/ipclick/ca.crt"        # 验证客户端证书用
require_client_cert = true

⚠️ require_client_cert = true 却不配 ca_file启动时直接报错。 那种组合下任何自签名证书都能通过,等于没验证——所以不会静默降级。

客户端

[SECURITY.tls]
enabled = true
ca_file = ""                    # 留空用系统信任库(对接公网 CA 时就该留空)
cert_file = ""                  # 只有服务端要 mTLS 时才填
key_file = ""
server_name_override = ""       # 证书名字与连接地址不一致时用

server_name_override跳过主机名匹配(比如证书签给 ipclick.internal 而你连 10.0.0.5)。只应在自签名证书的内网环境里用。

4. 机密管理

规则:.env 只放机密,ipclick.toml 只放行为。

# .env —— 600 权限,绝不进版本库
IPCLICK_AUTH_TOKEN=...
IPCLICK_WEB_USER=admin
IPCLICK_WEB_PASSWORD=...
IPCLICK_PROXY_AUTH_KEY=...
IPCLICK_PROXY_AUTH_PASSWORD=...
IPCLICK_CLUSTER_SECRET=...

ipclick init 生成的 .env 是 600 权限,并自动追加到 .gitignore

机密写进 ipclick.toml 仍然生效——受信环境里图省事是合理的。只是启动时会点名提醒: ipclick.toml 通常要进版本库,机密会跟着进 git、进备份、进 CI 日志。 设 allow_secrets_in_config = true 可以关掉提醒。

两边都写时环境变量优先

查每个机密来自哪里:

ipclick config-info
  机密来源:
    gRPC 鉴权令牌      环境变量 / .env
    Web 管理端密码     配置文件 ⚠️ 建议改用环境变量
    集群共享密钥       未配置

Web 管理端

Web 管理端 · 安全设计。要点:

  • 默认只监听 127.0.0.1
  • 无默认口令,不设就随机生成
  • CSRF、HttpOnly + SameSite=Strict、登录限速
  • CSP default-src 'none'script-src 用内联脚本的 sha256 哈希, 不是 'unsafe-inline'——注入进来的 <script> 哈希对不上 就执行不了。connect-src 'self' 把 fetch 限死在本源
  • 从不显示任何机密。「生成凭据」也不破这条:生成的值只在那一次响应里出现, 服务端不保存、不写进任何文件
  • 安全边界不可从网页修改 —— [SECURITY] 全部、Web 自己的凭据、集群密钥、 allow_scripts。攻破 Web 登录不该等于能关掉 SSRF 防护

唯一放开的一项:能装 / 卸可选组件。 "装依赖要在机器上执行命令"值得停一下, 放开它的理由是:这个界面已经能改配置、能代发任意请求,再加上"装它自己声明过的 那五个可选依赖"没有实质性地扩大攻击面。约束仍然在:

  • 包名只能来自白名单常量,表单传来的值必须精确匹配,绝不拼接;
  • 命令以列表形式交给 subprocessshell=False),做不到"执行任意命令";
  • 绑定当前解释器,不依赖 PATH。

换句话说,它能装的东西是闭集,不是"任意 pip 包",更不是任意命令。

它本身是明文 HTTP,远程访问请走 SSH 隧道或 TLS 终止的反向代理。

生产环境清单

[SERVER]
host = "10.0.0.5"                  # 不要 0.0.0.0,除非确实需要

[SECURITY]
block_private_networks = true      # ← 最容易漏的一项
block_metadata_endpoints = true
allowlist = []                     # 只列确实需要的

[SECURITY.tls]
enabled = true
cert_file = "..."
key_file = "..."

[BROWSER]
allow_scripts = false              # 保持关闭
no_sandbox = false                 # 容器里不得不开时,理解代价

[WEB]
host = "127.0.0.1"                 # 走 SSH 隧道访问

[CLUSTER.status_page]
host = "127.0.0.1"
# .env —— 600 权限,不进版本库
IPCLICK_AUTH_TOKEN=<32+ 字节随机>
IPCLICK_WEB_PASSWORD=<32+ 字节随机>
IPCLICK_CLUSTER_SECRET=<32+ 字节随机>

生成随机串:

python -c "import secrets; print(secrets.token_urlsafe(32))"

最后跑一遍 ipclick config-info 逐项核对——它显示的是什么真生效了, 不是配置文件里写了什么。

报告安全问题

发现漏洞请不要开公开 issue,改用 GitHub 私密安全通报

下一步

Clone this wiki locally