图SMS凭据管理系统与Jenkins Pipeline的集成架构动态凭据注入替代硬编码密码痛点复现Jenkins的凭据管理有多裸奔翻开任何一个运行超过1年的Jenkins实例大概率能找到这样的Pipeline代码// ❌ 典型的Jenkins Pipeline中的硬编码密码pipeline{agent any stages{stage(Deploy to Database){steps{sh mysql -h 192.168.1.100 -u root -pMyS3cretPss \ -e CREATE DATABASE IF NOT EXISTS app_production }}}}或者在application-prod.yml中# ❌ 配置文件里的明文密码spring:datasource:url:jdbc:mysql://192.168.1.100:3306/app_productionusername:rootpassword:MyS3cretPss# 直接写在Git仓库里redis:host:192.168.1.101password:R3dis2024# 也在Git仓库里这有多危险根据GitHub Secret Scanning的公开数据2024年扫描到的泄露密钥中密钥类型占比典型泄露位置数据库密码28%配置文件、Pipeline脚本AWS/云平台AK/SK22%环境变量、DockerfileAPI Key第三方19%源代码、.env文件SSH私钥15%仓库中的deploy脚本JWT Secret8%配置文件其他8%日志、文档一旦这些密码进了Git仓库即使你后续删除历史记录里依然有。更可怕的是这些密码往往几个月甚至几年不换给攻击者充足的利用窗口。Jenkins原生凭据管理的问题Jenkins确实提供了Credentials插件但它的安全性存在明显短板Jenkins Credentials Store 的安全链路 存储位置$JENKINS_HOME/credentials.xml默认 加密方式Master Key 加密 Master Key 存储位置$JENKINS_HOME/secrets/master.key同一台服务器上 问题 1. Master Key和加密后的凭据在同一台服务器 → 攻破Jenkins就全有了 2. 凭据更新需要手动操作 → 密码轮换困难 3. 没有细粒度权限控制 → 任何有Pipeline权限的人都能读取 4. 没有审计日志 → 谁在什么时候用了哪个密码查不到 5. 不支持动态凭据 → 密码是静态的不会自动轮转解决方案SMS凭据管理系统SMSSecret Management System是一种企业级凭据管理平台核心理念是密码不落盘到业务系统Jenkins从SMS动态拉取密码用完即废自动轮转密码定期自动更换业务无感知审计追踪谁、在什么时候、用了哪个密码全程记录细粒度权限不同Job只能访问对应的凭据不能越权架构对比改造前静态密码 ┌─────────┐ 明文密码 ┌─────────────┐ │ Jenkins │ ──────────────→ │ MySQL │ │ Pipeline │ (硬编码在代码中) │ 数据库 │ └─────────┘ └─────────────┘ 改造后动态凭据 ┌─────────┐ ①请求凭据 ┌───────────────┐ │ Jenkins │ ────────────→ │ SMS凭据管理 │ │ Pipeline │ ←──────────── │ 系统 │ └────┬─────┘ ②返回临时凭据 └───────┬───────┘ │ │ │ ③使用临时密码连接 │ │ ───────────────────────────→│ │ ▼ │ ┌─────────────┐ └──────────────────────→│ MySQL │ │ 数据库 │ └─────────────┘ ↑ ④ SMS定期自动轮转密码 Jenkins无感知3步接入从零到动态凭据注入Step 1部署SMS服务端# 1. 下载并安装SMS服务端# 要求Linux服务器CentOS 7/Ubuntu 20.044核8G# CentOSsudoyuminstall-ysms-server-2.8.0-1.x86_64.rpm# Ubuntusudodpkg-isms-server_2.8.0_amd64.deb# 2. 初始化配置sudosms-cli init\--admin-passwordAdmin2024Secure\--db-type mysql\--db-host127.0.0.1:3306\--db-name sms_vault\--db-user sms\--db-passwordSmsDB2024# 3. 启动服务sudosystemctlenablesms-serversudosystemctl start sms-serversudosystemctl status sms-server# 4. 验证服务状态curl-shttps://localhost:8443/api/v1/health\-HAuthorization: Bearer$(cat/etc/sms/admin-token)\|python3-mjson.tool# 输出# {# status: running,# version: 2.8.0,# uptime: 120,# backend: mysql# }Step 2配置Jenkins Credential Provider插件# 1. 安装SMS Jenkins插件# Jenkins管理界面 → 插件管理 → 可用插件 → 搜索 SMS Credential Provider → 安装# 或者通过CLI安装java-jarjenkins-cli.jar-shttp://localhost:8080/\install-plugin sms-credential-provider# 2. 重启Jenkinsjava-jarjenkins-cli.jar-shttp://localhost:8080/ safe-restart# 3. 配置SMS连接# Jenkins管理界面 → 系统配置 → SMS Credential Provider# 填写# SMS Server URL: https://sms.yourcompany.com:8443# API Token: 从SMS管理控制台获取# Default Timeout: 30s# Retry Count: 3配置完成后在Jenkins的凭据管理界面就能看到SMS作为新的凭据提供者。Step 3在Pipeline中使用动态凭据这是核心部分。先在SMS中创建凭据然后在Pipeline中引用。在SMS中创建凭据# 通过SMS CLI创建数据库凭据sms-cli secret create\--namemysql-app-production\--typedatabase\--metadata{ host: 192.168.1.100, port: 3306, database: app_production, username: app_user }\--rotation30d\--description生产环境应用数据库连接凭据# 通过SMS CLI创建API Keysms-cli secret create\--namethird-party-api-key\--typeapi-key\--metadata{ service: payment-gateway, key_length: 32 }\--rotation90d\--description第三方支付网关API密钥# 查看已创建的凭据列表sms-cli secret list# ID 名称 类型 轮转周期# sec-a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxx mysql-app-production database 30d# sec-e5f6g7h8-xxxx-xxxx-xxxx-xxxxxxxx third-party-api-key api-key 90d完整Pipeline代码4个实战场景场景一数据库密码动态注入pipeline{agent any environment{// SMS凭据ID自动从SMS拉取临时密码SMS_CRED_IDsec-a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxx}stages{stage(拉取数据库凭据){steps{script{// ① 从SMS动态拉取凭据返回临时凭据有效期默认1小时defcredssms.getCredentials(${SMS_CRED_ID})env.DB_HOSTcreds.metadata.host env.DB_PORTcreds.metadata.port env.DB_NAMEcreds.metadata.database env.DB_USERcreds.metadata.username env.DB_PASScreds.password// SMS返回的临时密码echo✅ 凭据拉取成功有效期至:${creds.expiresAt}}}}stage(数据库迁移){steps{sh # ② 使用临时密码连接数据库执行迁移 mysql -h ${DB_HOST} -P ${DB_PORT} -u ${DB_USER} -p${DB_PASS} \ ${DB_NAME} sql/migrations/V2.1__add_user_table.sql }}stage(运行集成测试){steps{sh # 测试环境也用动态凭据 mvn test -Dspring.datasource.urljdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME} \ -Dspring.datasource.username${DB_USER} \ -Dspring.datasource.password${DB_PASS} }}}post{always{// ③ Pipeline结束后临时凭据自动失效// 即使密码泄露也已过期无法使用echoPipeline完成临时凭据已失效}}}场景二SSH远程部署pipeline{agent any stages{stage(部署到生产服务器){steps{script{// 从SMS拉取SSH私钥defsshCredsms.getCredentials(sec-ssh-prod-server)writeFile file:deploy_key,text:sshCred.privateKey shchmod 600 deploy_key}sh # 使用动态SSH密钥进行部署 ssh -i deploy_key -o StrictHostKeyCheckingno \ deployprod-server-01 EOF cd /opt/myapp sudo docker-compose pull sudo docker-compose up -d sudo docker-compose ps EOF shrm -f deploy_key// 清理临时密钥文件}}}}场景三多环境配置pipeline{agent any parameters{choice(name:ENV,choices:[dev,staging,prod],description:部署环境)}stages{stage(获取环境凭据){steps{script{// 根据参数自动选择对应环境的凭据defcredIdsec-mysql-${params.ENV}defcredssms.getCredentials(credId)// 写入配置文件不进Git仓库writeFile file:application-secret.yml,text:\ spring: datasource: url: jdbc:mysql://${creds.metadata.host}:${creds.metadata.port}/${creds.metadata.database}username: \${creds.metadata.username} password: \${creds.password} .stripIndent()echo已为${params.ENV}环境加载凭据:${credId}}}}stage(构建并部署){steps{shmvn clean package -DskipTestsshjava -jar target/myapp.jar --spring.config.additional-locationapplication-secret.yml}}}}场景四自动轮转 零停机# 配置自动轮转策略sms-cli rotation create\--secret-idsec-a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxx\--schedule0 3 1 * *\# 每月1号凌晨3点--auto-applytrue\# 自动应用到目标数据库--notify-channeldingtalk\# 轮转后通知钉钉群--notify-webhookhttps://oapi.dingtalk.com/robot/send?access_tokenxxx# 轮换流程# 1. SMS生成新密码32位随机强密码# 2. SMS连接数据库执行 ALTER USER SET PASSWORD# 3. SMS更新存储的新密码# 4. 下次Jenkins Pipeline拉取的就是新密码# 5. 发送通知密码已轮转Jenkins无需任何改动# 查看轮转历史sms-cli rotationhistory--secret-idsec-a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxx# 时间 旧密码(掩码) 新密码(掩码) 状态# 2025-05-01 03:00 ****old1 ****new1 成功# 2025-04-01 03:00 ****old0 ****old1 成功# 2025-03-01 03:00 ****prev ****old0 成功硬编码 vs 动态凭据效果对比维度硬编码密码Jenkins CredentialsSMS动态凭据密码存储Git仓库/代码中Jenkins服务器SMS专用加密存储密码轮换手动改代码手动改Credential自动轮转30/60/90天泄露影响永久直到手动更换永久临时凭据1小时后失效权限控制代码可见即可用Jenkins账号级别按Job/项目级别审计日志❌ 无❌ 基础日志✅ 全操作审计多环境管理多份配置文件多个Credential一个凭据ID自动路由密码复杂度人工设置人工设置自动生成32位强密码集成Spring Boot应用启动时动态获取密码除了Jenkins Pipeline后端应用本身也可以直接接入SMS彻底消除配置文件中的明文密码// SMS Java SDK 集成示例// Maven依赖// dependency// groupIdcom.example/groupId// artifactIdsms-spring-boot-starter/artifactId// version2.8.0/version// /dependencySpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}}// 配置文件 application.yml不再有明文密码// sms:// server-url: https://sms.yourcompany.com:8443// api-token: ${SMS_API_TOKEN} # 从环境变量读取不落盘//// spring:// datasource:// url: jdbc:mysql://${sms.secret.mysql-app-production.host}:${sms.secret.mysql-app-production.port}/app// username: ${sms.secret.mysql-app-production.username}// password: ${sms.secret.mysql-app-production.password}// 自定义属性解析器从SMS动态获取密码ComponentpublicclassSmsPropertyResolverimplementsEnvironmentPostProcessor{OverridepublicvoidpostProcessEnvironment(ConfigurableEnvironmentenvironment,SpringApplicationapplication){// 拦截 sms.secret.xxx 格式的配置项// 自动从SMS服务端拉取对应凭据MapString,ObjectsmsPropertiesnewHashMap();// 拉取数据库凭据SmsClientclientSmsClient.create(environment);SecretdbCredclient.getSecret(mysql-app-production);smsProperties.put(spring.datasource.url,String.format(jdbc:mysql://%s:%s/%s,dbCred.getMetadata(host),dbCred.getMetadata(port),dbCred.getMetadata(database)));smsProperties.put(spring.datasource.username,dbCred.getMetadata(username));smsProperties.put(spring.datasource.password,dbCred.getPassword());// 注入Spring环境environment.getPropertySources().addFirst(newMapPropertySource(smsCredentials,smsProperties));}}这样配置文件里再也看不到任何明文密码了。密码从SMS服务端动态获取轮转后应用下次启动自动拿到新密码。实施Checklist在正式接入SMS之前建议按以下清单逐项检查□ 盘点Jenkins中所有Pipeline标记包含硬编码密码的Job □ 盘点Git仓库中的配置文件标记包含明文密码的文件 □ 统计需要接入SMS的凭据数量数据库、SSH、API Key等 □ 确认SMS服务端部署方案独立服务器/K8s/Docker □ 配置SMS高可用至少2节点 □ 配置SMS备份策略凭据数据每日备份 □ 安装Jenkins SMS插件 □ 先在1-2个非关键Job上试点 □ 验证Pipeline运行正常 □ 逐步迁移所有Job □ 清理历史凭据从Jenkins Credentials中删除 □ 配置密码自动轮转策略 □ 配置告警通知轮转失败、凭据访问异常 □ 编写应急SOPSMS宕机时的降级方案总结Jenkins Pipeline中的硬编码密码是企业DevSecOps链路中最常见也最容易被忽视的安全隐患。SMS凭据管理系统通过动态凭据注入 自动轮转 全量审计三位一体的方式从根本上解决了这个问题。核心收益安全性密码不再出现在代码和配置文件中即使代码泄露也不会暴露凭据合规性满足等保2.0密码管理和访问控制相关条款运维效率密码自动轮转告别手动改密码通知所有相关方审计能力谁用了什么密码、什么时候用的全程可追溯如果你还在用Jenkins的Credentials Store甚至硬编码密码管理你的生产环境凭据是时候认真考虑升级了。 话题讨论你们公司的Jenkins Pipeline里还有多少硬编码密码有没有过密码泄露的惊险经历欢迎评论区交流。