Skip to content

整合安全与发布修复:权限、Secret、运行环境、webhook 与扫描策略 - #338

Merged
GoSimplicity merged 179 commits into
mainfrom
ops-sec27-w1merge-shaomai
Oct 24, 2026
Merged

GoSimplicity merged 179 commits into
mainfrom
ops-sec27-w1merge-shaomai

Conversation

@GoSimplicity

@GoSimplicity GoSimplicity commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

整合 #327–#337 的全部分支提交,并保留原 #338 分支的祖先关系,解决叠加分支和主线之间的冲突。

保留精确路由鉴权和内置超管角色规则,同时要求 Secret reveal、ServiceAccount GET/POST token 显式授权;统一采用严格 ENV 解析、主线密钥校验、Secret YAML 防护及发布 webhook 配置。修复发布容器缺少 JWT_ISSUER 的启动失败,以及 webhook 预检脚本与实际 loader 的整数上限、timeout=0、多文档 YAML 规则差异。保留主线对 cron job_type 默认值的现有决策,移除与最终模型不符的旧升级说明。

验证已通过:Go 1.26.10 完整 go test ./...、go build;82 项发布脚本测试;权限 matcher/Expand 与敏感接口回归;webhook 跨 shell 222 次回归;全部 gitleaks 策略回归。#339 已先行合入经审查的 RFC 6455 公开示例误报策略和发布 issuer 修复,最终 177 个新增历史提交扫描零发现。

最终提交 2ca7574 的 GitHub CI 全部通过:

整栈验收覆盖容器健康、MySQL 迁移与事务、真实 Redis 异步通知、网关会话、RBAC/工单流程和内部 MCP。

- K8sCluster.KubeConfigContent 改为 json:"-",通过 MarshalJSON 统一输出 has_kube_config,
  所有序列化集群对象的接口都不会泄露 kubeconfig;GORM 读写与内部连接不受影响
- 列表不再清空 kubeconfig,以便正确计算 has_kube_config;删除已无调用方的
  utils.CleanClusterSensitiveInfo / CleanClusterSensitiveInfoList
- 更新接口 kube_config_content 选填:不传/空字符串保留原值,非空整段替换并按创建规则校验
- 新增 sqlite + httptest 单测覆盖详情/列表无泄露、更新不传 kubeconfig 保留原值且仍能连接
- 新增 Session.CloseNormally:先发送 CloseNormalClosure,在 1s 内等待对端回应关闭帧,再关闭连接
- 新增 Session.CloseWithError:exec 异常结束(非 context 取消)时发送 CloseInternalServerErr,
  reason 为固定说明“终端会话异常结束”,不含内部错误细节,并保证不超过 123 字节
- 两者共用有超时的发送与有上限的等待;等待期间 Pong 不再延长读期限;
  仍阻塞在 Read 的 stdin 复制 goroutine 读到关闭帧后通知
- context 取消路径保持原样
- 新增模拟 pods/exec SPDY 服务端的单测:exit 后收到 1000、exec 出错收到 1011,
  且终端/remotecommand/websocket goroutine 全部退出
- git rm deploy/kubernetes/config(不改写历史,密钥轮换见 SEC-01B)
- 新增 .gitleaks.toml(扩展默认规则,仅对 RFC6455 样例 nonce 做窄 allowlist)
- pre-commit 钩子增加 gitleaks 暂存区扫描;setup-git-hooks.sh 配置 core.hooksPath 并检查 gitleaks
- 新增 .github/workflows/gitleaks.yml(官方二进制 + sha256 校验)
- 新增 deploy/release/env.example;env.example 与 docker-compose.yaml 去除硬编码密钥
Pong 处理器“检查 closing → 延长读期限”与关闭流程“置 closing → 收紧读期限”
此前不是原子的:stdin reader 刚通过检查、关闭方随后收紧期限,Pong 再把期限
改回 30s;若该 reader 随即停止读取、关闭方拿到读锁,就会按 30s 期限等待。

- closing 标记与读期限写入改由 deadlineMu 串行化(beginClosing /
  extendReadDeadlineOnPong)
- awaitPeerClose 拿到读锁后再收紧一次读期限
- 新增测试:对端持续发送 Ping/Pong 且不回关闭帧,三种 reader 形态下关闭均需
  在 closeGracePeriod+500ms 内结束
- config.production.yaml / config.development.yaml 中 jwt.key1/key2/issuer、
  redis.password、mysql.addr、tree.password_encryption_key、
  notification.email.username/password、notification.feishu.app_id/app_secret
  置空,并注明对应环境变量;注入沿用结构体 env 标签 + viper.BindEnv 机制
- Load 拆出 loadFromFile(path),便于测试直接加载指定配置文件
- 新增测试:配置文件中上述字段必须为空;设置环境变量后两份配置均能注入
- 删除 setDefaults 与结构体 default 标签中的 jwt.key1/key2/issuer、
  mysql.addr(含 root:root DSN)、tree.password_encryption_key 默认值;
  不区分环境,不保留开发环境后门
- Config.Validate 首先检查 JWT_KEY1、JWT_KEY2、JWT_ISSUER、MYSQL_ADDR、
  TREE_PASSWORD_ENCRYPTION_KEY,缺失时一次性列出全部缺失变量名,不输出任何值
- 现有测试改为不内嵌旧默认密钥原文;新增 Validate / Load 缺失即失败、
  错误不泄露值、无内置密钥默认值的单元测试
- 命令行/脚本任务子进程只继承 PATH、HOME、LANG、LC_*、TZ、TMPDIR 及任务自身变量,
  不再继承服务进程的 JWT/数据库/AIOps 等密钥(纵深防御)
- 新增配置 cron.allow_local_exec(环境变量 CRON_ALLOW_LOCAL_EXEC),默认 false;关闭时:
  - 拒绝创建/更新为 job_type 2(命令行)、4(脚本)的任务
  - 拒绝手动触发这两类任务
  - 已有的这两类任务到点不执行,告警日志只记任务ID和类型,本次运行按失败记录且不重试
- 不删除执行器代码;开关打开时行为不变
- GET /api/cron/job/list 的 data 在原有 items、total 之外新增同级 bool 字段
  allow_local_exec,取自 cron.allow_local_exec(环境变量 CRON_ALLOW_LOCAL_EXEC),
  供前端在开关关闭时禁用命令行(2)/脚本(4)任务的创建、编辑与触发入口
- 新增 model.CronJobListResp(内嵌 ListResp[*CronJob],不破坏已有字段)
- 新增测试:开关开/关两种情况下 data 同时包含 items/total/allow_local_exec 且取值正确
- config.production.yaml / config.development.yaml 的 jwt.issuer 恢复为 188e907 时的原值
  (逐字节一致,已签发令牌的 iss 保持有效)
- 从 requiredSecretEnvVars 移除 JWT_ISSUER;仍可由环境变量 JWT_ISSUER 覆盖
  (沿用 env 标签 + viper.BindEnv/AutomaticEnv,环境变量优先于配置文件)
- Validate 增加普通校验 jwt.issuer 不能为空(为空会使令牌校验跳过 iss 检查),
  错误信息不归入"缺少必需的密钥环境变量"
- 测试:secretFields/requiredEnvNames 去掉 jwt.issuer;新增配置文件提供 issuer、
  两份配置一致、未设置时取 yaml 值、设置 JWT_ISSUER 时覆盖、为空时校验失败的用例
- internal/model/cron_job.go:JobType 的 gorm tag 去掉 default:2,保留 NOT NULL;
  漏传类型时由 NOT NULL 报错,不再静默落成命令行(2)任务
- 新增 docs/release/cron-job-type-default.md:待执行 DDL、回滚语句、执行前 SHOW CREATE TABLE、
  等 REL-01 迁移体系上线后再执行、生产执行须经用户批准
- 注意:启动时 InitTables 的 AutoMigrate(gorm v1.25.11 MigrateColumn 默认值比较)
  会对已有表自动 MODIFY COLUMN 去掉 DEFAULT 2,部署前须先决策,详见上述文档
- 新增测试:job_type 不声明列默认值且保持 NOT NULL
- 未对任何数据库执行 ALTER
…QL_ADDR 注入

- config/webhook.yaml 的 mysql.addr 置空并注明环境变量,不再包含账号密码
- 与 SEC-02A 一致,完整 DSN 整体走环境变量;与 SEC-22 一致,无内置默认值,
  缺少 WEBHOOK_MYSQL_ADDR 时 LoadWebhook 返回致命错误、webhook 启动即失败,错误只列变量名
- 取值后 viper.Set("mysql.addr"),webhook/di/gorm.go 的读取方式不变;
  主程序的 MYSQL_ADDR 与配置文件中的值不再对 webhook 生效
- LoadWebhook 拆出 loadWebhookFromFile(path),便于测试
- 新增测试:webhook.yaml 不含 DSN;缺失即失败且不泄露值、MYSQL_ADDR 不能替代;
  设置后注入成功且优先于 MYSQL_ADDR;配置文件缺失时同样失败
- 保留 job_type=2:判定依赖任务在后端容器内写标记文件、首次挂起、重投递后输出
  HA_RECOVERY_OK,HTTP 任务在 backend 被 SIGKILL 后无法保留这种状态
- 脚本开头说明前置条件;未设置 CRON_ALLOW_LOCAL_EXEC=true 时直接退出并提示
- 开关只经临时 override 注入本工具的随机独立项目 backend,不改 deploy/
- 创建任务前读取列表响应的 allow_local_exec(CRON-A),未开启时报错写明所需变量
- docs/release/cron-job-type-default.md:升级/回滚语句加上
  COMMENT '任务类型 1系统任务 2命令行 3HTTP 4脚本 5SSH'(逐字取自 CronJob.JobType 的 gorm tag),
  避免 MODIFY 清空列注释后 AutoMigrate 再 MODIFY COLUMN 一次
- 新增 DryRun 核对表:新模型+升级语句、旧模型+回滚语句均不再生成 ALTER;不带 COMMENT 时仍会生成
- 仅改文档;未对任何数据库执行 DDL
- README.en.md 配置表:JWT_ISSUER 从 JWT_KEY1/JWT_KEY2 一行拆出,注明由 config/*.yaml 的
  jwt.issuer 配置,环境变量仅用于覆盖;不能为空,改动会使已签发令牌失效
- README.md(中文)未列 JWT_ISSUER,无需修改;两份 README 均未提及
  WEBHOOK_MYSQL_ADDR / CRON_ALLOW_LOCAL_EXEC,未改
- list/detail 只返回键名和值字节数(data_keys/string_data_keys/redacted),
  data/string_data 保留键、值置空;YAML 中 data/stringData 值替换为 <redacted>
- 三个接口都去掉 kubectl.kubernetes.io/last-applied-configuration 注解
- 新增 GET /api/k8s/secret/:cluster_id/:namespace/:name/reveal,每次调用同步写审计
  (仅谁/何时/集群/命名空间/名称/键名,不含值),审计失败时不返回明文
- reveal 在接口目录登记为 k8s:secret:reveal,通配符授权不覆盖该接口
- 更新时回填脱敏值:表单空值保留原值,YAML 占位符还原原值,创建时拒绝占位符
- 用已有 MYSQL_PASSWORD 与 compose 中的 cloudops 用户/库拼出 go-sql-driver DSN
  (与 backend MYSQL_ADDR 同格式,主机默认 Compose 服务 mysql:3306),值不打印
- 新增 --add-missing:已有 .env 只在缺少该项时追加,不覆盖任何已有值,保持 0600
- 新增 --env-file / --mysql-host 供隔离验证使用;默认行为(拒绝覆盖已有 .env)不变
- compose.yaml 新增 webhook(deploy/release/webhook.Dockerfile 构建 cmd/webhook,内网 8888,
  WEBHOOK_MYSQL_ADDR 必填 :?,depends_on mysql healthy,健康检查 GET /api/v1/alerts/silence)
- 新增 alertmanager(profiles: [alerting],默认不启动)及 alertmanager.yml,转发到 webhook
- env.example / docs/release/README.md 同步说明
- 鉴权中间件、部门管理、资料修改不再比较用户名 admin,统一以启用中的
  内置超管角色(code=super_admin 且 is_system=1)判定,并写入请求上下文
- 新增幂等启动引导:确保内置超管角色存在并把 admin 绑定到该角色
- 授予/撤销内置超管角色仅限超管操作,防止持有角色分配权限的用户自我提权
- 无模型/表结构变更,种子数据与回滚见 docs/release/rbac-super-admin.md
…ld 重新对齐(w1align-1)

w1 119c332 起 alert_cache.go buildWebhookURL 用 hasPrefixFold 判断 http:// / https://,
HTTP://、Https:// 也按完整 URL 处理,7b47940(r4 A-3)写的“scheme 必须是小写 / 代码区分大小写”
在合入 w1 后过时。

- env.example、deploy/release/env.example、deploy/release/compose.yaml(只改注释)、
  docs/release/README.md:改为建议写小写、不区分大小写,并注明合入前旧代码仍区分大小写
- test_env_example.py / test_release.py:helper 按 ASCII 字节折叠对齐 hasPrefixFold;
  r4 的“大写 scheme 判错”用例改为按完整 URL 接受,新增 httpſ://、全角 H 仍判错;
  AlertAddrSchemeDocTests 断言新文案,并扫描四份文档不再出现旧说法
w1 119c332 起 internal/config/config.go 给 webhook Port 绑定 env:"WEBHOOK_PORT"、默认 8889,
loader 逐键合并(WEBHOOK_PORT > 配置文件 > 默认 8889,空串视为未设置),“WEBHOOK_PORT 对监听端口不生效”
在合入 w1 后过时。发布 compose 不给 webhook 传 WEBHOOK_PORT,发布栈合入前后都监听 8889。

- docs/release/README.md:41、deploy/release/compose.yaml webhook 段注释(只改注释,展开配置与 db97f42 一致)
- test_env_example.py:新增 WebhookPortDocTests;改掉“本分支 WEBHOOK_PORT 不生效”的注释
- prometheus-scrape-auth.md:15 不改,合并时取 w1 一侧
- P2-1:YAML 库报错按白名单改写,只保留错误类别与行号,invalid map key 等不再回显值
- P3-1:判断占位符前去掉首尾空白、NUL 与 U+200B/U+200C/U+200D/U+FEFF
- P3-2:同一映射中转成字符串后相同的键(1 与 "1")按重复键拒绝,结果确定
- P3-3:注释与文档改为 encoding/json 不区分大小写的规则,补充已知差异与升级风险
- P3-4:补非标准大小写 Metadata 下的重复键测试
- P3-6:Secret YAML 入口 1 MiB 上限,解析前检查
- maxSecretYAMLBytes 由 1 << 20 改为 2 << 20,仍按 YAML 文本字节数计算,在任何解析之前检查;报错文案改为「YAML 超过 2 MiB 上限」,不回显内容。
- data 的值是 base64(膨胀约 4/3),1 MiB 的入口上限会挡住接近 Kubernetes 1 MiB 上限的 Secret;放宽后这类 Secret 也能提交,真正的大小限制交给 apiserver。
- TestR2SizeLimit:2097152 字节通过,2097153 字节在三条路径上均以固定的大小错误拒绝且不回显;恰好上限的语法错误输入进入解析、多 1 字节则在解析前拒绝。
- 发布说明同步更新上限与原因,升级风险中补充超过 2 MiB 的 YAML 会在入口被拒绝。
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T03:33:23.749581Z 2ca7574 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

# Conflicts:
#	internal/app/permissions.go
#	internal/middleware/auth.go
# Conflicts:
#	internal/middleware/auth.go
# Conflicts:
#	docs/release/w1-jwt-config.md
#	env.example
#	internal/config/config.go
#	internal/config/jwt_expiration_limit_test.go
#	internal/config/loader.go
#	internal/config/production_env_checks_test.go
#	pkg/ssh/hostkey.go
#	pkg/ssh/hostkey_test.go
# Conflicts:
#	README.en.md
#	README.md
#	internal/config/config.go
#	internal/config/loader.go
#	pkg/envmode/envmode.go
#	pkg/envmode/envmode_test.go
#	pkg/ssh/hostkey.go
#	pkg/ssh/hostkey_test.go
…ecrets

# Conflicts:
#	docs/release/cron-job-type-default.md
# Conflicts:
#	env.example
#	scripts/tests/test_env_example.py
# Conflicts:
#	README.en.md
#	env.example
@GoSimplicity GoSimplicity changed the title ops-sec27-w1merge-shaomai:w1 与 ops-sec27 的合并结果 整合安全与发布修复:权限、Secret、运行环境、webhook 与扫描策略 Oct 24, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03f55bd9aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

IPAddress: actor.IP,
UserAgent: actor.UserAgent,
HttpMethod: "GET",
Endpoint: actor.Endpoint,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accommodate valid resource paths in synchronous audit records

For a valid 63-character namespace and 190-character Secret name, the reveal URL is 279 characters, but AuditLog.Endpoint in internal/model/system_audit.go is limited to 255 characters. This full endpoint passes through buildAuditLog unchanged, so MySQL with strict mode rejects the audit insert. Because RevealSecret now requires that insert to succeed, an otherwise successful Kubernetes read returns HTTP 500 and the Secret cannot be revealed. Store a bounded route template or enlarge the audit columns to accommodate valid resource paths; the same issue affects writeTokenAudit.

Useful? React with 👍 / 👎.

# Conflicts:
#	deploy/release/compose.yaml
#	scripts/tests/test_release_env.py
@GoSimplicity
GoSimplicity merged commit a8f1700 into main Oct 24, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant