02 API 鉴权与腾讯云公网桥接:把局域网模型服务安全地挂到域名上

文章目录

  • [02 API 鉴权与腾讯云公网桥接:把局域网模型服务安全地挂到域名上](#02 API 鉴权与腾讯云公网桥接:把局域网模型服务安全地挂到域名上)
    • [1. 目标架构](#1. 目标架构)
    • [2. API Key 鉴权(llama-server 侧)](#2. API Key 鉴权(llama-server 侧))
    • [3. 反向隧道(autossh + 最小权限密钥)](#3. 反向隧道(autossh + 最小权限密钥))
    • [4. nginx 反向代理(与既有站点共存)](#4. nginx 反向代理(与既有站点共存))
    • [5. 暴露面收敛(重要)](#5. 暴露面收敛(重要))
    • [6. 验收清单](#6. 验收清单)

02 API 鉴权与腾讯云公网桥接:把局域网模型服务安全地挂到域名上

《DeepSeek-V4-Flash 本地部署与公网服务化》系列 · 第 ② 篇 / 共 5 篇

① 硬件选型与 llama.cpp 部署 | ② API 鉴权与腾讯云公网桥接(本篇) | ③ 多协议支持:Anthropic 与 Responses API | ④ 稳定性与性能调优实录 | ⑤ 客户端接入:pi 与各类 SDK
本篇解决:llama-server 起在局域网工作站上(192.168.x.x:8081),如何让外网通过 https://api.example.com/v1(或 http)安全地访问,且不被白嫖算力。

1. 目标架构

复制代码
客户端(本机/任意网络)
  → https://api.example.com(腾讯云 nginx, 443/80)
  → 127.0.0.1:8081(跳板机本地回环)
  → SSH 反向隧道(local-server 主动发起)
  → local-server:8081 llama-server(API Key 鉴权)

模型机在 NAT 后,跳板机无法直连,所以用反向隧道:local-server 主动 ssh 到腾讯云,把远端的 8081 端口映射回自己的 8081。整条链路只有腾讯云安全组放行的 80/443 暴露公网。

2. API Key 鉴权(llama-server 侧)

llama-server 原生支持 --api-key-file(比 --api-key 好------key 不会出现在 ps 进程列表里):

bash 复制代码
umask 077
openssl rand -hex 32 > ~/deepseek-v4-flash/.api-key   # 64 位十六进制
chmod 600 ~/deepseek-v4-flash/.api-key

serve.sh 里做成自动检测(删文件并重启即回到无鉴权):

bash 复制代码
KEY_ARGS=()
if [ -f "$HOME/deepseek-v4-flash/.api-key" ]; then
  KEY_ARGS=(--api-key-file "$HOME/deepseek-v4-flash/.api-key")
fi
exec ... "$LLAMA" "${KEY_ARGS[@]}" ...

实测鉴权行为(这个行为边界要清楚):

请求 无 key 带 key
POST /v1/chat/completions 401 200
GET /props 401 200
GET /health 200 200(公开,设计如此)
GET /v1/models 200 200(公开,只泄露模型名)

3. 反向隧道(autossh + 最小权限密钥)

local-server 上生成专用密钥(空口令,仅供隧道):

bash 复制代码
ssh-keygen -t ed25519 -f ~/.ssh/jump_tunnel_ed25519 -N "" -C ds-tunnel-to-jump

跳板机 ~/.ssh/authorized_keys 里给这把公钥上镣铐------即使密钥泄露也只能用于这两条端口转发,拿不到 shell、改不了别的端口:

复制代码
restrict,port-forwarding,permitlisten="127.0.0.1:8081",permitlisten="127.0.0.1:8090" ssh-ed25519 AAAA... ds-tunnel-to-jump

(8090 是 03 篇的 LiteLLM shim,先跑 8081 一条也行。)

local-server 上用 autossh 保持隧道,跑在 tmux 里(与模型服务同款运维方式):

bash 复制代码
tmux new-session -d -s ds-tunnel 'autossh -M 0 -N \
  -o ServerAliveInterval=30 -o ServerAliveCountMax=3 \
  -o ExitOnForwardFailure=yes -o StrictHostKeyChecking=accept-new \
  -i ~/.ssh/jump_tunnel_ed25519 \
  -R 127.0.0.1:8081:127.0.0.1:8081 \
  -R 127.0.0.1:8090:127.0.0.1:8090 \
  ubuntu@<跳板机>'

运维:tmux attach -t ds-tunnel 查看;断线 autossh 自动重连。注意一个坑 :tmux kill-session 杀不掉 autossh 进程本身(忽略 SIGHUP),残留进程会占着远端端口导致新隧道 ExitOnForwardFailure 退出------重建前先 pkill -f 'autossh.*<跳板机IP>'(模式里 IP 用 106[.]54 这类写法,避免 pkill 匹配到自身命令行误杀会话------亲身踩过)。

4. nginx 反向代理(与既有站点共存)

跳板机 80/443 原本跑着一个静态站点(SPA)。要求:API 走隧道,站点不受影响;http 的 API 路径不做 301 跳转(很多 SDK 不跟随 POST 重定向,用户明确要 http base_url 可用)。

共享代理参数(/etc/nginx/snippets/ds-api-proxy.conf):

nginx 复制代码
proxy_pass http://127.0.0.1:8081;
proxy_http_version 1.1;
proxy_set_header Connection "";          # 关闭逐跳 Connection 头, 支持 SSE
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off;                     # SSE 流式必须关缓冲
proxy_request_buffering off;
client_max_body_size 100m;               # 大 prompt, 默认 1m 会 413
proxy_read_timeout 900s;                 # 长生成
proxy_send_timeout 900s;

站点配置里(80 和 443 两个 server block 都加):

nginx 复制代码
location ^~ /v1/            { include /etc/nginx/snippets/ds-api-proxy.conf; }
location = /health          { include /etc/nginx/snippets/ds-api-proxy.conf; }
location = /healthz         { include /etc/nginx/snippets/ds-api-proxy.conf; }
location = /chat/completions { include /etc/nginx/snippets/ds-api-proxy.conf; }
location = /completion      { include /etc/nginx/snippets/ds-api-proxy.conf; }
location = /completions     { include /etc/nginx/snippets/ds-api-proxy.conf; }
# 其余 location / { try_files ... }(443)或 return 301(80)保持不变

要点:

  • location ^~ /v1/ 是前缀匹配且跳过正则,静态站点的其它路径不受影响;80 块里它排在 location / { return 301 ... } 之前被精确/前缀规则优先命中,所以 API 的 http 请求直达后端。
  • 只代理白名单路径,不整站转发------/ 仍然是原来的站点。
  • 改配置前备份到 sites-enabled 之外(如 /etc/nginx/backups/):include /etc/nginx/sites-enabled/* 会把 .bak 文件也加载进来 ,我们踩出过一个 "conflicting server name" 警告------备份文件参与了 server_name 竞争。sudo nginx -t && sudo systemctl reload nginx。

5. 暴露面收敛(重要)

跳板机 sshd 配置了 GatewayPorts yes(历史原因),导致 -R 127.0.0.1:8081:... 实际绑在 0.0.0.0------回环绑定请求被全局配置覆盖了。两层兜底:

  1. 腾讯云安全组 :本来就不放行 8081/8090,公网不可达(实测从外网 curl <跳板机IP>:8081 超时)。

  2. iptables 主机防火墙 (防御纵深,防 VPC 内横向 + 防安全组误开):

    bash 复制代码
    sudo iptables -I INPUT 1 -p tcp --dport 8081 ! -i lo -j DROP
    sudo iptables -I INPUT 1 -p tcp --dport 8090 ! -i lo -j DROP

    注意 iptables-persistent 未安装,跳板机重启后需重加(可写进 systemd unit 或装好 persistent)。

  3. 就算前两层都失守,llama-server 的 API Key 鉴权仍是最后防线(401)。

6. 验收清单

bash 复制代码
# 链路通
curl -s https://api.example.com/health            # {"status":"ok"}
curl -s http://api.example.com/health             # 同样直达, 不是 301
# 鉴权
curl -s -o /dev/null -w '%{http_code}' -X POST https://api.example.com/v1/chat/completions \
  -H 'content-type: application/json' -d '{"model":"x","messages":[{"role":"user","content":"hi"}]}'
# → 401
# 带 key 生成(OpenAI SDK 同此)
curl -s -X POST https://api.example.com/v1/chat/completions \
  -H "Authorization: Bearer $(cat .api-key)" -H 'content-type: application/json' \
  -d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":16}'
# 站点未被破坏
curl -s -o /dev/null -w '%{http_code}' https://api.example.com/    # 200

上一篇 :01 硬件选型与 llama.cpp 部署 · 下一篇 :03 多协议支持:Anthropic 与 Responses API

相关推荐
龙亘川5 天前
一网统管AI平台民生业务实践:基于城市数字底座赋能公积金业务服务升级
大数据·安全·智慧城市·开源软件·数据可视化·政务
kybs19915 天前
全球灾害数据分析可视化 毕业设计-附源码66794
vue.js·spring boot·mysql·安全·django·c#·asp.net
LorryJovens5 天前
【LAAP科研】双系统具身AGI范式研究——基于LAAP认知架构与Jev概率决策模型的系统性技术调研与范式验证
人工智能·gpt·安全·架构
其实防守也摸鱼5 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
山东科恩光电5 天前
提升安全性的关键技术:安全触边在工业自动化中的应用解析
安全
云贝贝贝5 天前
腾讯云 TDSQL(MySQL 版)性能优化与慢查询排障实战 6 招
运维·腾讯云
LONGZETECH5 天前
一线职教实测:风光 580 汽车故障诊断仿真系统,破解实车实训四大核心痛点
人工智能·学习·安全·架构·汽车
Mikko75 天前
jackson-databind 升到 2.21.6 就安全了吗?jackson-core 是另一个坐标,它那条 high 全局库至今没收
java·后端·安全·json
小葱运维5 天前
命名与环境规范
运维·开源·云计算