シークレット漏洩への自動対応

GitLab 13.6で導入されました

GitLab Secret Detectionは、特定のタイプの漏えいシークレットを発見すると自動的に応答します。自動応答には以下のようなものがあります:

  • 自動的にシークレットを取り消します。
  • シークレットを発行したパートナーに通知します。パートナーはその秘密を取り消したり、オーナーに通知したり、悪用から保護したりすることができます。

サポートされる秘密のタイプとアクション

GitLabは以下のシークレットタイプの自動応答をサポートしています:

シークレットタイプアクションGitLab.comでサポートされています。セルフマネージドでサポート
GitLabパーソナルアクセストークン トークンを直ちに失効させ、オーナーにメールを送信15.9 以降
Amazon Web Services(AWS) IAM アクセスキー AWSへの通知
Google CloudサービスのアカウントキーAPI キーOAuth クライアントのシークレット Google Cloud への通知
PostmanAPI キー Postmanに通知;Postmanがキーオーナーに通知

コンポーネントの凡例

  • ✅ - デフォルトで使用可能
  • トークン失効APIを使った手動インテグレーションが必要です。

機能の可用性

GitLab 15.11でデフォルト以外のブランチで有効になりました。

クレデンシャルは Secret Detection が検出した時のみ後処理されます:

  • 公開プロジェクトでは、公開されたクレデンシャルは脅威を増大させるため。非公開プロジェクトへの拡張はイシュー391379で検討されています。
  • GitLab Ultimate を使ったプロジェクトでは、技術的な理由から。全階層への拡張は、イシュー391763 で追跡されています。

高レベルアーキテクチャ

この図は、GitLabアプリケーションのポスト処理フックがどのようにシークレットを取り消すかを説明します:

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. シークレット検出ジョブのあるパイプラインが完了し、スキャンレポートが作成されます(1)。
  2. レポートはサービスクラスによって処理され(2)、トークンの失効が可能な場合は非同期ワーカーをスケジュールします。
  3. 非同期ワーカー(3) は外部にデプロイされた HTTP サービス(45) と通信し、どの種類のシークレットが自動的に失効できるかを決定します。
  4. ワーカーは、GitLab Token Revocation APIが失効させることができる検出されたシークレットのリストを送信します(6と 7)。
  5. GitLab Token Revocation APIは、それぞれの取り消し可能なトークンをそれぞれのベンダーのPartner APIに送信します(8と 9)。詳細については、GitLab Token Revocation APIのドキュメントを参照してください。

漏洩したクレデンシャルを通知するパートナープログラム

GitLabは、パートナーがイシューしたクレデンシャルがGitLab.comの公開リポジトリに流出した場合、パートナーに通知します。クラウドまたは SaaS 製品をオペレーションしていて、これらの通知を受け取ることに興味がある場合は、エピック 4944 で詳細をご覧ください。パートナーは、GitLab Token Revocation APIから呼び出されるPartner APIを実装する必要があります。

パートナーAPIの実装

Partner API は GitLab Token Revocation API とインテグレーションし、リークされたトークンの失効リクエストを受け取り、応答します。このサービスは公開アクセス可能なHTTP APIである必要があります。

あなたのサービスへのリクエストには、一つ以上の漏れたトークンと、リクエストボディのシグネチャを含むヘッダを含めることができます。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 は、(1) Partner API に失効リクエストを送信します。リクエストには公開鍵識別子とリクエスト本体の署名を含むヘッダーが含まれます。
  2. Partner APIは(2)GitLabに公開鍵のリストを要求します。レスポンス(3)には、鍵のローテーションのイベントの際に複数の公開鍵が含まれることがあり、リクエストヘッダ内の識別子でフィルタリングする必要があります。
  3. Partner API は公開鍵(4) を使って、実際のリクエスト本文に対する署名を検証します。
  4. パートナーAPIは漏れたトークンを処理し、自動的な失効を伴う可能性があります(5)。
  5. Partner API は GitLab Token Revocation API(6) に適切な HTTP ステータスコードで応答します:
    • 成功したレスポンスコード (HTTP 200 から 299) は、パートナーがリクエストを受信して処理したことを示します。
    • エラーコード(HTTP 400以上)は、GitLab Token Revocation 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 Detection はmy_api_token のインスタンスが流出したと判断しました。流出したトークンを含むファイルの生のコンテンツへの公開アクセス URL に加えて、トークンの値が提供されます。

リクエストは二つの特別なヘッダを含みます:

ヘッダ種類説明
Gitlab-Public-Key-Identifier文字列です。このリクエストの署名に使われる鍵ペアの一意な識別子。主に鍵のローテーションに使われます。
Gitlab-Public-Key-Signature文字列です。リクエストボディを base64 エンコードしたシグネチャ。

これらのヘッダと 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 ヘッダをリクエストボディと照合することで、失効リクエストが本物かどうかを確認できます。ECDSAとSHA256ハッシュを使用して署名を生成し、それをヘッダ値にbase64エンコードします。

以下の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. Gitlab-Public-Key-Signature ヘッダー値をBase64デコード。
  3. SHA256ハッシュを使用したECDSAを指定し、復号化された署名に対して本文を検証。