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

相关推荐
FairGuard手游加固5 小时前
手游加固怎么做?SO、IL2CPP、AssetBundle 与运行时保护详解
安全·游戏·unity
略懂而已5 小时前
腾讯云COS 自动同步 for windows
windows·云计算·腾讯云
迪康妍妍6 小时前
终端管控实操:封堵网盘与IM外发通道的落地思路
运维·网络·安全·架构·安全性测试
kunicoding6 小时前
多线程编程核心概念详解:从线程基础到并发安全
java·jvm·安全
FinelyYang6 小时前
JeecgBoot + EuroOffice 协同文档在线编辑全栈接入指南(支持 JWT 安全校验)
安全
舰长1157 小时前
curl发布8.23.0修复24个漏洞
安全
迪康Defender9 小时前
终端安全运维:浅谈迪康端点安全一体化管理系统工具箱实用能力
运维·安全·信息可视化·自动化·运维开发·学习方法·远程工作
嘟哩DuliDuli9 小时前
AI创作工作区的项目上下文与多模态任务边界
android·人工智能·安全·ai·软件工程
pusheng202510 小时前
普晟传感再获“消防产业核心配套优质品牌”:以可靠传感技术,让全球合规变简单
大数据·前端·人工智能·物联网·安全
EasyGBS10 小时前
通信基站/铁塔机房无人值守,国标GB28181公网平台EasyGBS视频能力怎么去管
深度学习·安全·音视频