资讯动态

第30章:RAGFlow 安全边界:鉴权、密钥、沙箱与数据隔离

发布时间:2026/10/4 7:03:34 来源:尧图企业网站定制
1 项目背景业务场景「云帆科技」的安全团队在对 RAGFlow 进行首次安全审计时发现了几个触目惊心的问题MinIO Bucket 策略为空任何人只要知道 MinIO 的 IP 和端口就可以匿名列出和下载所有上传的文档原文件——包括标为机密的薪资调整方案。API Token 在 Git 中测试脚本里硬编码了生产环境的 API Token已经提交到了内部 GitLab——所有有代码仓库权限的人都能操作 RAGFlow。Code 组件无沙箱Agent Canvas 中的 Code 组件可以执行任意 Python 代码包括os.system(rm -rf /)——在未隔离的环境中这就是一个定时炸弹。数据库密码明文.env文件中的 MySQL 和 Redis 密码都是明文且该文件被十几个同事复制传播。无审计日志谁在什么时候查了什么文档、问了什么问题——完全没有记录。安全事件发生后无法追溯。安全团队给运维小李下了限期整改令——一个月内完成基础安全加固。痛点安全配置缺失的直接后果数据泄露MinIO 公开访问 所有上传的 PDF/Word 原文件对任何人可见。Token 泄露 无轮换Token 被提交到 Git → 撤销 → 重新生成 → 所有使用该 Token 的服务全部中断。任意代码执行Code 组件无沙箱 → 恶意用户可执行系统命令 → 获取服务器 Shell。无审计追溯出事后不知道谁操作了什么——合规审计不通过、安全事件无法定责。密钥硬编码数据库密码泄露 所有数据可被直接读取/篡改。RAGFlow 安全风险矩阵按严重程度排序 严重程度 风险 影响范围 Critical MinIO 公开访问 所有文档原文件泄露 Critical Code 组件无沙箱 服务器被远程控制 High API Token 在代码仓库中 RAGFlow 全量被控 High 数据库密码明文传播 元数据文档全泄露 Medium 无审计日志 无法追溯/定责 Medium 默认密码未修改 管理员权限被冒用 Low CORS 配置过于宽松 跨站请求伪造风险2 项目设计小胖慌慌张张地关掉浏览器“大师大师我刚在浏览器地址栏直接输入了http://minio-server:9000居然直接打开了 MinIO 的控制台——没有登录界面所有上传的合同 PDF、薪资表 Excel 都能直接下载这安全吗”大师放下咖啡杯表情严肃“这是最严重的安全漏洞——对象存储未设访问控制。MinIO 默认允许匿名访问你必须在部署后第一分钟就把它锁掉。RAGFlow 的安全配置不是’可选’的而是’必须’的。我们从头梳理安全边界应该怎么设。”RAGFlow 安全五大防线 第一道网络边界 ├── 防火墙只开放 80/443Nginx 代理不暴露内部端口 ├── MinIO/Redis/MySQL 只监听内网不绑定 0.0.0.0 └── API Server 前加 WAF 或 Rate Limiting 第二道鉴权Authentication ├── 强密码策略长度≥12含大小写数字符号 ├── JWT Token 有效期 ≤ 2 小时Refresh Token ≤ 7 天 ├── API Token 定期轮换90 天 └── 多因子认证MFA——如果接入企业 SSO 第三道授权Authorization ├── 租户级数据隔离tenant_id 贯穿所有查询 ├── 数据集级权限控制只读/读写/管理 └── 最小权限原则——默认无权限显式授权 第四道数据安全 ├── MinIO Bucket Policy: deny anonymous access ├── 数据库密码用 Secret Manager 管理Vault/K8s Secret ├── 传输加密 TLSHTTPS MySQL SSL Redis TLS └── API Key / Token 不允许硬编码在代码中 第五道执行安全 ├── Code 组件运行在沙箱中gVisor / nsjail / Docker ├── 网络访问白名单只允许访问指定的内网服务 └── 文件系统只读挂载 tmpfs 临时目录技术映射五道安全防线 城堡的防御体系——护城河网络、城门检查鉴权、通行证分级授权、密室上锁数据、审讯室隔离执行。任何一道单独都不够。小胖“那 MinIO 具体怎么锁我已经被吓到了。”大师“两步走——先设 MinIO 自身的访问策略再确保只有 API Server 能访问 MinIO”# 步骤1MinIO 自身的 Policy——禁止匿名访问mcanonymoussetnone ragflow-minio/ragflow# 步骤2检查当前 Policymcanonymous get ragflow-minio/ragflow# 应输出: Access permission for ragflow-minio/ragflow is none# 步骤3验证——匿名访问应被拒绝curlhttp://localhost:9000/ragflow/# 应返回: ?xml version1.0 encodingUTF-8?ErrorAccessDenied/Error“更进一步——MinIO 应该只绑定到 Docker 的内部网络不暴露任何端口到宿主机。API Server 通过 Docker 内部 DNS 名称minio:9000访问。”小白“那 Code 组件的沙箱怎么搞据说能在 Canvas 里执行任意 Python 代码。”大师“这是最危险但最容易被忽视的漏洞。RAGFlow 的 Code 组件本质上是在 Task Executor 进程中exec()用户代码——如果不去限制用户可以执行任何操作。三层沙箱方案”Code 组件三层沙箱 第一层Python 代码限制最轻量 - 禁用危险内置函数: __import__, eval, exec, open, os, subprocess - 使用 RestrictedPython 或自定义 AST 过滤器 - 优点零额外资源开销 - 缺点可被绕过Python 沙箱逃逸技术很多 第二层进程级沙箱推荐 - nsjail / gVisor / Firecracker - 限制文件系统只读、网络白名单、内存/CPU上限、超时杀死 - 优点OS 级别隔离几乎无法逃逸 - 缺点额外资源开销每个沙箱 ~50MB 内存 第三层容器级沙箱最强隔离 - 每次 Code 执行启动一个临时 Docker/K8s Pod - 执行完自动销毁 - 优点完全隔离 - 缺点启动开销大~2 秒/次、不适合高频场景# 第一层沙箱示例受限 Python 执行环境importastimportsysclassSafeCodeExecutor:安全的 Python 代码执行器FORBIDDEN_MODULES{os,subprocess,sys,shutil,socket,requests,urllib}FORBIDDEN_FUNCTIONS{eval,exec,compile,__import__,open,input}classmethoddefexecute(cls,code:str,allowed_vars:dict,timeout:int5):在受限环境中执行用户代码# 1. AST 静态分析——检测禁止的函数/模块调用try:treeast.parse(code)cls._validate_ast(tree)exceptSyntaxErrorase:raiseValueError(f代码语法错误:{e})# 2. 动态执行——限制作用域restricted_globals{__builtins__:cls._restricted_builtins(),output:None,**allowed_vars,}importsignaldeftimeout_handler(signum,frame):raiseTimeoutError(代码执行超时)signal.signal(signal.SIGALRM,timeout_handler)signal.alarm(timeout)try:# 注意这仍是简化的安全方案生产环境建议用 nsjail/gVisorexec(compile(tree,user_code,exec),restricted_globals)finally:signal.alarm(0)returnrestricted_globals.get(output,None)classmethoddef_validate_ast(cls,tree):AST 静态分析——禁止危险调用fornodeinast.walk(tree):# 禁止 import os / import subprocess 等ifisinstance(node,ast.Import):foraliasinnode.names:ifalias.name.split(.)[0]incls.FORBIDDEN_MODULES:raiseValueError(f禁止导入模块:{alias.name})ifisinstance(node,ast.ImportFrom):ifnode.moduleandnode.module.split(.)[0]incls.FORBIDDEN_MODULES:raiseValueError(f禁止导入模块:{node.module})classmethoddef_restricted_builtins(cls):受限的内置函数集importbuiltins restricted{}fornameindir(builtins):ifname.startswith(_)ornameincls.FORBIDDEN_FUNCTIONS:continuerestricted[name]getattr(builtins,name)returnrestricted小胖“那 API Token 怎么管理我们已经有 Token 泄露了。”大师“五步紧急处理 长期改进”Token 泄露紧急处理 1. 立即撤销泄露的 Token控制台→API Token→撤销 2. 生成新 Token 3. 更新所有使用旧 Token 的服务环境变量/CI/CD Secret 4. 检查旧 Token 在泄露期间的 API 调用日志有无异常操作 5. 通知安全团队做影响评估 长期改进 - Token 用 Secret Manager 管理HashiCorp Vault / K8s Secret / CI/CD Secret - 代码仓库配置 pre-commit hook检测硬编码的 Token 模式gitleaks / TruffleHog - Token 设定期轮换策略90 天自动失效 - 限制 Token 的 IP 白名单仅允许服务器出口 IP3 项目实战环境准备目标对 RAGFlow 部署环境完成基础安全加固。前提RAGFlow 已部署具备管理员权限。分步实现步骤1MinIO 安全加固目标关闭 MinIO 匿名访问仅允许 API Server 访问。# 1. 禁止匿名访问mcaliassetragflow-minio http://localhost:9000${MINIO_USER}${MINIO_PASSWORD}mcanonymoussetnone ragflow-minio/ragflow# 2. 在 Docker Compose 中确保 MinIO 不暴露端口到宿主机# docker-compose.yml 中修改# services:# minio:# ports:# - 127.0.0.1:9000:9000 # 仅本地回环# - 127.0.0.1:9001:9001 # 不暴露到公网# 3. 创建只读策略用于审计/备份账户mcadmin policy create ragflow-minio read-only-policy -EOF { Version: 2012-10-17, Statement: [ { Effect: Allow, Action: [s3:GetObject, s3:ListBucket], Resource: [arn:aws:s3:::ragflow/*] } ] } EOF# 4. 验证安全加固效果# 匿名访问应被拒绝curl-Ihttp://localhost:9000/ragflow/21|head-1# 预期: HTTP/1.1 403 Forbidden步骤2密钥与凭证管理目标消除硬编码密钥迁移到环境变量/Secret Manager。# 1. 扫描代码仓库中的硬编码密钥gitleaks detect--source.--report-format json --report-path leaks_report.json# 2. 检查常见模式# - API Token: ragflow-xxxxxxxxxxxx# - Password: password / passwd# - API Key: sk-xxxx / api_key# 发现任何匹配 → 立即撤销 替换# 3. 迁移到环境变量最小化方案cat.env.secretEOF RAGFLOW_API_TOKENragflow-new-token-xxxxxxxx MYSQL_PASSWORDStrongPass2024!K8s REDIS_PASSWORDStrongPass2024!K8s MINIO_ROOT_PASSWORDStrongPass2024!K8s LLM_API_KEYsk-new-key-xxxxxxxx EOF# 加到 .gitignoreecho.env.secret.gitignoreecho.env.gitignore# 含敏感信息# 4. 在 docker-compose.yml 中引用# environment:# - MYSQL_PASSWORD${MYSQL_PASSWORD} # 从 .env.secret 读取# - REDIS_PASSWORD${REDIS_PASSWORD}步骤3API Token 轮换自动化目标实现 Token 的定期自动轮换。# token_rotation.py - API Token 自动轮换importosimportjsonfromdatetimeimportdatetime,timedeltafromragflowimportRAGFlowclassTokenRotator:定期轮换 RAGFlow API Tokendef__init__(self,admin_api_key):self.ragRAGFlow(api_keyadmin_api_key,base_urlos.environ[RAGFLOW_BASE_URL])defrotate(self,token_name_prefixauto-rotated):执行一次轮换# 1. 生成新 Tokennew_tokenself.rag.create_api_token(namef{token_name_prefix}-{datetime.now():%Y%m%d-%H%M})print(f[新Token] ID{new_token.id}, 前缀{new_token.token[:8]}...)# 2. 更新环境变量/Secret Managernew_token_valuenew_token.token# update_vault(secret/ragflow/api_token, new_token_value)# 或写 .env.secret 文件withopen(.env.secret.new,w)asf:# 读取旧文件只替换 Token 行withopen(.env.secret)asold:forlineinold:ifline.startswith(RAGFLOW_API_TOKEN):f.write(fRAGFLOW_API_TOKEN{new_token_value}\n)else:f.write(line)os.rename(.env.secret.new,.env.secret)# 3. 列出旧 Token超过 90 天的撤销old_tokensself.rag.list_api_tokens()fortokeninold_tokens:age_days(datetime.now()-token.created_at).daysifage_days90andtoken.id!new_token.id:self.rag.revoke_api_token(token.id)print(f[撤销] Token{token.name}(创建{age_days}天前))print(轮换完成。)# 在 CronJob 中定期执行如每月1号# 0 2 1 * * python /scripts/token_rotation.py步骤4审计日志配置目标记录关键操作的审计日志。# audit_logger.py - 审计日志中间件importjsonimportloggingfromdatetimeimportdatetime audit_loggerlogging.getLogger(ragflow.audit)classAuditMiddleware:审计日志中间件——记录关键操作SENSITIVE_ENDPOINTS[/api/v1/datasets,# 数据集 CRUD/api/v1/documents,# 文档操作/api/v1/chats,# 聊天问答/api/v1/users,# 用户管理/api/v1/llm,# 模型配置变更]classmethodasyncdeflog_request(cls,request,response,user,latency_ms):记录受审计的请求pathrequest.path# 只审计敏感端点ifnotany(path.startswith(ep)forepincls.SENSITIVE_ENDPOINTS):returnaudit_record{timestamp:datetime.now().isoformat(),user_id:user.idifuserelseanonymous,tenant_id:user.tenant_idifuserelseNone,method:request.method,path:path,status_code:response.status_code,latency_ms:latency_ms,ip:request.remote_addr,user_agent:request.headers.get(User-Agent,)[:100],}# 对 POST/PUT/DELETE 记录更详细的信息ifrequest.methodin(POST,PUT,DELETE):audit_record[query_params]dict(request.args)# 注意不要记录 request body可能含密码、Token 等敏感信息audit_logger.info(json.dumps(audit_record,ensure_asciiFalse))# 审计日志查询示例# grep DELETE.*datasets /var/log/ragflow/audit.log# 找到谁在什么时间删除了哪些数据集步骤5安全检查清单自动化目标可定期运行的安全基线检查脚本。# security_baseline_check.sh - 安全基线检查#!/bin/bashecho RAGFlow 安全基线检查$(date)ISSUES0# 1. 默认密码echo-n[1] 默认管理员密码: ifcurl-s-XPOST http://localhost/api/v1/login\-HContent-Type: application/json\-d{email:adminragflow.io,password:ragflow}|jq-e.code 0/dev/null21;thenechoFAIL - 仍使用默认密码((ISSUES))elseechoPASSfi# 2. MinIO 匿名访问echo-n[2] MinIO 匿名访问: ifcurl-s-o/dev/null-w%{http_code}http://localhost:9000/ragflow/|grep-q200;thenechoFAIL - 允许匿名访问((ISSUES))elseechoPASSfi# 3. HTTPS 启用echo-n[3] HTTPS 启用: ifcurl-s-o/dev/null-w%{http_code}https://localhost/api/v1/version2/dev/null|grep-q200;thenechoPASSelseechoWARN - 仅 HTTP生产环境建议启用 HTTPS((ISSUES))fi# 4. 数据库端口暴露echo-n[4] MySQL 端口暴露: ifss-tlnp|grep:3306|grep-q0.0.0.0;thenechoFAIL - MySQL 暴露在公网((ISSUES))elseechoPASSfi# 5. Code 组件沙箱echo-n[5] Code 组件沙箱: # 检查 gVisor/nsjail 是否安装...echoINFO - 请手动确认沙箱已启用echoecho 检查完成:$ISSUES个问题 if[$ISSUES-gt0];thenecho请立即修复上述 FAIL 项fi测试验证# test_security.pyclassTestSecurityBaseline:deftest_default_password_changed(self):验证默认密码已修改rrequests.post(f{BASE}/api/v1/login,json{email:adminragflow.io,password:ragflow})assertr.status_code!200,默认密码仍有效deftest_minio_anonymous_access_denied(self):验证 MinIO 禁止匿名访问rrequests.get(http://localhost:9000/ragflow/)assertr.status_codein[403,401],fMinIO 允许匿名访问:{r.status_code}deftest_api_token_not_leaked(self):验证 Token 未出现在代码仓库中importsubprocess resultsubprocess.run([gitleaks,detect,--source,.,--no-git,--verbose],capture_outputTrue,textTrue)leaksresult.stdout.count(leak found)assertleaks0,f扫描发现{leaks}个密钥泄露deftest_code_sandbox_rejects_dangerous_imports(self):验证沙箱拒绝危险代码withpytest.raises(ValueError,match禁止导入):SafeCodeExecutor.execute(import os\noutputos.listdir(.))完整代码清单路径说明api/apps/__init__.py鉴权中间件api/db/services/user_service.py用户权限管理agent/tools/base.pyTool 安全基类沙箱、校验column/chapter30/本章安全脚本4 项目总结优点 缺点维度RAGFlow 内置安全企业安全网关自建安全体系SaaS 平台租户隔离★★★ 数据库级★☆☆ 需映射★★★ 完全控制★★★ 平台保证密钥管理★★☆ 需配合 Vault★★★ 统一密钥★★★ 可定制★★★ 平台管理沙箱执行★★☆ 需配置 gVisor★☆☆ 不支持★★★ 灵活★★☆ 平台限制审计日志★★☆ 需自实现★★★ 网关级★★★ 完整★★★ 内置部署复杂度★★★ 一体化★★☆ 需独立部署★☆☆ 开发量大★★★ 零运维适用场景数据合规要求GDPR、个人信息保护法等要求数据隔离和审计追溯。多租户 SaaS不同客户的数据必须物理/逻辑隔离。Agent 执行不受信代码Code 组件中运行用户提交的代码——必须沙箱。安全审计通过SOC2、ISO27001 等认证需要。内网 外网混合部署部分数据必须在特定网络域内。不适用场景纯内网隔离环境物理隔离的网络中数据安全压力小得多但仍需基础鉴权和审计。个人/实验项目没有敏感数据、没有多用户——安全配置可以适当简化但不可完全忽略。注意事项安全不是一次性配置安全基线需要定期重检建议每月一次新功能上线前做安全评审。MinIO 的 Policy 修改不会自动生效修改后需要等待缓存过期或手动重启 MinIO。Code 组件的 Python 沙箱不可靠Python 语言的动态特性使其极难完全沙箱化。生产环境必须用 OS 级隔离nsjail/gVisor/Docker。审计日志的建议保留期合规要求通常 90 天-1 年。超过保留期的日志可归档到冷存储。HTTPS 证书自动续期使用 cert-manager Let’s Encrypt 自动续期避免证书过期导致全站不可用。常见踩坑经验故障现象根因解决方法MinIO Policy 修改后仍可匿名访问Policy 缓存未刷新重启 MinIO 或等待 TTL 过期gVisor 中 Code 组件无法访问 LLM APIgVisor 默认禁止出站网络在 gVisor 配置中开启网络白名单Token 轮换后服务大面积 401部分服务未及时更新 Token保留旧 Token 24 小时灰度过渡期安全扫描报告误报太多规则太宽泛如匹配到注释中的 “password”调优 gitleaks 规则排除测试文件和注释审计日志磁盘爆满未设日志滚动和保留策略配置 logrotate 或 Docker logging driver 的 max-size思考题RAGFlow 的 Code 组件允许用户执行任意 Python 代码。即使使用了 nsjail 沙箱如何设计一个代码审查流程——用户提交的代码需要管理员审核通过后才能在生产环境执行需要考虑审核效率和用户体验的平衡。公司有三级数据分类——公开、内部、机密。请设计一个数据分类标签机制——在上传文档时标记密级检索和问答时自动根据用户的安全等级过滤不可见的数据。该标签需要嵌入到哪种存储中MySQL 元数据/Chunk 元数据/文件系统中如何防止被绕过答案提示见第31章末尾或附录 D。延伸阅读与资源10倍开发者的 Dify 魔法书从零构建全栈 AI 应用后端工程师转型AI第一课-Ollama 与私有化大模型实战大型语言模型(LLM) vLLM 高性能推理落地实战Agent开发之LlamaIndex 实战修炼与源码进阶大语言模型Transformers 实战修炼与源码剖析

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑