Ollama API安全防护实战:如何用反向代理和OAuth2.0防止‘裸奔‘
Ollama API安全防护实战:从“裸奔”到企业级堡垒的构建之路
最近在帮几个创业团队部署本地大模型时,我发现一个普遍存在的安全隐患——很多开发者把Ollama部署起来后,就直接对外提供服务了,完全没意识到自己的API接口正在“裸奔”。这就像把自家保险柜放在路边,还贴了张纸条写着“钥匙在门垫下面”。我见过最夸张的情况是,有人把训练了半年的行业专用模型直接暴露在公网,三天后模型权重就被爬了个干净,还被人注入了恶意提示词。
Ollama确实是个好东西,它让本地运行大模型变得像docker run一样简单。但这份“简单”也带来了安全上的麻痹。默认情况下,Ollama的API服务监听在0.0.0.0:11434,这意味着任何能访问到你服务器IP的人,都能直接调用你的模型、上传你的数据、甚至通过一些尚未被发现的安全漏洞控制整台机器。更可怕的是,很多开发者连基本的日志监控都没配置,被攻击了都浑然不知。
这篇文章不是简单的配置教程,而是基于我过去半年在多个生产环境中实际踩坑、优化、再踩坑的经验总结。我会带你从最基础的网络层防护开始,一步步构建起完整的OAuth2.0身份认证体系,最终实现一个既安全又不失便捷的企业级Ollama部署方案。无论你是个人开发者想保护自己的实验环境,还是团队负责人需要为公司的AI服务搭建安全防线,这里都有你需要的实战方案。
1. 基础防护:从网络层筑起第一道防线
在讨论任何高级安全方案之前,我们得先确保基础牢靠。很多安全漏洞其实源于最基本的配置疏忽。我见过不少团队花大力气搞OAuth2.0,结果服务器防火墙却开着所有端口——这就像给金库装了虹膜识别,却忘了锁大门。
1.1 理解Ollama的默认网络行为
当你执行ollama serve时,发生了什么?默认情况下,Ollama会绑定到0.0.0.0:11434。这个0.0.0.0是个特殊地址,意味着“监听所有网络接口”。如果你的服务器有多个网卡(比如一个内网、一个公网),那么所有网卡上的11434端口都会开放。
# 查看Ollama当前监听的端口和地址
netstat -tlnp | grep 11434
# 或者用更现代的ss命令
ss -tlnp | grep 11434
你会看到类似这样的输出:
tcp 0 0 0.0.0.0:11434 0.0.0.0:* LISTEN 12345/ollama
那个0.0.0.0:11434就是问题所在。在开发环境这没问题,但在生产环境,这就是安全隐患。
1.2 系统级防火墙配置:不只是iptables
很多人一提到防火墙就想到iptables,但在现代Linux发行版中,我们有更好的选择。以Ubuntu 22.04为例,系统默认使用ufw(Uncomplicated Firewall)作为前端,底层仍然是iptables,但配置起来直观得多。
第一步:先放行必要的服务端口
假设你的服务器还需要提供SSH(22端口)和HTTP(80/443端口)服务:
# 允许SSH连接
sudo ufw allow 22/tcp
# 允许HTTP和HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# 查看当前规则
sudo ufw status numbered
第二步:为Ollama创建精细化的访问控制
单纯的“允许/拒绝”不够精细。我们需要的是基于IP的白名单机制。但注意,如果你的团队使用动态IP或者经常远程办公,单纯的IP白名单会很痛苦。这时候可以结合地理位置或VPN来管理。
# 假设你的办公室IP是203.0.113.0/24,家庭办公IP是198.51.100.123
sudo ufw allow from 203.0.113.0/24 to any port 11434 proto tcp
sudo ufw allow from 198.51.100.123 to any port 11434 proto tcp
# 启用防火墙
sudo ufw enable
注意:在启用防火墙前,务必确保你当前的SSH连接不会被阻断。最好在物理控制台或通过不会断开的连接执行这些操作。
第三步:更高级的防火墙策略
对于需要更精细控制的场景,可以考虑使用nftables(iptables的继任者)。它支持更复杂的匹配条件和更好的性能。
# 安装nftables(如果尚未安装)
sudo apt install nftables
# 创建一个简单的nftables配置
sudo nano /etc/nftables.conf
# 添加以下内容
table inet filter {
chain input {
type filter hook input priority 0;
# 允许已建立的连接
ct state established,related accept
# 允许回环接口
iif lo accept
# 允许SSH(来自特定IP段)
ip saddr { 203.0.113.0/24, 198.51.100.123 } tcp dport 22 accept
# 允许Ollama API(同样来自特定IP段)
ip saddr { 203.0.113.0/24, 198.51.100.123 } tcp dport 11434 accept
# 默认拒绝所有其他入站连接
drop
}
}
1.3 环境变量配置:让Ollama只监听本地
这是最简单也最有效的一步。通过设置OLLAMA_HOST环境变量,我们可以强制Ollama只绑定到本地回环地址127.0.0.1,这样即使防火墙配置有误,外部也无法直接访问。
Linux系统(Systemd服务)的配置方法:
大多数生产环境使用Systemd来管理服务。Ollama的Systemd服务文件通常位于/etc/systemd/system/ollama.service或/lib/systemd/system/ollama.service。
# 首先停止Ollama服务
sudo systemctl stop ollama
# 编辑服务配置文件
sudo systemctl edit --full ollama.service
在[Service]部分添加环境变量:
[Service]
# 原有的配置保持不变...
Environment="OLLAMA_HOST=127.0.0.1:11434"
Environment="OLLAMA_MODELS=/path/to/your/models" # 可选:自定义模型路径
Docker部署时的配置:
如果你使用Docker运行Ollama,配置方式略有不同:
# 创建自定义的docker-compose.yml
version: '3.8'
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
environment:
- OLLAMA_HOST=127.0.0.1:11434
volumes:
- ollama_data:/root/.ollama
networks:
- internal_network
# 注意:不对外暴露端口
# ports:
# - "11434:11434"
volumes:
ollama_data:
networks:
internal_network:
driver: bridge
提示:在Docker环境中,即使设置了
OLLAMA_HOST=127.0.0.1,这个127.0.0.1指的是容器内部的回环地址,不是宿主机的。所以还需要确保不通过ports暴露端口。
验证配置是否生效:
配置完成后,重启服务并检查监听状态:
# 重新加载systemd配置
sudo systemctl daemon-reload
# 启动Ollama
sudo systemctl start ollama
# 查看服务状态
sudo systemctl status ollama
# 检查端口监听情况
sudo ss -tlnp | grep 11434
正确的输出应该显示只监听127.0.0.1:11434,而不是0.0.0.0:11434。
2. 反向代理:安全与功能的完美平衡
只监听本地回环地址确实安全,但我们也需要让授权用户能够访问。这时候就需要反向代理出场了。反向代理不仅解决了访问问题,还带来了额外的好处:负载均衡、SSL终止、请求过滤、访问日志等。
2.1 为什么选择Nginx作为反向代理
在众多反向代理选项中,我推荐Nginx,原因很实际:
- 性能优异:事件驱动架构,内存占用小,能处理大量并发连接
- 功能全面:内置了SSL、gzip、缓存、限流等常用功能
- 配置直观:相比Apache的.htaccess,Nginx的配置更清晰易读
- 社区活跃:遇到问题容易找到解决方案
下面是一个完整的Nginx配置示例,我会逐段解释每个配置项的作用。
2.2 基础Nginx配置:从零开始
首先安装Nginx(以Ubuntu为例):
sudo apt update
sudo apt install nginx
创建Ollama专用的配置文件:
sudo nano /etc/nginx/sites-available/ollama-proxy
以下是完整的配置内容,我添加了详细的注释:
# Ollama API反向代理配置
# 作者:基于生产环境经验整理
# 最后更新:2024年
# 上游服务器定义
upstream ollama_backend {
# 指向本地Ollama服务
server 127.0.0.1:11434;
# 连接池配置
keepalive 32; # 保持的连接数
keepalive_timeout 60s; # 连接保持时间
# 如果需要负载均衡,可以添加多个后端
# server 127.0.0.1:11435;
# server 127.0.0.1:11436;
}
server {
# 监听端口 - 可以根据需要修改
listen 443 ssl http2;
listen [::]:443 ssl http2;
# 你的域名
server_name ollama.yourcompany.com;
# SSL证书配置
ssl_certificate /etc/ssl/certs/your-domain.crt;
ssl_certificate_key /etc/ssl/private/your-domain.key;
# SSL优化配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头部
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# 客户端请求限制
client_max_body_size 100M; # 允许上传大模型文件
client_body_timeout 300s; # 长请求超时设置
client_header_timeout 60s;
# 访问日志(建议开启用于审计)
access_log /var/log/nginx/ollama_access.log combined;
error_log /var/log/nginx/ollama_error.log warn;
# 主要API端点配置
location /api/ {
# 只允许特定HTTP方法
limit_except GET POST PUT DELETE {
deny all;
}
# 反向代理到Ollama
proxy_pass http://ollama_backend;
# 代理相关配置
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置(大模型推理可能需要较长时间)
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
# 缓冲设置
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
# 启用gzip压缩(节省带宽)
gzip on;
gzip_types application/json text/plain;
gzip_min_length 1024;
}
# 健康检查端点
location /health {
access_log off;
proxy_pass http://ollama_backend/api/tags;
proxy_set_header Host $host;
# 只允许内网访问健康检查
allow 127.0.0.1;
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
return 200 "healthy\n";
}
# 阻止对敏感路径的直接访问
location ~ ^/(\.git|\.env|config|logs) {
deny all;
return 404;
}
# 默认拒绝所有其他请求
location / {
return 403;
}
}
# HTTP重定向到HTTPS(可选)
server {
listen 80;
listen [::]:80;
server_name ollama.yourcompany.com;
return 301 https://$server_name$request_uri;
}
启用配置并测试:
# 创建符号链接
sudo ln -s /etc/nginx/sites-available/ollama-proxy /etc/nginx/sites-enabled/
# 测试配置语法
sudo nginx -t
# 重新加载Nginx
sudo systemctl reload nginx
# 测试访问
curl -k https://ollama.yourcompany.com/api/tags
2.3 高级防护:限流与请求过滤
基础配置完成后,我们还需要防止滥用。大模型API调用消耗资源,如果没有限制,很容易被爬虫或恶意用户耗尽。
基于IP的限流配置:
# 在http块中定义限流区域
http {
# 定义限流区域
limit_req_zone $binary_remote_addr zone=ollama_api:10m rate=10r/s;
# ... 其他配置 ...
}
server {
# ... 之前的server配置 ...
location /api/generate {
# 应用限流
limit_req zone=ollama_api burst=20 nodelay;
# 限制请求体大小(防止过大的提示词)
client_max_body_size 1M;
# 限制Content-Type
if ($content_type !~ "application/json") {
return 415;
}
proxy_pass http://ollama_backend/api/generate;
# ... 其他代理配置 ...
}
location /api/chat {
# 聊天接口可以宽松一些
limit_req zone=ollama_api burst=30 nodelay;
proxy_pass http://ollama_backend/api/chat;
# ... 其他代理配置 ...
}
}
基于地理位置的访问控制:
如果你只服务特定地区的用户,可以结合GeoIP数据库进行限制:
# 安装GeoIP模块
sudo apt install libnginx-mod-http-geoip
# 下载GeoIP数据库
sudo mkdir -p /usr/share/GeoIP
sudo wget -O /usr/share/GeoIP/GeoIP.dat.gz https://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
sudo gunzip /usr/share/GeoIP/GeoIP.dat.gz
在Nginx配置中使用:
http {
geoip_country /usr/share/GeoIP/GeoIP.dat;
# 创建基于国家的访问映射
map $geoip_country_code $allowed_country {
default 0;
CN 1; # 中国
US 1; # 美国
JP 1; # 日本
# ... 其他允许的国家 ...
}
}
server {
location /api/ {
# 检查国家代码
if ($allowed_country = 0) {
return 403 "Access denied from your region";
}
proxy_pass http://ollama_backend;
# ... 其他配置 ...
}
}
3. OAuth2.0集成:企业级身份认证方案
反向代理解决了网络层的访问控制,但对于需要多用户、多团队协作的场景,我们还需要身份认证和授权。OAuth2.0是目前最成熟的解决方案,它不仅能验证用户身份,还能精细控制每个用户的权限。
3.1 OAuth2.0基础概念与选择
在开始集成之前,我们需要明确几个关键概念:
| 概念 | 说明 | 在Ollama场景中的应用 |
|---|---|---|
| 资源所有者 (Resource Owner) | 拥有受保护资源的用户 | 使用Ollama API的开发者或终端用户 |
| 客户端 (Client) | 请求访问资源的应用 | 调用Ollama API的前端应用或脚本 |
| 授权服务器 (Authorization Server) | 验证用户身份并颁发令牌 | 如Keycloak、Auth0或自建服务 |
| 资源服务器 (Resource Server) | 托管受保护资源的服务器 | 运行Ollama的服务器 |
| 访问令牌 (Access Token) | 代表授权权限的凭证 | JWT令牌,包含用户信息和权限 |
对于Ollama API保护,我推荐使用客户端凭证模式或密码模式,具体取决于你的使用场景:
- 客户端凭证模式:适合机器对机器的通信,比如后台服务调用Ollama
- 密码模式:适合受信任的客户端应用,需要用户直接输入凭证
3.2 使用Keycloak搭建授权服务器
Keycloak是一个开源的身份和访问管理解决方案,功能全面且易于部署。下面是在Docker中快速部署Keycloak的步骤:
# 创建docker-compose.yml
version: '3.8'
services:
keycloak:
image: quay.io/keycloak/keycloak:latest
container_name: keycloak
command: start-dev
environment:
KC_HOSTNAME: auth.yourcompany.com
KC_HOSTNAME_PORT: 8443
KC_HOSTNAME_STRICT_BACKCHANNEL: "true"
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: your_secure_password_here
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: keycloak_db_password
ports:
- "8080:8080"
- "8443:8443"
volumes:
- ./keycloak_data:/opt/keycloak/data
depends_on:
- postgres
networks:
- auth_network
postgres:
image: postgres:15
container_name: postgres_keycloak
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: keycloak_db_password
volumes:
- ./postgres_data:/var/lib/postgresql/data
networks:
- auth_network
networks:
auth_network:
driver: bridge
启动后,访问https://auth.yourcompany.com:8443,使用设置的admin凭证登录,然后按照以下步骤配置:
- 创建Realm:每个Realm是一个独立的安全域
- 创建Client:为Ollama API创建一个客户端
- 配置Client:设置有效的重定向URI和Web Origins
- 创建用户和角色:定义可以访问Ollama的用户和权限
3.3 Nginx与OAuth2.0的集成
有了授权服务器,我们需要在Nginx层面验证每个请求的令牌。这里使用auth_request模块和Lua脚本实现。
首先安装必要的Nginx模块:
# 对于Ubuntu/Debian
sudo apt install nginx-extras
# 对于CentOS/RHEL
sudo yum install nginx-mod-http-lua
然后创建验证脚本:
-- /etc/nginx/lua/validate_token.lua
local http = require "resty.http"
local cjson = require "cjson"
local function validate_token(access_token)
local httpc = http.new()
-- 向Keycloak验证令牌
local res, err = httpc:request_uri("https://auth.yourcompany.com:8443/realms/ollama-realm/protocol/openid-connect/userinfo", {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. access_token,
["Content-Type"] = "application/json"
},
ssl_verify = false -- 生产环境应该设为true并配置证书
})
if not res then
ngx.log(ngx.ERR, "Failed to validate token: ", err)
return false
end
if res.status == 200 then
-- 令牌有效,可以进一步检查用户角色等
local userinfo = cjson.decode(res.body)
-- 检查用户是否有访问Ollama的权限
if userinfo and userinfo.realm_access then
local roles = userinfo.realm_access.roles or {}
for _, role in ipairs(roles) do
if role == "ollama_user" or role == "ollama_admin" then
-- 将用户信息传递给后端
ngx.req.set_header("X-User-ID", userinfo.sub or "")
ngx.req.set_header("X-User-Email", userinfo.email or "")
ngx.req.set_header("X-User-Roles", table.concat(roles, ","))
return true
end
end
end
end
return false
end
-- 从请求头中提取令牌
local auth_header = ngx.var.http_Authorization
if not auth_header then
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
if not token or token == "" then
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
-- 验证令牌
if not validate_token(token) then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
更新Nginx配置以使用这个验证:
server {
listen 443 ssl http2;
server_name ollama.yourcompany.com;
# ... SSL配置 ...
# 内部认证端点
location = /auth {
internal;
# 调用Lua脚本验证令牌
access_by_lua_file /etc/nginx/lua/validate_token.lua;
# 如果验证通过,返回200
return 200;
}
# 受保护的API端点
location /api/ {
# 先进行认证
auth_request /auth;
# 认证失败时的错误处理
auth_request_set $auth_status $upstream_status;
error_page 401 = @error401;
error_page 403 = @error403;
# 认证通过后,代理到Ollama
proxy_pass http://ollama_backend;
# 传递认证信息给后端(如果需要)
proxy_set_header X-User-ID $upstream_http_x_user_id;
proxy_set_header X-User-Email $upstream_http_x_user_email;
proxy_set_header X-User-Roles $upstream_http_x_user_roles;
# ... 其他代理配置 ...
}
# 错误处理
location @error401 {
return 401 '{"error": "Unauthorized", "message": "Missing or invalid authentication token"}';
}
location @error403 {
return 403 '{"error": "Forbidden", "message": "Insufficient permissions"}';
}
}
3.4 客户端如何获取和使用令牌
对于需要调用Ollama API的客户端,现在需要先获取访问令牌。这里以Python客户端为例:
import requests
import json
from typing import Optional, Dict
class OllamaClient:
def __init__(self, base_url: str, client_id: str, client_secret: str, realm: str = "ollama-realm"):
self.base_url = base_url.rstrip('/')
self.token_url = f"https://auth.yourcompany.com:8443/realms/{realm}/protocol/openid-connect/token"
self.client_id = client_id
self.client_secret = client_secret
self.access_token = None
self.token_expiry = None
def authenticate(self) -> bool:
"""获取访问令牌"""
try:
response = requests.post(
self.token_url,
data={
'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret
},
headers={'Content-Type': 'application/x-www-form-urlencoded'},
verify=True # 生产环境应该验证SSL证书
)
if response.status_code == 200:
token_data = response.json()
self.access_token = token_data['access_token']
# 设置令牌过期时间(提前30秒刷新)
self.token_expiry = time.time() + token_data['expires_in'] - 30
return True
else:
print(f"认证失败: {response.status_code} - {response.text}")
return False
except Exception as e:
print(f"认证过程中发生错误: {e}")
return False
def _ensure_token_valid(self):
"""确保令牌有效,必要时刷新"""
if not self.access_token or time.time() >= self.token_expiry:
if not self.authenticate():
raise Exception("无法获取有效的访问令牌")
def generate(self, model: str, prompt: str, **kwargs) -> Dict:
"""调用生成API"""
self._ensure_token_valid()
url = f"{self.base_url}/api/generate"
headers = {
'Authorization': f'Bearer {self.access_token}',
'Content-Type': 'application/json'
}
data = {
'model': model,
'prompt': prompt,
**kwargs
}
response = requests.post(url, json=data, headers=headers, verify=True)
response.raise_for_status()
return response.json()
def chat(self, model: str, messages: list, **kwargs) -> Dict:
"""调用聊天API"""
self._ensure_token_valid()
url = f"{self.base_url}/api/chat"
headers = {
'Authorization': f'Bearer {self.access_token}',
'Content-Type': 'application/json'
}
data = {
'model': model,
'messages': messages,
**kwargs
}
response = requests.post(url, json=data, headers=headers, verify=True)
response.raise_for_status()
return response.json()
# 使用示例
if __name__ == "__main__":
# 初始化客户端
client = OllamaClient(
base_url="https://ollama.yourcompany.com",
client_id="ollama-client",
client_secret="your-client-secret-here"
)
# 认证
if client.authenticate():
# 调用API
try:
response = client.generate(
model="llama2",
prompt="解释一下量子计算的基本原理",
stream=False
)
print(f"响应: {response['response']}")
except requests.exceptions.HTTPError as e:
print(f"API调用失败: {e}")
if e.response.status_code == 401:
print("令牌可能已过期,尝试重新认证...")
client.authenticate()
else:
print("客户端认证失败,请检查凭证")
4. 监控、审计与持续安全
安全不是一次性的配置,而是一个持续的过程。即使部署了最完善的安全措施,如果没有监控和审计,你仍然无法及时发现和响应安全事件。
4.1 全面的日志记录策略
日志是我们了解系统状态、排查问题、审计访问的第一手资料。对于Ollama API服务,我们需要从多个层面收集日志:
Nginx访问日志的增强配置:
http {
# 定义自定义日志格式,包含更多安全相关信息
log_format ollama_security '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$http_x_forwarded_for" "$http_x_user_id" '
'"$http_x_user_roles" "$request_time" '
'"$upstream_response_time"';
# ... 其他配置 ...
}
server {
# ... server配置 ...
access_log /var/log/nginx/ollama_security.log ollama_security;
error_log /var/log/nginx/ollama_error.log warn;
# 单独记录API请求日志
location /api/ {
access_log /var/log/nginx/ollama_api.log ollama_security;
# ... 其他配置 ...
}
}
Ollama自身的日志配置:
Ollama默认的日志输出比较简略,我们可以通过环境变量增加日志级别:
# 在systemd服务文件中添加
Environment="OLLAMA_DEBUG=1"
Environment="OLLAMA_LOG_LEVEL=debug"
# 或者通过命令行启动时指定
ollama serve --verbose
使用Logrotate管理日志文件:
创建Logrotate配置,防止日志文件无限增长:
# /etc/logrotate.d/ollama-nginx
/var/log/nginx/ollama_*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 640 nginx adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}
4.2 实时监控与告警
日志收集后,我们需要实时分析并设置告警。这里推荐使用Prometheus + Grafana + Alertmanager的组合。
在Nginx中暴露Prometheus指标:
首先安装nginx-prometheus-exporter:
# 下载并安装exporter
wget https://github.com/nginxinc/nginx-prometheus-exporter/releases/download/v0.11.0/nginx-prometheus-exporter_0.11.0_linux_amd64.tar.gz
tar -xzf nginx-prometheus-exporter_0.11.0_linux_amd64.tar.gz
sudo mv nginx-prometheus-exporter /usr/local/bin/
# 创建systemd服务
sudo nano /etc/systemd/system/nginx-exporter.service
服务文件内容:
[Unit]
Description=NGINX Prometheus Exporter
After=network.target
[Service]
Type=simple
User=nginx
ExecStart=/usr/local/bin/nginx-prometheus-exporter -nginx.scrape-uri=http://127.0.0.1:8080/nginx_status
Restart=always
[Install]
WantedBy=multi-user.target
配置Nginx状态页面:
server {
# ... 主配置 ...
# 状态页面(只允许本地访问)
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
Prometheus配置:
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'nginx'
static_configs:
- targets: ['localhost:9113']
metrics_path: /metrics
- job_name: 'ollama'
static_configs:
- targets: ['localhost:11434']
metrics_path: /api/metrics # 如果Ollama提供metrics端点
- job_name: 'node'
static_configs:
- targets: ['localhost:9100'] # node_exporter
关键监控指标和告警规则:
在Prometheus中定义告警规则:
# ollama_alerts.yml
groups:
- name: ollama_alerts
rules:
# API调用频率异常
- alert: HighAPICallRate
expr: rate(nginx_http_requests_total{status=~"2.."}[5m]) > 100
for: 2m
labels:
severity: warning
annotations:
summary: "高API调用频率"
description: "Ollama API调用频率超过100次/分钟,当前值: {{ $value }}"
# 认证失败率过高
- alert: HighAuthFailureRate
expr: rate(nginx_http_requests_total{status="401"}[5m]) / rate(nginx_http_requests_total[5m]) > 0.1
for: 2m
labels:
severity: warning
annotations:
summary: "高认证失败率"
description: "认证失败率超过10%,可能遭受暴力破解攻击"
# 服务器资源监控
- alert: HighMemoryUsage
expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes > 0.9
for: 5m
labels:
severity: critical
annotations:
summary: "内存使用率过高"
description: "内存使用率超过90%,可能影响服务稳定性"
4.3 定期安全审计与漏洞扫描
除了实时监控,定期的人工审计和自动化扫描同样重要。我建议建立以下审计清单:
每月安全审计清单:
-
访问控制审计
- 检查防火墙规则是否仍然适用
- 审查OAuth2.0客户端列表,移除不再使用的客户端
- 验证用户权限,确保最小权限原则
-
日志审计
- 分析异常访问模式
- 检查是否有失败的认证尝试
- 验证管理员操作日志
-
配置审计
- 检查SSL/TLS配置是否仍然安全
- 验证所有服务是否使用最新版本
- 审查环境变量和配置文件中的敏感信息
-
依赖项审计
# 检查Ollama依赖的漏洞 trivy image ollama/ollama:latest # 检查系统包 apt list --upgradable # 检查Python依赖(如果有) pip-audit
自动化漏洞扫描脚本示例:
#!/bin/bash
# security_scan.sh - Ollama安全扫描脚本
set -e
echo "开始Ollama安全扫描 - $(date)"
# 1. 检查开放的端口
echo "=== 检查开放端口 ==="
sudo netstat -tulpn | grep -E ':(11434|80|443)' || true
# 2. 检查防火墙规则
echo "=== 检查防火墙规则 ==="
sudo ufw status verbose || sudo iptables -L -n -v
# 3. 检查服务运行状态
echo "=== 检查服务状态 ==="
sudo systemctl status ollama --no-pager || true
sudo systemctl status nginx --no-pager || true
# 4. 检查SSL证书有效期
echo "=== 检查SSL证书 ==="
if [ -f "/etc/ssl/certs/your-domain.crt" ]; then
openssl x509 -in /etc/ssl/certs/your-domain.crt -noout -dates
fi
# 5. 检查日志文件权限
echo "=== 检查日志权限 ==="
ls -la /var/log/nginx/ollama_*.log 2>/dev/null || echo "未找到Ollama日志文件"
# 6. 检查最近的安全更新
echo "=== 检查安全更新 ==="
apt list --upgradable 2>/dev/null | grep -i security || echo "无安全更新"
# 7. 检查可疑进程
echo "=== 检查可疑进程 ==="
ps aux | grep -E '(ollama|nginx)' | grep -v grep || true
echo "安全扫描完成 - $(date)"
4.4 应急响应计划
即使有最好的防护,安全事件仍可能发生。提前制定应急响应计划至关重要:
安全事件响应清单:
-
检测与确认
- 监控告警触发
- 人工验证是否为误报
- 确定影响范围
-
遏制与隔离
# 立即隔离受影响系统 sudo ufw deny from <攻击者IP> sudo systemctl stop ollama # 如果需要 # 备份当前状态用于后续分析 sudo tar -czf /backup/incident_$(date +%Y%m%d_%H%M%S).tar.gz \ /var/log/nginx/ \ /etc/nginx/ \ /var/lib/ollama/ \ /etc/systemd/system/ollama.service -
根除与恢复
- 分析日志确定攻击向量
- 修复安全漏洞
- 从干净备份恢复数据
-
事后分析与改进
- 编写事件报告
- 更新安全策略
- 进行团队培训
关键联系人清单(应保存在安全的地方):
| 角色 | 联系人 | 联系方式 | 职责 |
|---|---|---|---|
| 安全负责人 | 张三 | 电话/邮件 | 总体协调 |
| 系统管理员 | 李四 | 电话/邮件 | 技术处理 |
| 网络管理员 | 王五 | 电话/邮件 | 网络隔离 |
| 法务联系人 | 赵六 | 电话/邮件 | 法律咨询 |
我在实际部署中遇到过最棘手的一次安全事件,不是来自外部攻击,而是内部配置错误——一个开发人员为了方便调试,临时关闭了防火墙,结果忘记重新开启。等我们发现时,系统已经被扫描了上千次。从那以后,我们建立了严格的变更管理制度和自动化的配置检查脚本。安全不是一劳永逸的事情,它需要持续的关注、定期的审计,以及整个团队的安全意识。每次部署新功能或修改配置时,多问一句“这对安全有什么影响”,往往能避免很多潜在问题。
更多推荐

所有评论(0)