访问和转储Jenkins凭证

大多数管道都要求使用机密来对某些外部资源进行身份验证。
所有机密信息都应存在于我们的代码存储库之外,并应直接输入到管道中。
詹金斯(Jenkins)提供了一个凭证存储,我们可以在其中保存我们的秘密并以几种不同的方式访问它们。

为什么要转储凭证

詹金斯(Jenkins)在情报收集方面很容易被选中。

为了提供最佳的顾问服务,我们经常需要客户可以提供给我们的所有信息。
通常,客户端向我们提供对代码库和基础结构的完全访问权限。

但是,有时候,我们想检查的东西暂时无法到达。 当然,我们可以请求访问权限,但可能需要一段时间。 也可能是没人知道如何访问资源的情况。

我们可以在詹金斯(Jenkinses)闲逛并自己寻找出路,从而可以稍微加快步伐。 如果我们被授予了书面上的完全使用权,那么这里就没有道德困境。

为了给人留下良好的第一印象,请詹金斯坦白。

通过请求访问权限,我们可能会被诸如“您为什么需要它?”之类的问题拖延。 或“我必须先与我的主管交谈。”
没有必要; 我们已经有了批准。 我们是顾问。

不过实话说。

授予詹金斯访问权限等同于查看其中存储的所有机密的许可证。
如果您不希望别人闲逛,请不要给他们任何访问您CI的权限。

您寻求的答案,詹金斯将泄漏。

有时,我们遇到不愿分享的实体。
可能有许多不同的原因。 但是,我们不做判断。 我们知道,事情发生了,必须按时完成。 我们需要的是全面了解情况。

我们不认识他们。 他们不认识我们; 但是,詹金斯没有选择双方。

加密,解密,Jenkins提供纯文本订阅。

即使您不需要从您自己的公司偷偷获得证书,使用Jenkins时也仍然需要了解这些漏洞。

凭证存储

在引言中的任何地方,我都没有使用“安全”一词,因为任何CI服务器存储凭证的方式本质上都是不安全的。

CI服务器不能使用单向哈希(如bcrypt)来编码机密信息,因为当管道请求时,这些机密信息需要恢复为原始形式。
然后就没有了单向散列,剩下的就是双向加密。 这意味着两件事:

  1. Jenkins加密静态信息,但将解密密钥保留在其主机上的某个位置。
  2. 可以在Jenkins上创建工作的任何人都可以用纯文本查看所有机密。

您可能想知道为什么詹金斯(Jenkins)如果仅通过询问就可以以纯格式检索秘密,为什么还要对这些秘密进行加密。 我对此没有很好的答案。

但是,使用Jenkins凭证存储绝对比在项目存储库中保密要好得多。 在本文的后面,我将讨论如何最大程度地减少詹金斯泄露的机密。

创建凭证

如果您想阅读这篇文章并自己运行示例,则可以在不到一分钟的时间内从我的jenkinsfile-examples存储库中启动一个预先配置的Jenkins实例(取决于您的Internet带宽):

git clone https://github.com/hoto/jenkinsfile-examples.git
docker-compose pull
docker-compose up

打开localhost:8080 ,您将在其中看到詹金斯夫妇的工作。

要浏览和添加机密,请点击Credentials
我的Jenkins实例已经具有我创建的一些预制凭据。

访问和转储Jenkins凭证_第1张图片

要添加秘密,请将鼠标悬停在(global)上方以显示▼符号,然后单击它。
选择Add credentials ,最后可以在其中添加机密。

访问和转储Jenkins凭证_第2张图片

如果需要,可以添加更多机密,但是我将使用现有机密。

我的建议是提供一个有意义的ID并为Description使用相同的值。
user不是一个好的IDgithub-rw-user更好。

访问和转储Jenkins凭证_第3张图片
访问和转储Jenkins凭证_第4张图片

既然我们已经介绍了创建凭据,那么让我们继续从Jenkinsfile访问它们。

从Jenkins文件访问凭证

我们将运行作业130-accessing-credentials

访问和转储Jenkins凭证_第5张图片

作业130-accessing-credentials具有以下Jenkinsfile :

pipeline {
  agent any
  stages {

    stage('usernamePassword') {
      steps {
        script {
          withCredentials([
            usernamePassword(credentialsId: 'gitlab',
              usernameVariable: 'username',
              passwordVariable: 'password')
          ]) {
            print 'username=' + username + 'password=' + password

            print 'username.collect { it }=' + username.collect { it }
            print 'password.collect { it }=' + password.collect { it }
          }
        }
      }
    }

    stage('usernameColonPassword') {
      steps {
        script {
          withCredentials([
            usernameColonPassword(
              credentialsId: 'gitlab',
              variable: 'userpass')
          ]) {
            print 'userpass=' + userpass
            print 'userpass.collect { it }=' + userpass.collect { it }
          }
        }
      }
    }

    stage('string (secret text)') {
      steps {
        script {
          withCredentials([
            string(
              credentialsId: 'joke-of-the-day',
              variable: 'joke')
          ]) {
            print 'joke=' + joke
            print 'joke.collect { it }=' + joke.collect { it }
          }
        }
      }
    }

    stage('sshUserPrivateKey') {
      steps {
        script {
          withCredentials([
            sshUserPrivateKey(
              credentialsId: 'production-bastion',
              keyFileVariable: 'keyFile',
              passphraseVariable: 'passphrase',
              usernameVariable: 'username')
          ]) {
            print 'keyFile=' + keyFile
            print 'passphrase=' + passphrase
            print 'username=' + username
            print 'keyFile.collect { it }=' + keyFile.collect { it }
            print 'passphrase.collect { it }=' + passphrase.collect { it }
            print 'username.collect { it }=' + username.collect { it }
            print 'keyFileContent=' + readFile(keyFile)
          }
        }
      }
    }

    stage('dockerCert') {
      steps {
        script {
          withCredentials([
            dockerCert(
              credentialsId: 'production-docker-ee-certificate',
              variable: 'DOCKER_CERT_PATH')
          ]) {
            print 'DOCKER_CERT_PATH=' + DOCKER_CERT_PATH
            print 'DOCKER_CERT_PATH.collect { it }=' + DOCKER_CERT_PATH.collect { it }
            print 'DOCKER_CERT_PATH/ca.pem=' + readFile("$DOCKER_CERT_PATH/ca.pem")
            print 'DOCKER_CERT_PATH/cert.pem=' + readFile("$DOCKER_CERT_PATH/cert.pem")
            print 'DOCKER_CERT_PATH/key.pem=' + readFile("$DOCKER_CERT_PATH/key.pem")
          }
        }
      }
    }

    stage('list credentials ids') {
      steps {
        script {
          sh 'cat $JENKINS_HOME/credentials.xml | grep ""'
        }
      }
    }

  }
}

有关不同类型机密的所有示例都可以在Jenkins官方文档中找到 。

访问和转储Jenkins凭证_第6张图片

运行作业并检查日志后,Jenkins会通过查找密钥值并将其替换为星号****来尝试编辑构建日志中的密钥。
如果我们以一种简单的匹配和替换将不起作用的方式打印它们,便可以看到实际的秘密值。
这样,每个字符将单独打印,并且詹金斯不会编辑值。

示例代码:

print 'username.collect { it }=' + username.collect { it }

日志输出:

username.collect { it }=[g, i, t, l, a, b, a, d, m, i, n]
访问和转储Jenkins凭证_第7张图片

对Jenkins上建立的存储库具有写访问权的任何人都可以通过修改该存储库中的Jenkinsfile来发现所有Global凭证。

可以在詹金斯上创建工作的任何人都可以通过创建管道工作来发现所有Global机密。

列出机密ID

在要求詹金斯提供证书之前,您需要知道其ID。
您可以通过阅读$JENKINS_HOME/credentials.xml文件列出所有凭据ID。

码:

stage('list credentials ids') {
  steps {
    script {
      sh 'cat $JENKINS_HOME/credentials.xml | grep ""'
    }
  }
}

日志输出:

gitlab
production-bastion
joke-of-the-day
production-docker-ee-certificate

从UI访问

Jenkins具有两种类型的凭据: SystemGlobal

System凭证只能从Jenkins配置(例如插件)访问。

Global凭据与System相同,但也可以从Jenkins作业中访问。

访问和转储Jenkins凭证_第8张图片

使用浏览器检查工具获取凭证

根据定义,作业无法访问System凭证,但是我们可以从Jenkins UI对其解密。
为此,您需要管理员权限。

Jenkins将每个机密的加密值发送到UI。
这是一个巨大的安全漏洞。

要获取加密的秘密:

  1. 导航到http://localhost:8080/credentials/
  2. 更新任何凭据。
  3. 打开开发者控制台(Chrome中为F12)。
  4. 检查虚线元素。
  5. 复制文字值value
访问和转储Jenkins凭证_第9张图片
访问和转储Jenkins凭证_第10张图片

在我的情况下,加密的机密是{AQAAABAAAAAgPT7JbBVgyWiivobt0CJEduLyP0lB3uyTj+D5WBvVk6jyG6BQFPYGN4Z3VJN2JLDm}

要解密任何凭据,我们可以使用需要管理员特权才能访问的Jenkins控制台。

如果您没有管理员权限,请尝试通过在Global凭据中查找管理员用户来提升权限。

要打开Script Console浏览至http://localhost:8080/script

告诉詹金斯解密并打印出秘密值:

println hudson.util.Secret.decrypt("{AQAAABAAAAAgPT7JbBVgyWiivobt0CJEduLyP0lB3uyTj+D5WBvVk6jyG6BQFPYGN4Z3VJN2JLDm}")
访问和转储Jenkins凭证_第11张图片

你有它; 现在,您可以解密任何Jenkins机密(如果您具有管理员权限)。

旁注:如果您尝试从Jenkinsfile作业运行此代码,则会收到错误消息:

Scripts not permitted to use staticMethod hudson.util.Secret decrypt java.lang.String. 
Administrators can decide whether to approve or reject this signature.

尽管大多数凭据存储在http://localhost:8080/credentials/视图中,但是您可以在以下位置找到其他机密:

  1. http://localhost:8080/configure –一些插件在此视图中创建密码类型字段
  2. http://localhost:8080/configureSecurity/ –查找类似AD凭证的内容

从控制台迭代和解密凭据

另一种方法是列出所有凭据,然后从控制台解密它们:

def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
    com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
    Jenkins.instance,
    null,
    null
)

for(c in creds) {
  if(c instanceof com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey){
    println(String.format("id=%s  desc=%s key=%s\n", c.id, c.description, c.privateKeySource.getPrivateKeys()))
  }
  if (c instanceof com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl){
    println(String.format("id=%s  desc=%s user=%s pass=%s\n", c.id, c.description, c.username, c.password))
  }
}

输出:

id=gitlab  desc=gitlabadmin user=gitlabadmin pass=Drmhze6EPcv0fN_81Bj
id=production-bastion  desc=production-bastion key=[-----BEGIN RSA PRIVATE KEY...

该脚本尚未完成。 您可以在Jenkins源代码中查找所有凭证类名称。

Jenkins如何存储凭证

要访问和解密Jenkins凭证,您需要三个文件。

  • credentials.xml –保存加密的凭据
  • hudson.util.Secret –解密credentials.xml hudson.util.Secret条目,此文件本身已加密
  • master.key –解密hudson.util.Secret

这三个文件都位于Jenkins主目录中:

$JENKINS_HOME/credentials.xml 
$JENKINS_HOME/secrets/master.key
$JENKINS_HOME/secrets/hudson.util.Secret

由于Jenkins是开源的,因此有人已经对加密和解密过程进行了反向工程。 如果您对这些细节感兴趣,请阅读这个迷人的博客 。

秘密被以加密credentials.xml使用AES-128hudson.util.Secret作为密钥,然后是base64编码。
hudson.util.Secret二进制文件使用master.key加密。
master.key以纯文本存储。

credentials.xml存储GlobalSystem凭据。
要直接访问和解密该文件,您不需要管理员权限。

解密和转储凭证

现有的工具可以解密詹金斯的机密。 我发现的那些是Python,像这样的一个 。
我将在此处包含源代码,但不幸的是,该存储库没有许可证。

Python加密模块未包含在python标准库中,它必须作为依赖项安装。 因为我不想处理python运行时和外部依赖关系,所以我在Go中编写了自己的解密器。
Go二进制文件是独立的,仅需要内核即可运行。

旁注:Jenkins使用的是AES-128-ECB算法,该算法未包含在Go标准库中。 该算法在2009年被故意排除在外,以阻止人们使用它。

我的jenkins-credentials-decryptor工具的Github存储库可在此处找到。
要查看运行中的二进制文件,运行作业131-dumping-credentials ,它使用以下Jenkinsfile:

pipeline {
  agent any
  stages {

    stage('Dump credentials') {
      steps {
        script {
           sh '''
             curl -L \
               "https://github.com/hoto/jenkins-credentials-decryptor/releases/download/0.0.5-alpha/jenkins-credentials-decryptor_0.0.5-alpha_$(uname -s)_$(uname -m)" \
                -o jenkins-credentials-decryptor

             chmod +x jenkins-credentials-decryptor

             ./jenkins-credentials-decryptor \
               -m $JENKINS_HOME/secrets/master.key \
               -s $JENKINS_HOME/secrets/hudson.util.Secret \
               -c $JENKINS_HOME/credentials.xml 
           '''
        }
      }
    }

  }
}
访问和转储Jenkins凭证_第12张图片
访问和转储Jenkins凭证_第13张图片

该工具也可以通过ssh在Jenkins主机上运行。 它只有〜6MB,可以在任何Linux发行版上使用。

通过使用jenkins-credentials-decryptor解密credentials.xml jenkins-credentials-decryptor ,我们可以打印GlobalSystem凭据的值,而无需管理员权限。

预防和最佳做法

我认为使用CI时,无法完全缓解安全漏洞。
我们只能通过设置层并创建移动目标来让攻击者获取我们的秘密,这只会花费更多时间。

1.将詹金斯隐藏在VPN后面

这是一个轻松的选择,也是我给使用Jenkins的任何人的第一建议。

通过在公共互联网中隐藏您的詹金斯来防止最基本的攻击。
我知道VPN很烦人,但是如今Internet连接是如此之快,而且响应Swift,您甚至都不会注意到。

2.定期更新詹金斯

詹金斯经常被留下数月甚至数年之久而没有更新。 旧版本充满已知漏洞和漏洞。 插件和操作系统也一样,请随时更新。 如果您担心更新,则每24小时设置一次Jenkins磁盘的自动备份。

3.遵循最小特权原则

如果只读访问权限已足够,则不要将凭据与读写访问权限一起使用。

4.限制访问范围

当管道仅需要访问资源的子集时,则仅为这些资源创建凭据,仅此而已。

5.尽可能避免使用秘密

如果我们从不首先创建秘密,它们就不会泄漏。
一些云提供商可以通过为计算机分配角色(例如,AWS IAM角色)来访问资源。

6.创建一个移动目标

与其将秘密存储在Jenkins上,不如将其存储在具有自动密码轮换的保管库中(例如,Hashicorp Vault,AWS Secrets Manager)。 使管道在每次需要时都调用保管库以访问机密。 这使得自动密码轮转非常容易设置,因为金库将是唯一的事实来源。

尽管密码轮换不能防止机密泄漏,但是机密值仅在有限的时间内有效。
这就是为什么我们称其为“创建移动目标”。

7.将存储在詹金斯中的所有凭据视为纯文本

将有权访问Jenkins的所有人都视为管理员用户,您会没事的。
一旦给某人访问(甚至是只读的)Jenkins的权限,游戏就结束了。
无论如何,项目中的所有开发人员都应该知道所有秘密。

8.期望您会被黑客入侵

如果您有值得偷的东西,有人会尝试偷它。

您可能会认为,如果有人偷走了您的源代码并转储了您的数据库,那就结束了,但这不一定是正确的。 例如,如果您的生产数据库已转储,但其中的客户秘密已被适当地单向散列,则可以大大减少损害。 在这种情况下,贵公司唯一失去的就是信誉。

翻译自: https://www.javacodegeeks.com/2019/06/accessing-dumping-jenkins-credentials.html

你可能感兴趣的:(python,java,数据库,大数据,编程语言)