泄露 secrets 时的自动响应

引入于极狐GitLab 13.6。

极狐GitLab Secret 检测在发现某些类型的泄露机密时会自动响应,可以:

  • 自动撤销 secret。
  • 通知发布 secret 的合作伙伴。然后合作伙伴可以撤销 secret,通知其所有者,或以其他方式防止滥用。

支持的 secret 类型和操作

极狐GitLab 自动响应支持以下类型的 secret:

Secret 类型 执行的操作 SaaS 私有化部署
极狐GitLab 个人访问令牌 立即撤销令牌,发送电子邮件给所有者 ✅ (15.9 及更高版本)
Amazon Web Services (AWS) IAM 访问密钥 通知 AWS

说明

  • ✅ - 默认可用
  • ⚙ - 需要使用令牌撤销 API 进行手动集成

可用的功能

为非默认分支启用于 15.11 版本。

凭据仅在 Secret 检测发现它们时才进行后处理:

  • 支持公开项目,因为公开暴露的凭据构成了更大的威胁。
  • 支持旗舰版项目。

高层架构

下图描述了后处理钩子如何撤销极狐GitLab 应用程序中的 secret:

sequenceDiagram autonumber GitLab Rails-->+GitLab Rails: gl-secret-detection-report.json GitLab Rails->>+GitLab Sidekiq: StoreScansService GitLab Sidekiq-->+GitLab Sidekiq: ScanSecurityReportSecretsWorker GitLab Sidekiq-->+GitLab Token Revocation API: GET revocable keys types GitLab Token Revocation API-->>-GitLab Sidekiq: OK GitLab Sidekiq->>+GitLab Token Revocation API: POST revoke revocable keys GitLab Token Revocation API-->>-GitLab Sidekiq: ACCEPTED GitLab Token Revocation API-->>+Partner API: revoke revocable keys Partner API-->>+GitLab Token Revocation API: ACCEPTED
  1. 带有 Secret 检测作业的流水线完成,生成扫描报告 (1)。
  2. 该报告由一个服务类处理 (2),如果可以撤销令牌,该服务类会安排一个异步 worker。
  3. 异步 worker (3) 与外部部署的 HTTP 服务(45)通信,来确定可以自动撤销哪些类型的 secret。
  4. Worker 发送(67)极狐GitLab 令牌撤销 API 能够撤销的检测到的 secret 列表。
  5. 极狐GitLab 令牌撤销 API 将(89)每个可撤销令牌发送到各自供应商的 Partner API

泄露凭据通知的合作伙伴计划

当 SaaS 上的公开仓库中泄露他们颁发的凭据时,极狐GitLab 会通知合作伙伴。合作伙伴必须实施合作伙伴 API,其由极狐GitLab 令牌撤销 API 调用。

实施合作伙伴 API

合作伙伴 API 与极狐GitLab 令牌撤销 API 集成,来接收和响应泄漏的令牌撤销请求。该服务应该是一个可公开访问的 HTTP API,具有幂等性和速率限制。

对您的服务的请求可以包含一个或多个泄露的令牌,以及带有请求正文签名的 header,建议您使用此签名验证传入请求,以证明它是来自极狐GitLab 的真实请求。下图详细说明了接收、验证和撤销泄漏令牌的必要步骤:

sequenceDiagram autonumber GitLab Token Revocation API-->>+Partner API: Send new leaked credentials Partner API-->>+GitLab Public Keys endpoint: Get active public keys GitLab Public Keys endpoint-->>+Partner API: One or more public keys Partner API-->>+Partner API: Verify request is signed by GitLab Partner API-->>+Partner API: Respond to leaks Partner API-->>+GitLab Token Revocation API: HTTP status
  1. 极狐GitLab 令牌撤销 API 向合作伙伴 API 发送 (1) 撤销请求。该请求包括包含公钥标识符和请求正文签名的 header。
  2. 合作伙伴 API 请求 (2) 来自极狐GitLab 的 公钥列表。在密钥轮换的情况下,响应 (3) 可能包含多个公钥,应该使用请求 header 中的标识符进行过滤。
  3. 合作伙伴 API 针对实际请求正文,使用公钥 (4) 验证签名
  4. 合作伙伴 API 处理泄漏的令牌,可能涉及自动撤销 (5)。
  5. 合作伙伴 API 使用适当的 HTTP 状态代码响应极狐GitLab 令牌撤销 API (6):
    • 成功的响应代码(HTTP 200 到 299)确认合作伙伴已收到并处理了请求。
    • 错误代码(HTTP 400 或更高版本)导致极狐GitLab 令牌撤销 API 重试请求。

撤销请求

以下 JSON 架构文档描述了撤销请求的主体:

{
    "type": "array",
    "items": {
        "description": "A leaked token",
        "type": "object",
        "properties": {
            "type": {
                "description": "The type of token. This is vendor-specific and can be customised to suit your revocation service",
                "type": "string",
                "examples": [
                    "my_api_token"
                ]
            },
            "token": {
                "description": "The substring that was matched by the Secret Detection analyser. In most cases, this is the entire token itself",
                "type": "string",
                "examples": [
                    "XXXXXXXXXXXXXXXX"
                ]
            },
            "url": {
                "description": "The URL to the raw source file hosted on GitLab where the leaked token was detected",
                "type": "string",
                "examples": [
                    "https://gitlab.example.com/some-repo/-/raw/abcdefghijklmnop/compromisedfile1.java"
                ]
            }
        }
    }
}

示例:

[{"type": "my_api_token", "token": "XXXXXXXXXXXXXXXX", "url": "https://example.com/some-repo/-/raw/abcdefghijklmnop/compromisedfile1.java"}]

在此示例中,Secret 检测已确定 my_api_token 的一个实例已被泄露。除了包含泄漏令牌的文件的原始内容的可公开访问的 URL 之外,还会向您提供令牌的值。

请求包含两个特殊 headers:

Header 类型 描述
Gitlab-Public-Key-Identifier string 用于签署此请求的密钥对的唯一标识符。主要用于帮助密钥轮换。
Gitlab-Public-Key-Signature string 请求正文的 base64 编码签名。

您可以将这些 header 与极狐GitLab 公钥端点一起使用来验证撤销请求是否真实。

公开密钥端点

极狐GitLab 维护一个可公开访问的端点,可获取用于验证撤销请求的公钥。您可以根据要求提供端点。

以下 JSON 架构文档描述了公钥端点的响应主体:

{
    "type": "object",
    "properties": {
        "public_keys": {
            "description": "An array of public keys managed by GitLab used to sign token revocation requests.",
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "key_identifier": {
                        "description": "A unique identifier for the keypair. Match this against the value of the Gitlab-Public-Key-Identifier header",
                        "type": "string"
                    },
                    "key": {
                        "description": "The value of the public key",
                        "type": "string"
                    },
                    "is_current": {
                        "description": "Whether the key is currently active and signing new requests",
                        "type": "boolean"
                    }
                }
            }
        }
    }
}

示例:

{
    "public_keys": [
        {
            "key_identifier": "6917d7584f0fa65c8c33df5ab20f54dfb9a6e6ae",
            "key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEN05/VjsBwWTUGYMpijqC5pDtoLEf\nuWz2CVZAZd5zfa/NAlSFgWRDdNRpazTARndB2+dHDtcHIVfzyVPNr2aznw==\n-----END PUBLIC KEY-----\n",
            "is_current": true
        }
    ]
}

验证请求

您可以使用从上述 API 响应中获取的相应公钥,通过针对请求正文验证 Gitlab-Public-Key-Signature header 来检查撤销请求是否真实。我们使用带有 SHA256 哈希的 ECDSA 来生成签名,然后将其 base64 编码到 header 值中。

下面的 Python 脚本演示了如何验证签名,它使用流行的 pyca/cryptography 模块进行加密操作:

import hashlib
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from cryptography.hazmat.primitives.asymmetric import ec

public_key = str.encode("")      # obtained from the public keys endpoint
signature_header = ""            # obtained from the `Gitlab-Public-Key-Signature` header
request_body = str.encode(r'')   # obtained from the revocation request body

pk = load_pem_public_key(public_key)
decoded_signature = base64.b64decode(signature_header)

pk.verify(decoded_signature, request_body, ec.ECDSA(hashes.SHA256()))  # throws if unsuccessful

print("Signature verified!")

主要步骤:

  1. 将公钥加载为适合您正在使用的加密库的格式。
  2. Base64 解码 Gitlab-Public-Key-Signature header 值。
  3. 根据解码签名验证正文,指定带有 SHA256 哈希的 ECDSA。