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

相关推荐
小新讲网安1 小时前
PowerShell安全攻防实战:从渗透利用到检测防御全攻略
安全·web安全·网络安全·黑客·自动化·sqlmap·网安
嘟哩DuliDuli3 小时前
AI 账单变高的技术原因:重复上下文和用量归属
android·人工智能·安全·ai·软件工程
清水白石0083 小时前
Python 如何设计一个线程安全的缓存?从锁策略、LRU 到工程化实战
python·安全·缓存
九硕智慧建筑一体化厂家13 小时前
数字化管控落地,直流照明构筑安全照明体系
运维·人工智能·笔记·安全·智慧城市
chunmiao303215 小时前
DeepSeek V4-Pro 正式版上线,API 8月17日起分时调价
网络·安全
湘美书院--湘美谈教育16 小时前
湘美书院主理人谈AI文学:提示词与Skill的与时俱进
大数据·人工智能·安全·自动化·生活
hz5678916 小时前
视频会议终端对接详解:协议兼容、系统集成与常见问题
安全·实时音视频·信息与通信
安全指北针17 小时前
AI编程工具供应链安全实战:当你的AI编码助手成为跳板
人工智能·安全·ai编程
迪康Defender17 小时前
AI 重构终端安全运营:智能分析中枢 AI Insight 模块架构与落地场景深度解析
运维·网络·人工智能·其他·安全·重构·架构