文章围绕 Jenkins 中的密钥管理展开,先介绍 Jenkins Credentials 插件的用法,包括凭据类型、在 Freestyle 和 Pipeline 中的注入方式,以及用 JCasC 做配置化管理;随后指出其核心问题在于凭据本质上仍可被高权限用户通过 Script Console 或文件系统解密,存在审计缺失、轮换困难、难以支持动态凭证等局限。

Jenkins 是领先的开源自动化服务器,在全球驱动着相当大一部分 CI/CD 流水线。其庞大的插件生态系统和灵活的 pipeline-as-code 方法,使其成为团队自动化构建、测试和部署流程的首选。但与其他 CI/CD 平台一样,Jenkins 也需要访问敏感凭证才能正常工作。
Secrets 是赋予你基础设施访问权限的敏感凭证:数据库密码、API 密钥、Docker 镜像仓库凭证以及云服务提供商Token。在 Jenkins 流水线中,这些 secrets 支持从拉取私有仓库代码到将应用部署到生产环境的各种操作。
挑战在于:在保持 Jenkins 所具备的自动化和灵活性的同时,确保这些 secrets 的安全。随着 SOC 2、GDPR 和 HIPAA 等合规要求对完整审计追踪、加密标准和访问控制提出更高要求,这一点变得尤为关键。
虽然 Jenkins 通过 Credentials 和 Credentials Binding 插件提供了原生的凭证管理能力,但像 Infisical 这样的现代 secrets management 平台提供了更高级的功能。
在本指南中,我们将探讨这两种方式及其取舍,并帮助你为自己的 CI/CD 安全需求 实施合适的解决方案。
Jenkins’ Credentials Plugin 是用于管理敏感数据的内置方案。它将 credentials 以加密格式存储在 Jenkins controller 上,使其可供作业和流水线使用,同时避免它们出现在日志和控制台输出中。
Jenkins credentials 最常见的作用域是 System(仅 Jenkins 可用)或 Global(对作业和流水线可用)。在许多基于文件夹的配置中,团队还会使用文件夹级凭证存储,将 credentials 限定在某个文件夹及其子作业中。
该插件支持多种凭证类型:
在 Jenkins 中创建和管理 secrets 需要几个步骤。
dockerhub-prod-credentials)对于 freestyle 项目,credentials 通过 Build Environment 注入:
在构建期间,这些 credentials 会作为环境变量提供。
Pipeline 集成更加灵活。要在 Jenkinsfile 中使用 credentials,请使用以下模式:
pipeline {
agent any
stages {
stage('Build and Push Docker Image') {
steps {
script {
withCredentials([
usernamePassword(
credentialsId: 'dockerhub-credentials',
passwordVariable: 'DOCKER_PASS',
usernameVariable: 'DOCKER_USER'
)
]) {
sh '''
echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
docker build -t myapp:${BUILD_NUMBER} .
docker push myapp:${BUILD_NUMBER}
'''
}
}
}
}
stage('Deploy to Production') {
steps {
withCredentials([
string(credentialsId: 'api-token', variable: 'API_TOKEN'),
file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')
]) {
sh '''
kubectl apply -f deployment.yaml
curl -H "Authorization: Bearer $API_TOKEN" \
-X POST https://api.example.com/deploy
'''
}
}
}
}
}
当你需要同时使用多个 credentials 时,可以在 withCredentials 数组中包含多个 string() 调用。
stage('Database Migration') {
steps {
withCredentials([
usernamePassword(
credentialsId: 'db-credentials',
passwordVariable: 'DB_PASS',
usernameVariable: 'DB_USER'
),
string(credentialsId: 'db-host', variable: 'DB_HOST'),
string(credentialsId: 'db-name', variable: 'DB_NAME')
]) {
sh '''
export DATABASE_URL="postgresql://$DB_USER:$DB_PASS@$DB_HOST/$DB_NAME"
npm run migrate
'''
}
}
}
对于践行基础设施即代码的团队,Jenkins Configuration as Code(JCasC)允许你在 YAML 中定义 credentials:
credentials:
system:
domainCredentials:
- credentials:
- usernamePassword:
id: "github-credentials"
username: "${GITHUB_USER}"
password: "${GITHUB_TOKEN}"
description: "GitHub credentials from environment"
虽然这提升了版本控制和可复现性,但底层的安全限制依然存在,credentials 仍然以 Jenkins 可解密的格式存储。
Jenkins 试图通过在日志中屏蔽 credentials 来保护 secrets,但这种保护并非万无一失。
// Jenkins masks credentials in logs
stage('Secure Operation') {
steps {
withCredentials([string(credentialsId: 'secret', variable: 'SECRET')]) {
sh 'echo $SECRET' // This will show as **** in logs
}
}
}
// But beware of encoding tricks that bypass masking
stage('Unsafe Example') {
steps {
withCredentials([string(credentialsId: 'secret', variable: 'SECRET')]) {
sh 'echo $SECRET | base64' // This reveals the encoded secret!
}
}
}
关于 Jenkins credentials,有一个令人不安的事实:它们并不是真正安全的。任何拥有 Script Console 访问权限的人(这是一项高权限能力)都可以在 Jenkins controller 运行时中执行 Groovy 代码并解密 credentials:
// Run this in Jenkins Script Console (Manage Jenkins → Script Console)
import hudson.util.Secret
import com.cloudbees.plugins.credentials.SystemCredentialsProvider
def credentials = SystemCredentialsProvider.getInstance().getCredentials()
credentials.each { cred ->
if (cred instanceof com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl) {
println("ID: ${cred.id}")
println("Username: ${cred.username}")
println("Password: ${cred.password.plainText}")
println("---")
}
}
Jenkins 将 credential 加密材料存储在 $JENKINS_HOME/secrets/ 下。如果攻击者获得该目录的文件系统访问权限(例如 master.key 和相关 secret 文件)以及加密后的 credential 值,就可以解密已存储的 credentials:
// Decrypt any credential if you have the encrypted value
encryptedPassword = '{AQAAABAAAAAQom3LN7ei0wdm9cdOlGOa4GxDHzpndn0BUPeI4biARto=}'
password = hudson.util.Secret.decrypt(encryptedPassword)
println(password) // Outputs the plain text password
虽然 Jenkins’ Credentials Plugin 解决了基本的存储需求,但它在运维和安全方面仍存在显著局限。
注意:虽然像 Matrix Authorization 和 Folder-based Authorization 这样的 Role-Based Access Control 插件可以限制谁能修改作业和访问 Script Console,但它们无法阻止那些确实需要作业配置访问权限的用户提取 credentials。RBAC 有助于限制暴露范围,但并不能解决根本的加密弱点。
除了让 secrets 更容易泄露之外,Jenkins 还缺少健全的 secrets management 所必需的功能:
这些缺口会让满足常见合规期望变得更加困难,例如 SOC 2、HIPAA、PCI DSS 和 GDPR。对于生产环境,尤其是受监管行业而言,这些限制使原生 Jenkins credentials 并不适用。
鉴于这些限制,许多团队正在转向专用的 secrets management 平台。对于需要企业级 secrets management 的团队,Infisical 提供了一个 原生 Jenkins 插件,在保持易用性的同时解决了这些限制。
在 Jenkins 中使用 Infisical 可提供:
通过 Jenkins Plugin Manager 安装该插件:
或者,也可以通过 Jenkins CLI 安装该插件:
jenkins-plugin-cli --plugins infisical:29.va_e0dc5ca_b_8fa_
在 Infisical 中创建一个 machine identity 供 Jenkins 认证使用:
将这些 credentials 添加到 Jenkins:
- 原文链接: infisical.com/blog/jenki...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!
作者暂未设置收款二维码