Wujies 无界平台
平台功能(自研)打包与上架

安全校验模式

这一页只回答三个问题:app-market.security.mode 三档各自拦什么、某个部署实际生效的是哪一档、切到 enforce 之前必须做完什么。

签名怎么算、密钥怎么管见 密钥与签名;产物白名单允许哪些文件见 包安全与签名。本页不重复那些内容。


这个开关拦什么

检查只有一处实现:wujies-platform/wujies-modules/wujies-app-market/src/main/java/cn/wujies/appmarket/security/PackageSecurityInspector.java。它判两件事:

判什么判据不通过时的原话(Report.problems)
产物白名单包根只允许 app.json 与 signature.txt;顶层只允许 backend/、frontend/、migrations/;backend/ 只允许清单声明的那一个 .jar;migrations/ 只允许 .sql;frontend/ 只允许白名单扩展名包根出现不允许的文件: …、不允许的顶层目录: …、backend/ 下出现清单未声明的产物: …
签名包根有 signature.txt,且能用服务端的 app-market.security.public-key 验过应用包未签名(缺少 signature.txt)。…、应用包带签名,但服务端未配置 app-market.security.public-key,无法校验。、应用包签名校验失败: …

它不判断代码好坏,也不区分发布方——只回答"这个包是不是我签发的、有没有被改过"。所以"还没开放第三方上架"不是把模式调松的理由。

两个校验点都调 inspectOrThrow,缺一不可:

校验点谁调校验的是哪一份字节结论落在哪
上传时AppPackageController.parse(POST /app/package/parse)刚上传的那份响应字段 securityNote
下载后、解包前AppPackageDeployService.deploy(该类第 188 行,排在 verifyChecksum 之后)即将落位的那份部署报告的 security 字段

被 enforce 拒掉时抛 ServiceException,文案固定以这句开头,可直接拿它 grep 日志:

应用包安全检查未通过(app-market.security.mode=enforce):…

因为校验在解包之前,被拒时磁盘上不会落位任何文件(这一点实测过)。另外,问题条目最多只报前 6 条(MAX_REPORTED),汇报里的「问题 N 项」是截断后的数量。


三档模式的实际差别

模式inspect() 给出的结论会不会拦服务端日志接口响应 / 部署报告
offaccepted 恒为 true(问题照样被算出来)不拦不写(日志分支显式排除了 off)仍带出问题清单
warnaccepted 恒为 true不拦应用包安全检查发现问题(warn,仍放行): 模式=warn, 签名=…, 问题 N 项: …同上
enforce有问题则 accepted=false拦(上传与部署两处都拦)应用包安全检查未通过(enforce,将拒绝): …无问题时才写进报告:模式=enforce, 签名=有效, 无问题
off 不等于"什么结论都没有"

Report.summary() 不看模式,所以 off 下上传与部署的响应里照样能看到 模式=off, 签名=无签名文件, 问题 1 项: …。off 省掉的只是拦截与日志,检查本身仍然跑。类注释里那句"完全不检查"只在行为意义上成立。

模式值写错会静默变成 warn

normalizeMode(PackageSecurityInspector 末尾)会 trim + 转小写,只认 off / warn / enforce,其余一律当 warn,不报错。1Panel 表单那一栏是 type: text + rule: paramCommon,没有枚举校验,所以 enforce 这类写法多半会被 trim 救回来,而 strict、on、true 会悄悄退化成 warn。

另外,自检接口返回的 mode 字段是原样字符串(SignatureReadinessService 直接把 @Value 注入的值放进报告),不做归一化——它显示 strict 时实际按 warn 跑。判断当前行为看日志/部署报告里的 模式=,不要只看自检报告那个字段。


生效值来自哪:同一个键有三个来源

来源值位置
环境变量(容器化部署)1Panel 安装表单里的 SECURITY_MODE,默认 warn、required: truewujies-platform/deploy/1panel/apps/wujies-platform-server/5.6.3/data.yml 与 ...-client/5.6.3/data.yml;compose 里是 APP_MARKET_SECURITY_MODE: ${SECURITY_MODE}
jar 内 yml(本机直跑时生效)enforcewujies-platform/wujies-admin/src/main/resources/application.yml 的 app-market.security.mode
代码注解兜底(yml 缺失或键被删时)warnPackageSecurityInspector 与 SignatureReadinessService 的 @Value("${app-market.security.mode:warn}")

覆盖优先级(高 → 低):JVM 系统属性 -Dapp-market.security.mode=… → 环境变量 APP_MARKET_SECURITY_MODE → jar 内 application.yml → 注解兜底。

于是同一份代码在两种部署形态下默认值相反:

部署形态默认生效值怎么改要重建什么
本机直跑(wujies-platform/script/prune/start-host.ps1)enforce(yml 值)改 application.yml 后重启;start-host.ps1 -Build 会顺带重新 mvn -pl wujies-admin -am -B packageyml 打在 jar 里,改了就必须重建宿主
1Panel 应用包warn(表单值,盖掉镜像里 yml 的 enforce)安装表单里填,或装完到 1Panel「应用 → 参数」改 应用包检查模式 保存不用重建镜像:面板会重建容器;参数细节见 /deploy/configuration
容器里改不动公钥,enforce 容易踩空

public-key 没有环境变量:仓库里两个 1Panel 包的 data.yml 与 docker-compose.yml 都没有公钥字段,deploy/docker/backend/entrypoint.sh 也不导出它。所以容器里生效的公钥,就是构建镜像时打进 jar 的那个 application.yml 里的那一把——而按该文件自己的注释,那是"开发验证阶段生成的"密钥。

后果:1Panel 表单里把模式调成 enforce,用你自己 wujies/wujies-plugins/keys/private.pem 签的包会全部落到 invalid,报「签名与包内容不匹配」。这时候看起来像"签名坏了",实际是公钥不匹配。

要换公钥只有两条路:改 application.yml 的 public-key 后重建后端镜像(只重启容器没用),或自己在 compose 里加 APP_MARKET_SECURITY_PUBLIC_KEY(PEM 是多行文本,注意 YAML 块标量写法)。


切到 enforce 之前必须做完两件事

1. 公钥就位,且与发布私钥成对

没配公钥时 enforce 会拒绝所有包(包括你自己签的),报错里会明说「未配置公钥」而不是「签名无效」。打包时顺手就能核对:package.ps1 会拿本次签名用的公钥与宿主 application.yml 的 public-key 比对,一致打 ✓,不一致或为空会 Write-Warning(用的是 PackageKeys 同一套规范化)。

2. 已发布版本都能过:签名就绪度自检

GET /app/console/signature-readiness      # 权限 app:console:list

它把 app_version 里状态为 1(已发布,AppMarketConstants.VERSION_STATUS_RELEASED)、未删除的版本逐个下载验签,直接回答"现在能不能切":

返回字段含义
mode原样的配置值(见上面的告警,不一定等于实际行为)
publicKeyConfiguredpublic-key 是否非空
readyblockers 为空才为 true
summary一句话结论,可直接给人看
checked / truncated实际检查了几个包、是否因超过上限(100)被截断
counts五类计数,键恒为 signed / unsigned / invalid / unverifiable / failed
blockers阻止切换的原因(逐条可读)
items逐版本明细:versionId / appCode / version / status / reason
分类判据(SignatureReadinessService.checkOne)怎么处置
signed有签名且验过切了会被放行
unsigned包里没有 signature.txt就地重签,或重新打包上传成新版本
invalid有签名但验不过包被改过,或不是用同一私钥签的
unverifiable有签名但服务端没配公钥去配 public-key(不是包的问题)
failed包地址为空或下载失败不阻止切换,但表示这个版本没被检查到
它是按需调用的诊断接口

会真实下载所有包(上限 100,超出明确报 truncated)。不要做成定时任务——只会每天白拉一遍对象存储。就地重签的完整流程见 密钥与签名。

顺带把发布侧的默认行为固定下来

wujies/wujies-plugins/package.ps1 已经替你把"忘了签名"堵住:本机 keys/private.pem 存在时自动使用(跨机器/CI 用 WUJIES_SIGNING_KEY),显式传 -SigningKey 时若文件不存在会自动 keygen 到该目录;确实要打未签名包就显式加 -NoSign(与 -SigningKey 互斥)。参数表见 打包应用。

打包脚本的警告是按"宿主是 enforce"写的

未传私钥时它打印的是「宿主 app-market.security.mode=enforce 时这个包上传/部署会被拒绝」——它并不读宿主 yml 里的 mode(只读 public-key 做比对)。所以给 warn 部署打未签名包时这句警告会照旧出现,那只是提示,不是失败。


怎么切、怎么验收

本机直跑改的就是这一段(wujies-platform/wujies-admin/src/main/resources/application.yml):

app-market:
  security:
    mode: enforce          # off / warn / enforce
    public-key: |          # 与发布私钥成对的公钥;留空时 enforce 拒绝所有包
      -----BEGIN PUBLIC KEY-----
      …
      -----END PUBLIC KEY-----

按部署形态选一条:

形态切的动作生效时机
本机直跑改 application.yml 的 mode: enforce → start-host.ps1 -Build(或手工 mvn -pl wujies-admin -am -B install -DskipTests)→ 重启重启后
1Panel 包「应用 → 参数」把 应用包检查模式 改成 enforce → 保存面板重建容器后
临时验证(不改文件)设环境变量 APP_MARKET_SECURITY_MODE=enforce 再启动进程启动时

切完用这三条自证:

用例期望
再调一次自检mode=enforce、ready=true、counts.unsigned=0、counts.invalid=0
部署一个已签名包(控制台「部署」)成功,部署报告 security = 模式=enforce, 签名=有效, 无问题
上传一个未签名包(POST /app/package/parse)被拒,文案以 应用包安全检查未通过(app-market.security.mode=enforce):应用包未签名(缺少 signature.txt) 开头

warn 模式下问题的痕迹落在哪

位置有没有说明
上传响应 securityNote✅AppPackageController.parse 写入
控制台「部署」响应 security✅AppPackageDeployService.deploy 写入
后端服务日志✅log.warn("应用包安全检查发现问题(warn,仍放行)…"),grep 应用包安全检查 即可
任务中心的任务日志❌安装/升级路径只把 releasedCount / deferredLoad / migrationCount 写进任务日志(PluginAppInstaller),安全检查结论不进任务中心

所以在 warn 下排查"某个包为什么没签名"时:任务中心里看不到,要看后端服务日志,或自己调一次自检接口。任务中心本身的读法见 任务中心。


用户版(client)下自检接口不可用

app-market.edition=client 时,MarketEditionGuardFilter 会按路径前缀直接 403(附带明确文案,不是 404、也不是静默失效)挡掉 /app/console、/app/version、/app/package 以及 /app/category 的写操作。

例外只有 /app/console/owned-menus(菜单管理页要用它打归属标签)。签名就绪度自检不在例外里——它读的是本地目录,用户版下没有意义。所以用户版要确认签名状况,只能在官方版那一侧查,或看服务端日志。形态细节见 目录与版本形态。


相关阅读

  • 密钥与签名 —— 密钥生成、换钥、就地重签、自检分类的完整说明
  • 包安全与签名 —— 白名单与摘要算法的逐字定义
  • 上架与发布 —— 上传时那次校验发生在链路的哪一步
  • 部署配置覆盖 —— 1Panel 包覆盖了哪些键、public-key 为什么没有环境变量
  • 配置参考 —— app-market.security.* 三个键的默认值与注解兜底对照
  • 任务中心 —— 安装/升级的任务日志里有什么、没有什么

目录