Jenkins 密钥管理:如何在不泄露凭据的情况下进行管理

  • infisical
  • 发布于 2026-02-07 15:33
  • 阅读 89

文章围绕 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 2GDPRHIPAA 等合规要求对完整审计追踪、加密标准和访问控制提出更高要求,这一点变得尤为关键。

虽然 Jenkins 通过 Credentials 和 Credentials Binding 插件提供了原生的凭证管理能力,但像 Infisical 这样的现代 secrets management 平台提供了更高级的功能。

在本指南中,我们将探讨这两种方式及其取舍,并帮助你为自己的 CI/CD 安全需求 实施合适的解决方案。

理解 Jenkins Credentials

Jenkins’ Credentials Plugin 是用于管理敏感数据的内置方案。它将 credentials 以加密格式存储在 Jenkins controller 上,使其可供作业和流水线使用,同时避免它们出现在日志和控制台输出中。

Jenkins credentials 最常见的作用域是 System(仅 Jenkins 可用)或 Global(对作业和流水线可用)。在许多基于文件夹的配置中,团队还会使用文件夹级凭证存储,将 credentials 限定在某个文件夹及其子作业中。

该插件支持多种凭证类型:

  • Secret text:API Token和 webhook secrets
  • Username and password:基础认证凭证
  • Secret file:证书、许可证密钥、配置文件
  • SSH Username with private key:用于 Git 操作和 SSH 连接
  • Certificate:用于双向 TLS 的 PKCS #12 证书

创建和管理 Jenkins Secrets

在 Jenkins 中创建和管理 secrets 需要几个步骤。

通过 Jenkins UI 添加 Credentials

  1. 导航到 Manage Jenkins → Credentials
  2. 选择 System → Global credentials (unrestricted)
  3. 点击 Add Credentials
  4. 选择凭证类型和作用域
  5. 输入唯一 ID(例如:dockerhub-prod-credentials
  6. 填写凭证详情
  7. 点击 OK 保存

在 Freestyle Jobs 中使用 Credentials

对于 freestyle 项目,credentials 通过 Build Environment 注入:

  1. 在作业配置中,勾选 “Use secret text(s) or file(s)”
  2. 为 credentials 添加绑定
  3. 指定环境变量名称

在构建期间,这些 credentials 会作为环境变量提供。

在 Pipeline Jobs 中使用 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
                    '''
                }
            }
        }
    }
}

在单个 Stage 中使用多个 Credentials

当你需要同时使用多个 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
            '''
        }
    }
}

将 Credentials 作为代码管理

对于践行基础设施即代码的团队,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 解决了基本的存储需求,但它在运维和安全方面仍存在显著局限。

安全漏洞

  • 按设计可解密:任何 Jenkins 管理员都可以使用 Script Console 解密所有 credentials。
  • 主密钥暴露:加密密钥以明文形式存储在文件系统中。
  • 缺乏权限隔离:拥有作业配置访问权限的用户可以通过修改流水线提取 secrets。

注意:虽然像 Matrix Authorization 和 Folder-based Authorization 这样的 Role-Based Access Control 插件可以限制谁能修改作业和访问 Script Console,但它们无法阻止那些确实需要作业配置访问权限的用户提取 credentials。RBAC 有助于限制暴露范围,但并不能解决根本的加密弱点。

额外的运维挑战

除了让 secrets 更容易泄露之外,Jenkins 还缺少健全的 secrets management 所必需的功能:

  • 手动轮换负担:没有自动凭证轮换能力。每次更新都需要对所有受影响的作业进行手动干预。
  • 没有审计追踪:Jenkins 不会记录谁访问了 secrets、何时使用它们,或进行了哪些更改。
  • 团队协作摩擦:没有安全的方式在团队成员或 Jenkins 实例之间共享 vault 密码。
  • 仅支持静态 secrets:无法生成临时的、自动过期的 credentials 以增强安全性。
  • 规模复杂性:在多个 Jenkins 实例之间管理 credentials 会变成一场同步噩梦。

这些缺口会让满足常见合规期望变得更加困难,例如 SOC 2、HIPAA、PCI DSS 和 GDPR。对于生产环境,尤其是受监管行业而言,这些限制使原生 Jenkins credentials 并不适用。

现代替代方案:Infisical

鉴于这些限制,许多团队正在转向专用的 secrets management 平台。对于需要企业级 secrets management 的团队,Infisical 提供了一个 原生 Jenkins 插件,在保持易用性的同时解决了这些限制。

主要优势

在 Jenkins 中使用 Infisical 可提供:

  • 零停机自动轮换
  • 跨所有平台和工具的集中管理
  • 满足合规要求的完整审计日志
  • 会自动过期的动态 secrets
  • 基于角色权限的细粒度访问控制

第 1 步:安装 Infisical Plugin

通过 Jenkins Plugin Manager 安装该插件:

  1. 导航到 Manage Jenkins → Plugins → Available plugins
  2. 搜索 “Infisical”
  3. 安装插件并重启 Jenkins

或者,也可以通过 Jenkins CLI 安装该插件:

jenkins-plugin-cli --plugins infisical:29.va_e0dc5ca_b_8fa_

第 2 步:配置 Machine Identity

在 Infisical 中创建一个 machine identity 供 Jenkins 认证使用:

  1. 登录 Infisical Dashboard
  2. 导航到 Project Settings → Machine Identities
  3. 使用 Universal Auth 创建一个新 identity
  4. 复制 Client ID 和 Client Secret

将这些 credentials 添加到 Jenkins:

  1. 前往 Manage Jenkins → Credentials → System → Global credentials
  • 原文链接: infisical.com/blog/jenki...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
infisical
infisical
江湖只有他的大名,没有他的介绍。