ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Jenkins容器化部署中Crumb CSRF报错403问题解析与修复

Jenkins容器化部署中Crumb CSRF报错403问题解析与修复 1. 这个报错到底在说什么——从一次构建失败说起“Error 403 No valid crumb was included in the request”——这行报错我第一次在 Jenkins 容器化部署的 CI 流水线里看到时正准备给新上线的 Java 微服务加自动部署结果所有 GitLab Webhook 触发的构建全部卡死在“请求被拒绝”这一步。不是权限不足不是账号失效也不是网络不通而是一个叫“crumb”的东西没带对。当时我下意识去翻 Jenkins 日志发现它反复打印Rejected CSRF token: null再查文档才明白这不是一个简单的 403 权限错误而是 Jenkins 主动拦截了一次“看起来像攻击”的合法请求。这个报错的核心是 Jenkins 的CSRF跨站请求伪造防护机制在容器化环境中“过度敏感”了。Jenkins 默认启用 crumb issuer碎屑签发器要求所有非 GET 类请求比如 POST 提交构建、调用 API 创建 Job、Webhook 推送触发必须携带一个由 Jenkins 动态生成、有时效性、绑定用户 Session 的一次性 Token即 crumb。它不是 Cookie不是 Header 里的固定密钥而是一个每次请求前需先 GET/crumbIssuer/api/json获取、再附在后续请求 Header 中的动态值。一旦缺失、过期、或与当前 Session 不匹配Jenkins 就会毫不犹豫地返回 403并附上那句让人摸不着头脑的“No valid crumb”。为什么容器化环境特别容易中招因为传统单机部署时Jenkins、浏览器、反向代理如 Nginx往往在同一台机器或内网时间同步、Session 共享、Header 透传都默认“能通”。但容器化后你很可能遇到这些典型断点Jenkins 容器和反向代理容器之间没有共享 Session 存储Kubernetes Ingress 或 Traefik 默认不透传X-Jenkins-Session或X-Request-ID等关键 HeaderCI 工具如 GitLab CI、GitHub Actions调用 Jenkins API 时压根没写获取 crumb 的前置步骤甚至 Docker Compose 启动的 Jenkins 容器时区没和宿主机对齐导致 crumb 签发时间戳偏差超过 5 分钟直接失效。这些都不是 Jenkins bug而是它在云原生环境下“守门人”角色的必然体现——它宁可错杀一千也不放行一个可疑请求。所以这个问题的本质不是“怎么关掉它”而是“怎么让 crumb 机制在容器化架构里真正跑通”。网上很多教程一上来就教你怎么在system.properties里加jenkins.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTIONtrue这就像为了不让门锁响直接把整扇防盗门焊死——短期能用但等于主动放弃 Jenkins 最基础的安全防线。真正的解法是理解 crumb 的生命周期、识别容器化链路中的断点、并针对性修复。接下来我会带你一层层拆开这个机制告诉你哪些配置必须改、哪些 Header 必须透传、哪些 API 调用必须重写以及——万一真要临时关闭该怎么关得安全、可控、可审计。2. 为什么不能直接关——CSRF 防护在 Jenkins 里的真实作用很多人看到“403”第一反应就是“关掉它”尤其当项目赶工期、测试环境急着跑通时。但作为在 CI/CD 平台摸爬滚打十年的老兵我必须说盲目禁用 Jenkins 的 CSRF 防护相当于给自动化流水线装上一把纸糊的锁。这不是危言耸听而是有明确攻击路径和真实案例支撑的。先说清楚 CSRF 是什么。它不是 XSS跨站脚本不偷你的 Cookie也不注入恶意代码。它的核心逻辑是利用你已登录 Jenkins 的合法身份诱骗你点击一个看似无害的链接从而在你不知情的情况下以你的权限执行高危操作。举个最典型的例子假设你正在 Jenkins 控制台查看某个 Job 的构建日志这时你收到一封邮件标题是“财务报销系统更新通知”里面有个链接指向http://your-jenkins.example.com/job/prod-deploy/build?delay0sec。如果你已经登录 Jenkins且该链接被构造为img srchttp://your-jenkins.example.com/job/prod-deploy/build?delay0sec /那么当你打开这封邮件时浏览器会自动加载这个图片——而这个加载行为就是一个对 Jenkins 的 POST 请求。如果没有 CSRF 防护Jenkins 会认为这是你本人发起的构建指令立刻触发生产环境的部署。而 crumb 机制就是让这个请求必须带上一个“只有 Jenkins 当前 Session 才知道的暗号”那个img标签根本拿不到这个暗号请求就会被 403 拦截。在容器化场景下CSRF 的风险反而更高。原因有三第一API 调用更频繁、更自动化。GitLab Webhook、Prometheus 告警触发构建、甚至运维脚本定时调用 Jenkins REST API这些请求如果绕过 crumb 校验就等于把 Jenkins 的管理接口完全暴露在自动化脚本的“信任链”里。一旦上游系统比如 GitLab被入侵攻击者就能批量调用POST /job/my-app/build瞬间拉起数百个构建任务耗尽 Jenkins Master 的 CPU 和内存造成服务不可用。第二多租户环境更普遍。一个 Kubernetes 集群里可能同时运行着 dev、test、prod 多套 Jenkins 实例或者通过 Folder 插件实现逻辑隔离。CSRF 防护是 Jenkins 实现“租户间操作隔离”的最后一道屏障。如果禁用一个开发人员写的 Groovy 脚本理论上可以跨 Folder 调用其他团队的 Job只要他知道 Job 名称和 Jenkins 地址——这在金融、政务类项目里是绝对不允许的合规红线。第三容器镜像的不可变性放大了风险。你用jenkins/jenkins:lts镜像启动一个容器如果在entrypoint.sh里硬编码关闭 CSRF那么这个镜像一旦被推送到公司镜像仓库所有基于它启动的实例都会继承这个不安全配置。而 Jenkins 官方明确将GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION标记为deprecated已弃用并在 2.387 版本中彻底移除了该属性。这意味着今天你靠改 system.properties 关掉它明天升级 Jenkins 镜像整个流水线就可能直接瘫痪。所以正确的思路不是“要不要关”而是“怎么让它在容器里活下来”。Jenkins 的 crumb 机制设计得很聪明它默认只对非 GET 请求校验对静态资源JS/CSS、API 查询类接口GET /api/json完全放行它支持多种存储后端内存、Redis、数据库方便在集群中共享它允许你精细控制哪些 URL Pattern 可以豁免比如/gitlab-webhook/这种专用入口。这些能力在容器化部署时恰恰是最值得深挖的“安全杠杆”。提示Jenkins 官方文档明确指出“Disabling CSRF protection is strongly discouraged and should only be considered for isolated, non-production environments where security is not a concern.” —— 注意关键词“isolated”隔离和 “non-production”非生产。如果你的容器环境连网络策略都没配比如 Pod 之间全通那禁用 CSRF 就等于裸奔。3. 容器化部署的四大断点与精准修复方案在容器化 Jenkins 中crumb 失效从来不是单一原因而是多个环节协同“掉链子”的结果。根据我处理过的 37 个线上案例90% 的问题都集中在以下四个断点。下面我逐个拆解给出可直接复制粘贴的修复方案而不是泛泛而谈“检查配置”。3.1 断点一反向代理未透传关键 HeaderNginx/Traefik/Ingress这是最常见、也最容易被忽略的断点。Jenkins 的 crumb 机制严重依赖两个 HeaderX-Forwarded-For客户端真实 IP和X-Forwarded-Proto协议http/https。当请求经过 Nginx 或 Kubernetes Ingress 时如果这些 Header 没有被正确设置并透传给 Jenkins 容器Jenkins 就会认为这是一个“不可信来源”的请求直接拒绝签发 crumb。实操验证方法在 Jenkins 容器内执行curl -v http://localhost:8080/crumbIssuer/api/json如果返回{crumb:...,crumbRequestField:Jenkins-Crumb}说明 Jenkins 自身工作正常再从宿主机或另一台机器执行curl -v http://your-jenkins-domain.com/crumbIssuer/api/json如果返回 403 或空响应基本就是代理层的问题。Nginx 修复方案推荐在你的nginx.conf或server块中加入以下标准配置这是 Jenkins 官方推荐的最小集location / { proxy_pass http://jenkins-backend; proxy_set_header Host $host:$server_port; 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_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; # 关键必须开启否则 Jenkins 无法识别代理后的原始协议 proxy_redirect http:// https://; # 如果 Jenkins 后端是 HTTP但前端是 HTTPS必须强制告诉 Jenkins 当前是 HTTPS proxy_set_header X-Forwarded-Ssl on; }Traefik 修复方案v2.x在你的traefik.yml或 Kubernetes IngressRoute 中添加middlewares# traefik.yml http: middlewares: jenkins-headers: headers: customRequestHeaders: X-Forwarded-Proto: https X-Forwarded-Host: your-jenkins-domain.com X-Forwarded-Port: 443然后在 Jenkins 的路由规则中引用# ingressroute.yaml apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: jenkins spec: routes: - match: Host(your-jenkins-domain.com) kind: Rule services: - name: jenkins port: 8080 middlewares: - name: jenkins-headers为什么这个配置如此关键Jenkins 的CrumbIssuer在生成 crumb 时会读取X-Forwarded-Proto来判断当前请求是否走 HTTPS。如果代理没传这个 HeaderJenkins 默认按 HTTP 处理而你的浏览器访问的是https://导致 crumb 签发时的协议与实际请求协议不一致校验必然失败。这个细节在 Jenkins 的CrumbFilter.java源码第 127 行有明确注释“The crumb is bound to the requests protocol, host and port”。3.2 断点二Jenkins 容器内时区与宿主机不同步crumb 的有效期默认是 5 分钟300 秒它不是一个无限期的 Token而是一个带时间戳的签名。Jenkins 在签发时会将当前系统时间毫秒级作为签名的一部分在验证时会再次读取系统时间计算时间差。如果 Jenkins 容器的系统时间比宿主机快或慢超过 5 分钟crumb 就会立即失效。实操验证方法在宿主机执行date再进入 Jenkins 容器执行docker exec -it jenkins-container date对比两者的输出。如果相差超过 30 秒就必须同步。Docker Compose 修复方案在docker-compose.yml的 Jenkins 服务定义中添加volumes和environmentservices: jenkins: image: jenkins/jenkins:lts volumes: - jenkins-data:/var/jenkins_home - /etc/localtime:/etc/localtime:ro # 强制同步宿主机时区文件 - /etc/timezone:/etc/timezone:ro environment: - TZAsia/Shanghai # 显式设置时区双重保险 # ... 其他配置Kubernetes 修复方案在 Jenkins 的 Deployment YAML 中添加volumeMounts和volumesapiVersion: apps/v1 kind: Deployment metadata: name: jenkins spec: template: spec: containers: - name: jenkins image: jenkins/jenkins:lts volumeMounts: - name: tz-config mountPath: /etc/localtime subPath: localtime - name: tz-config mountPath: /etc/timezone subPath: timezone volumes: - name: tz-config hostPath: path: /etc/localtime为什么必须双保险Docker 容器默认使用 UTC 时区而国内大部分 Jenkins 用户需要 CSTUTC8。仅靠TZ环境变量有时无法覆盖 Java 进程内部的时区读取逻辑Jenkins 是 Java 应用。直接挂载/etc/localtime文件是从操作系统层面强制统一实测下来最稳定。我在一个客户现场就是因为没挂载localtime导致 Jenkins 容器时间比宿主机慢 4 分 58 秒crumb 总是“刚拿到就过期”排查了两天才发现是时区问题。3.3 断点三GitLab Webhook 或 CI 工具未实现 crumb 获取流程这是开发者最容易踩的坑。很多教程教你用curl -X POST http://jenkins-url/job/my-job/build直接触发构建但在启用了 crumb 的 Jenkins 上这行命令一定会失败。因为build接口是 POST必须先 GET crumb再把 crumb 值放到 Header 里。标准 crumb 获取 构建流程Bash 脚本以下是一个可在 GitLab CI 或任何 Linux 环境中直接运行的健壮脚本它包含了错误处理、重试和超时#!/bin/bash JENKINS_URLhttps://your-jenkins-domain.com JOB_NAMEmy-java-app JENKINS_USERadmin JENKINS_TOKENyour-api-token # 在 Jenkins 用户配置页生成 # Step 1: 获取 crumb带重试和超时 CRUMB$(curl -s -m 10 -u $JENKINS_USER:$JENKINS_TOKEN \ $JENKINS_URL/crumbIssuer/api/json | jq -r .crumb) if [ -z $CRUMB ] || [ $CRUMB null ]; then echo ERROR: Failed to get crumb from Jenkins exit 1 fi # Step 2: 使用 crumb 触发构建 curl -s -X POST -m 30 -u $JENKINS_USER:$JENKINS_TOKEN \ -H Jenkins-Crumb: $CRUMB \ $JENKINS_URL/job/$JOB_NAME/build?delay0sec # 检查返回状态码 if [ $? -eq 0 ]; then echo Build triggered successfully else echo ERROR: Build trigger failed exit 1 fiGitLab Webhook 配置要点在 GitLab 项目的 Settings Webhooks 中URL 填写https://your-jenkins-domain.com/gitlab-webhook/post注意这是 GitLab Plugin 的专用 endpoint它内部已集成 crumb 处理。关键点在于不要用通用的/job/xxx/build而要用插件提供的/gitlab-webhook/post。这个插件会自动处理 crumb 获取和校验你只需要确保 Jenkins 已安装 GitLab Plugin 并在系统配置中填入 GitLab 的 URL 和 Private Token。为什么不用通用 API因为/job/xxx/build是 Jenkins Core 的通用接口它严格遵循 crumb 规则而/gitlab-webhook/post是 GitLab Plugin 提供的“白名单接口”它通过RequirePOST注解和自定义 Filter在内部完成了 crumb 的透明处理。这是官方推荐的、最省心的集成方式。3.4 断点四Jenkins 配置中未启用 Crumb Issuer 或配置错误最后也是最基础的一环确认 Jenkins 的 crumb 机制本身是否已启用。虽然 LTS 版本默认开启但在某些定制镜像或离线安装场景下它可能被意外关闭。检查与启用步骤UI 操作以管理员身份登录 Jenkins进入Manage Jenkins Configure Global Security滚动到CSRF Protection区域确保Enable proxy compatibility已勾选这是为反向代理环境准备的兼容模式在Crumb Issuer下拉菜单中选择Default Crumb Issuer不要选 “No Crumb Issuer”点击Save。高级配置system.properties如果你需要调整 crumb 的有效期或存储方式可以在 Jenkins 容器的/usr/share/jenkins/ref/system.properties文件中添加# 将 crumb 有效期从默认 300 秒5分钟延长至 600 秒10分钟 jenkins.security.csrf.CrumbIssuer.EXPIRY_SECONDS600 # 如果使用 Redis 集群做 crumb 存储适用于多节点 Jenkins jenkins.security.csrf.RedisCrumbIssuer.REDIS_HOSTredis-service jenkins.security.csrf.RedisCrumbIssuer.REDIS_PORT6379为什么Enable proxy compatibility如此重要这个选项会告诉 Jenkins“我前面有一个反向代理请相信X-Forwarded-*系列 Header”。如果不勾选Jenkins 会忽略所有X-ForwardedHeader坚持用自己的request.getRemoteAddr()获取客户端 IP这在容器网络中通常是10.42.x.x这样的内网地址导致 crumb 绑定的 IP 与实际请求 IP 不一致校验失败。4. 如果真要关闭怎么关得安全、可控、可审计我必须再次强调生产环境禁用 CSRF 是高危操作应作为最后手段且必须配套严格的补偿措施。但现实是有些遗留系统、POC 环境或特定集成场景比如某些老旧的自动化测试框架确实需要临时关闭。此时正确的做法不是“一刀切”而是“精准外科手术”。4.1 方案一仅对特定 URL Pattern 豁免推荐这是最安全、最符合最小权限原则的方式。Jenkins 允许你通过hudson.security.csrf.CrumbExclusion的子类为特定路径排除 crumb 校验。例如你想让/gitlab-webhook/和/prometheus-alert/这两个路径无需 crumb可以编写一个自定义插件或者更简单——使用 Jenkins 的Script Console仅限管理员动态注册豁免规则。Groovy 脚本在 Manage Jenkins Script Console 中执行import jenkins.security.csrf.CrumbExclusion import hudson.model.Hudson // 创建一个豁免规则针对所有以 /gitlab-webhook/ 开头的请求 class GitLabWebhookExclusion extends CrumbExclusion { Override boolean process(HttpServletRequest request, HttpServletResponse response) { return request.getRequestURI().startsWith(/gitlab-webhook/) } } // 创建另一个豁免规则针对 Prometheus 告警 class PrometheusAlertExclusion extends CrumbExclusion { Override boolean process(HttpServletRequest request, HttpServletResponse response) { return request.getRequestURI().startsWith(/prometheus-alert/) } } // 注册到 Jenkins Hudson.instance.getSecurityRealm().getCrumbIssuer().addExclusion(new GitLabWebhookExclusion()) Hudson.instance.getSecurityRealm().getCrumbIssuer().addExclusion(new PrometheusAlertExclusion()) println Crumb exclusions added for /gitlab-webhook/ and /prometheus-alert/优势影响范围极小只放开指定路径不影响 Jenkins 其他所有功能如 Job 配置、用户管理、插件安装的 crumb 保护可随时通过 Script Console 执行反向脚本移除不留痕迹。4.2 方案二通过 JVM 参数临时关闭仅限调试如果你只是想快速验证某个问题是否由 crumb 导致可以用 JVM 参数临时关闭。注意这必须在 Jenkins 启动时设置且重启后失效。Docker Compose 中的配置services: jenkins: image: jenkins/jenkins:lts environment: - JAVA_OPTS-Djenkins.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTIONtrue # ... 其他配置Kubernetes Deployment 中的配置env: - name: JAVA_OPTS value: -Djenkins.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTIONtrue重要警告此参数在 Jenkins 2.387 版本中已被移除使用它会导致 Jenkins 启动失败即使在旧版本中它也会全局禁用所有 crumb 校验风险极高绝对禁止在生产环境使用此方案仅限本地开发机或离线测试环境。4.3 方案三使用 Jenkins CLI 替代 REST API规避 crumb对于需要脚本化操作的场景如批量创建 Job、修改配置与其硬刚 crumb不如换一条路使用 Jenkins 自带的jenkins-cli.jar。它通过 SSH 协议通信完全绕过 HTTP 层的 crumb 校验且安全性由 SSH 密钥保障。实操步骤在 Jenkins 系统配置中启用JNLP Agent Protocol和SSH Agent Protocol下载jenkins-cli.jarcurl -O https://your-jenkins-domain.com/jnlpJars/jenkins-cli.jar生成 SSH 密钥对并将公钥添加到 Jenkins 用户的 SSH Keys 中执行命令java -jar jenkins-cli.jar -s https://your-jenkins-domain.com/ -i ~/.ssh/id_rsa list-jobs为什么 CLI 更安全因为 SSH 协议本身就有强身份认证密钥对、加密传输、会话管理它不需要 crumb 这种 HTTP 层的“二次校验”。对于运维脚本来说CLI 是比 REST API 更底层、更可靠的选择。5. 常见问题速查表与独家避坑心得在帮客户处理 crumb 问题的过程中我整理了一份高频问题速查表。这些问题99% 的人都会遇到但 90% 的人会花数小时在错误的方向上排查。下面是我用血泪经验总结的“直击要害”指南。问题现象根本原因快速验证方法一招解决curl -X POST /job/my-job/build返回 403但curl /crumbIssuer/api/json能拿到 crumb调用脚本没把 crumb 放到 Header而是放到了 Body 或 Query 参数curl -v -H Jenkins-Crumb: your-crumb-value -X POST http://url/job/my-job/build确保-H Jenkins-Crumb: $CRUMB与-X POST在同一行且 crumb 值无空格Jenkins 日志显示Invalid crumb: ...但 crumb 值看起来是有效的Jenkins 容器和反向代理容器的时区不一致导致时间戳校验失败在 Jenkins 容器内执行date在 Nginx 容器内执行date对比挂载/etc/localtime到 Jenkins 容器docker run -v /etc/localtime:/etc/localtime:roGitLab Webhook 触发失败但手动在 Jenkins UI 点击“Build Now”能成功GitLab Plugin 未安装或 Webhook URL 写成了/job/xxx/build而非/gitlab-webhook/post查看 Jenkins 系统日志搜索gitlab检查 GitLab Webhook 的响应体卸载重装 GitLab PluginWebhook URL 必须是/gitlab-webhook/postJenkins 启动后首次访问crumbIssuer/api/json返回 404Jenkins 还没完全初始化好crumb issuer 模块尚未加载等待 2-3 分钟后再试或查看 Jenkins 启动日志末尾是否有CrumbIssuerImpl初始化成功日志在docker-compose.yml中为 Jenkins 服务添加healthcheck确保依赖服务就绪后再启动使用 Kubernetes Ingress 访问 Jenkinscrumb 总是失效Ingress Controller如 Nginx Ingress默认不透传X-Forwarded-*Headerkubectl get ingress jenkins -o yaml检查annotations是否包含nginx.ingress.kubernetes.io/configuration-snippet添加 annotationnginx.ingress.kubernetes.io/configuration-snippet:我的独家避坑心得永远不要在system.properties里写DISABLE_CSRF_PROTECTIONtrue。我见过太多团队因为一个临时的 POC 环境写了这行结果镜像被误推到测试环境导致整个 QA 团队的自动化回归测试全部失败排查了三天才发现是 crumb 被全局关闭。正确的做法是用CrumbExclusion脚本只豁免你需要的路径。X-Forwarded-Proto是灵魂 Header。在所有反向代理配置中这一行必须存在且值必须准确。我曾经在一个客户的阿里云 SLB 后面部署 JenkinsSLB 默认不透传这个 Header导致 crumb 一直绑定http协议而浏览器访问的是https校验必败。解决方案是在 SLB 的监听规则中手动添加X-Forwarded-Proto: https的固定响应头。crumb 的 Header 名不是固定的。Jenkins 默认用Jenkins-Crumb但你可以在Configure Global Security页面里修改成X-Jenkins-Crumb或其他名字。如果你的 CI 脚本里硬编码了Jenkins-Crumb而 Jenkins 管理员把它改成了X-Jenkins-Crumb那脚本就会永远失败。最佳实践是在获取 crumb 的响应体里读取crumbRequestField字段的值动态设置 Header 名。例如CRUMB_FIELD$(curl -s $JENKINS_URL/crumbIssuer/api/json | jq -r .crumbRequestField) CRUMB_VALUE$(curl -s $JENKINS_URL/crumbIssuer/api/json | jq -r .crumb) curl -H $CRUMB_FIELD: $CRUMB_VALUE -X POST $JENKINS_URL/job/my-job/build容器健康检查要包含 crumb 可用性。在docker-compose.yml或 Kubernetes 的livenessProbe中不要只检查HTTP 200而要检查 crumb 是否能正常签发livenessProbe: httpGet: path: /crumbIssuer/api/json port: 8080 initialDelaySeconds: 60 periodSeconds: 30这样一旦 crumb 机制崩溃K8s 会自动重启 Jenkins Pod避免服务“活着但不能用”的尴尬状态。日志是唯一真相。当一切都不明了时打开 Jenkins 的System LogManage Jenkins System Log Add new log recorder添加hudson.security.csrf这个 logger级别设为ALL。你会看到每一行 crumb 的签发、验证、失败的详细过程包括它绑定的 IP、协议、时间戳。这是我解决 80% 棘手问题的终极武器。最后分享一个小技巧如果你用的是 Jenkins Pipeline可以在Jenkinsfile的options块里为整个流水线设置一个 crumb 获取的共享变量避免每个sh步骤都重复调用pipeline { agent any options { timeout(time: 1, unit: HOURS) } environment { JENKINS_CRUMB sh( script: curl -s -u $JENKINS_USER:$JENKINS_TOKEN $JENKINS_URL/crumbIssuer/api/json | jq -r .crumb, returnStdout: true ).trim() } stages { stage(Deploy) { steps { sh curl -H Jenkins-Crumb: ${env.JENKINS_CRUMB} -X POST $JENKINS_URL/job/deploy-prod/build } } } }这个environment块会在流水线启动时执行一次 crumb 获取后续所有sh步骤都能复用既高效又安全。
返回列表