• 使用 VAULT 加密的 GITLAB CICD Pipeline

    云和安全管理服务专家新钛云服 祝祥翻译

    本文的的核心内容是提供有关如何设置 Gitlab 和 Vault 以在 CI/CD 管道构建期间使用密钥。另外,本文将分解 JWT 授权过程,并解释 Gitlab + Vault 的流程。通过本文,读者最终可以实现自己的带有 Vault 密钥的 CI/CD 流程

    目的

    • 在 Vault 上启用 JWT 身份验证方法
    • 利用 JWT 授权从 Vault 中读取密钥以进行 Gitlab CI/CD 作业

    背景

    什么是 Gitlab?

    GitLab 是一个基于 Web 的 DevOps 生命周期工具 ,它提供了一个 Git 代码存储库管理,同时它使用 GitLab Inc 开发的开源许可证提供 wiki、问题跟踪以及持续集成和pipeline的功能。

    在学习Gitlab的定义之前,首先需要了解一些术语。可能您经常遇到像Git,Gitlab,GitHub和Bitbucket这样的术语。

    Git – 它是一个源代码版本控制系统,可让您在本地跟踪更改并从远程资源推送或提取更改。 GitLab ,GitHubBitbucket – 提供远程访问Git存储库的服务。除了托管代码之外,这些服务还提供用来帮助管理软件开发生命周期的附加功能。这些附加功能包括管理不同人之间的代码共享,错误跟踪,wiki空间和其他“社交编码”工具。

    • GitHub 是一项公开可用的免费服务,它要求所有代码(除非您有付费帐户)公开。任何人都可以看到您推送给GitHub的代码并提供改进建议。GitHub目前承载数以万计的开源项目的源代码。
    • GitLab是一种类似github的服务,组织可以使用它来提供git存储库的内部管理。它是一个自我托管的Git-repository管理系统,可以保持用户代码的私密性,并且可以轻松地部署代码的更改。

    GitLab是集中服务器上管理git存储库的一个好方法。GitLab让您可以完全控制您的存储库或项目,并允许您自己决定是公共还是私有。

    什么是HashiCorp Vault?

    在企业级应用开发过程中,团队每时每刻都需要管理各种各样的私密信息,从个人的登陆密码、到生产环境的SSH Key以及数据库登录信息、API认证信息等。通常的做法是将这些秘密信息保存在某个文件中,并且放置到git之类的源代码管理工具中。个人和应用可以通过拉取仓库来访问这些信息。但这种方式弊端很多,比如跨团队分享存在安全隐患、文件格式难以维护、私密信息难以回收等。

    尤其是微服务大行其道的今天,如何让开发者添加私密信息、应用程序能轻松的获取私密信息、采用不同策略更新私密信息、适时回收私密信息等变得越来越关键。所以企业需要一套统一的接口来处理私密信息的方方面面,而HashiCorp Vault就是这样的一款工具。

    Vault 是 hashicorp 推出的 secrets 管理、加密即服务与权限管理工具。

    它提供了这些功能:

    • 集中管理各种私密信息;
    • 为私密信息设置租期(Lease),到期后自动失效;
    • 密钥的动态生成、注销和滚动更新;
    • 动态创建无需保存的一次性登录密钥;
    • 作为数据加密/解密接口;
    • 完整的审计记录;
    • 命令行 以及 RESTful API 访问接口;

    · Valut存储私密信息

    不仅可以存放现有的私密信息,还可以动态生成用于管理第三方资源的私密信息。所有存放的数据都是加密的。任何动态生成的私密信息都有租期,并且到期会自动回收。

    · 滚动更新秘钥

    用户可以随时更新存放的私密信息。Vault提供了加密即服务(encryption-as-a-service)的功能,可以随时将密钥滚动到新的密钥版本,同时保留对使用过去密钥版本加密的值进行解密的能力。对于动态生成的秘密,可配置的最大租赁寿命确保密钥滚动易于实施。

    · 审计日志

    保管库存储所有经过身份验证的客户端交互的详细审核日志:身份验证,令牌创建,私密信息访问,私密信息撤销等。可以将审核日志发送到多个后端以确保冗余副本。

    另外,HaishiCorp Vault提供了多种方式来管理私密信息。用户可以通过命令行、HTTP API等集成到应用中来获取私密信息。HashiCorp Vault也能与Ansible、Chef、Consul等DevOps工具链无缝结合使用。

    什么是 CI/CD?

    CI/CD 自动化了将代码从开发人员机器交付到生产环境的过程。虽然只有短短数语,但如上图所示,该流程本质上包含很多步骤的。本节将详细描述该过程。CI/CD 代表持续集成和持续部署,正如上面缩写以及图所示,这是两个不同的阶段。持续集成是集成代码更改的过程,验证新代码更改仍然可以构建/编译应用程序,并确保新代码通过一组测试。

    缩略词 CI / CD 具有几个不同的含义。CI/CD 中的”CI”始终指持续集成,它属于开发人员的自动化流程。成功的 CI 意味着应用代码的新更改会定期构建、测试并合并到共享存储库中。该解决方案可以解决在一次开发中有太多应用分支,从而导致相互冲突的问题。

    CI/CD 中的”CD”指的是持续交付和/或持续部署,这些相关概念有时会交叉使用。两者都事关管道后续阶段的自动化,但它们有时也会单独使用,用于说明自动化程度。

    持续交付通常是指开发人员对应用的更改会自动进行错误测试并上传到存储库(如 GitHub 或Gitlab),然后由运维团队将其部署到实时生产环境中。这旨在解决开发和运维团队之间可见性及沟通较差的问题。因此,持续交付的目的就是确保尽可能减少部署新代码时所需的工作量。

    例如,假设您有一个用 GoLang 编写的 Web 应用程序。作为开发人员,您对本地开发机器上的现有应用程序进行一些更改,并将更改推送到 Gitlab。接下来,Gitlab 将尝试使用您的更改编译现有代码库。假设编译成功,Gitlab 将对新编译的代码进行多次测试,以确保应用程序按预期运行。如果测试成功,开发人员可以将更改合并到 MAIN 分支中。

    现在你可能会问如果这个阶段不成功会发生什么?使用上面的示例,假设您初始化了一个未使用的变量。如果您是 GoLang 开发人员,您已经知道这将无法编译,但对于本示例,假设代码已推送到 Gitlab。Gitlab 将再次尝试编译包含您的更改的代码。但是,编译将失败,并且通常管道将在第一次出现错误时停止运行。Gitlab 将为开发人员提供审查产生的错误的能力。在解决此问题之前,Gitlab 将不允许合并新代码。

    持续部署是再次评估/测试新提交的代码的过程,将应用程序推送给 QA 以进行进一步评估,最后在人工交互后将代码推送到生产环境。推送到产线(生产)意味着将您的代码推送到环境中,以便用户可以使用您的新代码。同样,正如上面的图表所示,此过程还有更多内容,但希望读者能够去了解更多的关于CICD的过程。

    了解 JWT 授权

    JSON Web Token (JWT)是目前最流行的跨域身份验证解决方案。简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令(token),用来代替密码,供第三方应用使用。

    传统的授权认证方式,需要持久化session数据,写入数据库或文件持久层等,且授权校验依赖于数据持久层。这样的方式,对于结构维护成本大,实现单点登录较为复杂,且没有分布式架构,无法支持横向扩展,风险较大(如果持久层失败,整个认证体系都会挂掉)。

    JWT则无须持久化会话数据,是以加密签名的方式实现了用户身份认证授权,很好的解决了跨域身份验证,分布式session共享、单点登录和横向扩展等问题。另外,需要注意JWT:

    1. JWT默认不加密,但可以加密。生成原始令牌后,可以使用改令牌再次对其进行加密。
    2. JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。
    3. JWT的最大缺点是服务器不保存会话状态,一旦JWT签发,在有效期内将会一直有效。
    4. JWT的有效期不宜设置太长,认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。
    5. 为了减少JWT数据盗用和窃取的风险,JWT建议使用加密的HTTPS协议进行传输。

    ^什么是 JWT,为什么要使用 JWT^

    (https://www.youtube.com/watch?v=7Q17ubqLfaM&t=1s)

    第 1 步:身份验证/授权

    JWT 代表 JSON 网络令牌。需要注意的是,JWT 用于授权而不是身份验证。所以你可能会问有什么区别???身份验证是当您第一次访问 Web 服务器并使用用户名和密码登录时。通过提供正确的用户名和密码,您将作为该用户进行身份验证。

    一旦您通过身份验证,用户就会被授予一个秘密。此密钥用于 Web 服务器的每个后续操作。因此,在下一个请求中,您会将您的密钥发送到 Web 服务器,它会验证该密钥是否有效,以及它是否有权执行请求的操作,即授权。然后让我们继续,JWT/OIDC Auth Method
    https://www.vaultproject.io/docs/auth/jwt)详细描述了该认证。

    第 2 步:生成 JWT

    身份验证成功后,将为用户生成一个 JWT 令牌并由服务器签名。此 JWT 被发送给用户以存储在其浏览器的缓存中。按照目前所描述的,您可能会认为这与当前使用的典型流程(即会话 ID)没有什么不同。

    JWT 和会话 ID 实现之间的最大区别是 JWT 不需要服务器记住/存储任何内容。使用会话 ID,服务器必须为所有未来的请求记住/存储该会话。JWT 包含有关用户的所有必要信息,以便服务器为所有未来请求验证 JWT 令牌。

    第 3 步:使用 JWT 进行授权

    JWT是一个base64编码的blob,包含授权用户所需的所有信息。如果解码JWT,它将包含上面截图中列出的部分,即标header,payload以及signature。header部分定义了类型(type),即JWT,以及使用的加密/解密算法(alg),即HS256。

    payload部分包含一个主题(子)键值对,该键值对通常是用户ID,iat代表发出,iat是生成令牌时的时间戳,最后,该部分可以包含与应用程序相关的任何数据(名称),例如用户名。此部分可以包含用户名的键值对。但是,提到的键值对是最常见的。最后,我们有验证部分,用于确保发送的 JWT 有效。

    1. Header: 用于说明签名的加密算法等,下面类型的json经过base64编码后得到JWT头部。

    { “typ”: “JWT”, “alg”: “HS256”}

    2. Payload: 标准定义了7个字段,载荷json经过base64编码后得到JWT的载荷。

    { iss (issuer):签发人 exp (expiration time):过期时间 sub (subject):主题 aud (audience):受众 nbf (Not Before):生效时间 iat (Issued At):签发时间 jti (JWT ID):编号}

    示例:

    { “sub”: “1”, “iss”: “http://localhost:8000/user/sign_up”, “iat”: 1451888119, “exp”: 1454516119, “nbf”: 1451888119, “jti”: “37c107e4609ddbcc9c096ea5ee76c667”}

    3.Signature: 将头部和载荷用’.’号连接,再加上一串密钥,经过头部声明的加密算法加密后得到签名。

    HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)

    了解Gitlab + Vault + JWT 授权

    HashiCorp Vault GitLab 集成:为什么以及如何?

    (https://www.youtube.com/watch?v=VmQZwfgp3aA)

    下面的步骤将对Gitlab,Vault,JWT CICD的流程进行详解。

    第 0 步:创建 Gitlab 项目/存储库

    正如我们将在下面的步骤说明中看到的,您需要首先创建一个Gitlab repo/project。Gitlab为repo生成的project id(下图所示)将用于指定 Vault 在该项目可以访问哪些密钥信息。

    第 1 步:配置 Vault 和密钥

    这听起来很直观,但要访问密钥,它们必须首先存在于 Vault 中。因此,用户必须首先创建一个 Vault 密钥路径并将项目的所有必要密钥放置在那里。接下来,必须创建一个Vault policy来授予对这些密钥的权限。

    正如您在下图中看到的那样,该策略允许读取和列出存储在以下路径中的密钥:secrets/gitlab/project_1

    最后,此策略必须附加到指定身份验证机制 (JWT) 和绑定声明(稍后会详细介绍)的 Vault 角色,该声明可以访问附加策略中定义的那些密钥。

    步骤 2a:设置 Gitlab pipeline

    接下来,我们需要配置一个 Gitlab CI/CD 管道,使用.gitlab-ci.yml配置。首先,这个文件是在项目 repo 中定义的。虽然此配置可能是各式各样的,但基本内容将如下图所示。首先在变量部分定义vault address、vault secret path和vault role。当 CI/CD 管道被触发时,Gitlab 将生成一个 JWT 作为以下环境变量传递给管道CI_JOB_JWT。

    在接下来的部分中,我们将深入探讨此配置的各个细节部分。但是,首先,此配置需求从 Vault 请求 Vault 令牌。接下来,如果请求成功,则返回一个 Vault 令牌,Gitlab 运行程序可以使用它来请求密钥。

    步骤 2b:生成 JWT

    正如在上一节中所述,当触发 CI/CD 管道时,会生成 JWT。让我们看一下这个 JSON 文档,看看它是如何组成的。通常,Gitlab JWT 将包括 Gitlab 服务器、执行管道作业的用户、Gitlab 项目 ID、Vault 密钥路径和 Gitlab 作业详细信息的信息。下图显示了一个生成并发送到 Vault 的示例 JWT 有效payload。

    第 3 步和第 4 步:向 Vault 进行身份验证 + Vault 验证 JWT

    在本节中,我们将把第 3 步(向 Vault 验证)和第 4 步(Vault 验证 JWT)合并为一个部分。当 Vault 收到来自 Gitlab 的 JWT payload以及对机密的请求时,它需要验证 JWT。在下面的分步说明中,我们将在 Vault 上启用 JWT 身份验证,这需要bounder_issuer以及要提供的 JKWS 端点。

    bound_issuer通常被设置为 Gitlab 的 FQDN,以指示 Vault“谁”可以使用此身份验证方法并设置一个指向JKWS端点的URL,从而来指示 Vault“如何”验证 JWT。因此,当 Vault 收到 JWT 请求时,它会确保请求者已被批准使用此身份验证方法,并且可以验证 JWT 的创建者。

    为了验证 JWT,Vault 会提取bound_issuer/iss字段,通常是来自 JWT payload的 Gitlab 的 FQDN。接下来,Vault 对提取的字段进行查找以获取该字段的 JWKS 端点bound_issuer。Vault 使用 JKWS 端点验证签名的 JWT payload,以确保 Gitlab 是请求的创建者。

    例如,案例中 Gitlab 实例的 FQDN:gitlab.hackinglab.local。 正如您将在下面的分步说明中看到的那样,我设置bound_issuer到gitlab.hackinglab.local。并且 JWKS 端点设置为jwks_url = ”
    https://gitlab.hackinglab.local/-/jwks”。

    那么,这意味着什么?这意味着当 Vault 收到请求访问密钥的 JWT 时,将提取iss应该匹配的字段bound_issuer提供给Vault。接下来,Vault 将通过将 JWT 签名与位于所提供的 JWKS 端点的签名进行比较来验证 JWT 签名。

    第 5 步:Vault检测边界声明和附加策略

    验证 JWT payload后,Vault 将提取试图被假定的 Vault 角色。下面的 Vault 角色声明它可以用于 JWT 身份验证,它具有gitlab-vault-readonly附加政策,以及bound_claims定义“谁”的附加参数的部分可以承担这个角色。

    bound_claims部分指出必须满足以下标准:只有 Gitlab project/repo project_id为2可以担任此角色,并且仅构建一个 CI/CD 管道master主可以承担这个角色。

    如果JWT有效负载中包含的bound_claims符合此标准,则可以担任此角色。最后,假设JWT包含此角色的必要条件,Vault将生成一个令牌。此Vault令牌将被授予担任此角色的能力。

    第 6 步和第 7 步:Vault 返回令牌 + Gitlab runner从 Vault 读取密钥

    在本节中,我将把第 6 步(Vault 返回令牌)和第 7 步(Gitlab runner从 Vault 读取密钥)合并为一个部分。在上一节中,生成了一个 Vault 令牌并将其返回给 Gitlab runner。

    Gitlab runner可以使用此 Vault 令牌从 Vault 请求密钥。如上所述,Vault 令牌被授予具有一组已定义策略的角色。当 Vault 收到访问密钥的请求时,它将检查附加到角色的策略,以确保它已被授予访问此密钥的权限。

    根据下图,附加到此角色的策略授予对以下路径的机密的 READ 和 LIST 权限:secrets/gitlab/project_1。那么这是什么意思?假设 Gitlab runner正在尝试读取以下密钥:
    secrets/gitlab/project_1/password,根据策略,它可以读取该密钥。

    但是,假设 Gitlab runner正在尝试读取以下密钥:
    secrets/mysql_db/super_secret_db_passwrd,根据策略,它没有读取该密钥的权限。

    前提条件

    • 为每个服务生成 DNS A 记录
    • 现有的 Gitlab 基础环境
    • 至少有一个 Gitlab runner
    • 预先部署好的 Vault服务

    创建一个 Gitlab 存储库

    正如我在上面的“假设”部分所述,这篇博文假设你有一个 Gitlab EE 实例正在运行

    1. 登录到 Gitlab
    2. 选择左上角的“项目”
    3. 选择“创建空白项目”
    4. 进入Vault Gitlab作为项目名称
    5. 选择“创建项目”
    6. 选择左上角的“项目概览”
    7. 记录项目ID

    设置Vault

    启用 JWT 身份验证

    1. 使用 CLI 工具登录 Vault
    2. curl -s -k -X GET https://gitlab.<base_domain>: 8443 ///jwks
    3. 测试 Gitlab JWT 身份验证
    4. 以管理员身份通过 CLI 登录 Vault
    5. echo | openssl s_client -connect gitlab.<base_domain>:<port> 2 >& 1 | sed – ne ‘/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p’ > /tmp/gitlab.crt
    6. 如果 Gitlab 由自签名证书提供服务,请下拉证书
    7. vault auth enable jwt
    8. vault write auth/jwt/config jwks_url= “https://gitlab.<base_domain>:<port>/-/jwks” bound_issuer= “gitlab.<base_domain>” jwks_ca_pem= “$( cat /tmp/gitlab.crt) “
    9. Vault read auth/jwt/config
    10. 测试 Vault JWT 身份验证

    创建密钥

    1. vault secrets enable -version=2 -path=secrets kv启用Vault服务
    2. vault kv put secrets/<secret path> <secret_key>=<secret_value>
    3. vault kv get secrets/gitlab/project_1取回密码

    设置 Vault 以提供 Gitlab 对密钥的访问权限

    • Vault Policy
    1. cd GitlabVaultCICD进入相关目录
    2. cp conf/vault/policies/gitlab-vault-policy.hcl.example conf/vault/policies/gitlab-vault-policy.hcl
    3. vim conf/vault/policies/gitlab-vault-policy.hcl并设置:
    4. {{ vault_secret_path }}将此设置为密钥所在的 Vault 路径
    5. vault policy write <policy name> conf/vault/policies/gitlab-vault-policy.hcl
    6. 示例策略名称:gitlab-vault-readonly
    • Vault role
    1. 登录到 Gitlab
    2. 浏览到您要授予对 Vault 机密访问权限的 Gitlab 存储库
    3. 选择左上角的“项目概览”
    4. 获取项目 ID
    5. cp conf/vault/roles/gitlab-jwt-role.json.example conf/vault/roles/gitlab-jwt-role.json
    6. vim conf/vault/roles/gitlab-jwt-role.json并设置:
      1. {{ gitlab_project_id }}– 将此设置为项目 ID
      2. 上面的图显示了project_id 为 2
      3. {{ gitlab_vault_role }}
      4. 上一节中 Vault 策略的名称 – 使用gitlab-vault-readonly
    7. cat conf/vault/roles/gitlab-jwt-role.json | vault write auth/jwt/role/gitlab-vault-readonly –

    .gitlab-ci.yml – 获取 Vault 密钥

    1. 登录到 Gitlab
    2. 浏览到您要授予对 Vault 机密访问权限的 Gitlab 存储库
    3. 选择左上角的“项目概览”
    4. 选择“新建文件”
    5. VAULT_ADDR – 设置为 Vault 的 HTTP URL 地址
    6. {{vault_search_path}}– 设置为上面创建的 Vault 密钥路径
    7. {{gitlab_vault_role}} – 设置为上面为 Gitlab 创建的 Vault 角色
    8. 保存并退出
      1. 进入.gitlab-ci.yml作为文件名
      2. 复制内容conf/gitlab/gitlab-ci-example.yml.example进入.gitlab-ci.yml并设置:
    9. 选择左侧的 CI/CD > 管道
    10. 选择最新的管道作业
    11. 选择Vault_secrets stage

    总结:

    HashiCorp Vault 是一个密钥管理工具。与 Git 相比,Vault 的核心功能是以本机方式来处理机密信息的。所以对于配置密钥信息来说,使用 Vault 作为 Config Server 的后端存储,是一种更好的选择。

    生产环境代码泄露的危害,一方面是业务逻辑被不怀好意的人分析,容易被找出可利用的漏洞,当然更危险的是如果代码里面有一些明文存储secrets、Token、Api Key等,这些内容被别人拿到,服务就很容易遭到致命的攻击,给我们带来不可估量的损失 。所以合理存储程序中的secrets是十分有必要的。

    本文也主要介绍了如何使用Vault对基于Gitlab的CICD流程的加密。另外Vault的场景也是非常广泛的。本文的目的也仅仅是抛砖引玉,通过本文,你可以了解如下的知识点。

    • 了解 JWT 授权的工作原理
    • 了解授权和认证的区别
    • 如何从 Vault 为 Gitlab CI/CD 管道请求密钥
    • 如何设置 Vault JWT 身份验证方法
    • 如何创建一个 .gitlab-ci.yml配置

    参考:

    • 在 docker-compose 替换 NGINX 配置中的环境变量(https://stackoverflow.com/questions/56649582/substitute-environment-variables-in-nginx-config-from-docker-compose)
    • 在 GNU/Linux 上手动安装 GitLab Runner(https://docs.gitlab.com/runner/install/linux-manually.html)
    • 在 Windows 10 上安装 Hyper-V(https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v)
    • 使用 openssl 从服务器获取证书(http://using%20openssl%20to%20get%20the%20certificate%20from%20a%20server/)
    • Vaults – JWT/OIDC 身份验证方法(https://www.vaultproject.io/docs/auth/jwt)
    • Youtube – HashiCorp Vault GitLab 集成:为什么以及如何?(https://www.youtube.com/watch?v=VmQZwfgp3aA)
    • Youtube – 什么是 JWT,为什么要使用 JWT(https://www.youtube.com/watch?v=7Q17ubqLfaM&t=2s)

    原文:
    https://holdmybeersecurity.com/2021/03/04/gitlab-ci-cd-pipeline-with-vault-secrets/

    «
    »
以专业成就每一位客户,让企业IT只为效果和安全买单

以专业成就每一位客户,让企业IT只为效果和安全买单