CIにおける外部シークレットの利用

  • GitLab 13.4 と GitLab Runner 13.4 で導入されました
  • file GitLab 14.1 と GitLab Runner 14.1 で導入された設定。
  • VAULT_NAMESPACE GitLab 14.9 と GitLab Runner 14.9 で導入された設定。

シークレットは CI ジョブが作業を完了するために必要な機密情報を表します。この機密情報は、APIトークンやデータベースの認証情報、秘密鍵のようなものです。シークレットはシークレットプロバイダから取得します。

常にジョブに提示されるCI/CD変数とは異なり、シークレットはジョブによって明示的に要求されなければなりません。構文の詳細についてはGitLab CI/CD パイプライン設定リファレンスを読んでください。

GitLabは最初にサポートされたプロバイダとしてHashiCorpのVaultを、最初にサポートされたシークレットエンジンとしてKV-V2を選択しました。

Vaultでの認証には ID トークンを使います。HashiCorp Vault での認証とシークレットの読み取りに、ID トークンを使った認証の詳細があります。

CIジョブでVaultのシークレットを使用する前に、Vaultサーバを設定する必要があります。

GitLabとHashiCorp Vaultを使用するフローを図にまとめました:

Flow between GitLab and HashiCorp

  1. データ保管庫とシークレットの設定
  2. JWTを生成し、CIジョブに提供します。
  3. RunnerはHashiCorp Vaultに連絡し、JWTを使用して認証します。
  4. HashiCorp VaultはJWTを検証します。
  5. HashiCorp Vaultは拘束されたクレームをチェックし、ポリシーを添付します。
  6. HashiCorp Vaultはトークンを返します。
  7. RunnerはHashiCorp Vaultからシークレットを読み込みます。
note
この機能のバージョンについては、HashiCorp Vaultによるシークレットの認証と読み取りチュートリアルをお読みください。この機能はすべてのサブスクリプションレベルで利用可能で、Vaultへのシークレットの書き込みとVaultからのシークレットの削除をサポートし、複数のシークレットエンジンをサポートしています。

Vaultサーバの設定

Vaultサーバを設定するには、以下の手順に従います:

  1. Vault サーバがバージョン 1.2.0 以降で動作していることを確認してください。
  2. これらのコマンドを実行して認証方法を有効にします。これらのコマンドは、VaultサーバーにGitLabインスタンスのJSON Web Key Set (JWKS) エンドポイントを提供し、Vaultが公開署名鍵を取得し、認証時にJSON Web Token(JWT) を検証できるようにします:

    $ vault auth enable jwt
       
    $ vault write auth/jwt/config \
      jwks_url="https://gitlab.example.com/-/jwks" \
      bound_issuer="gitlab.example.com"
    
  3. Vault サーバーにポリシーを設定して、特定のパスやオペレーションへのアクセスを許可したり禁止したりします。この例では、本番環境で必要なシークレットセットへの読み取りアクセスを許可しています:

    vault policy write myproject-production - <<EOF
    # Read-only permission on 'ops/data/production/*' path
       
    path "ops/data/production/*" {
      capabilities = [ "read" ]
    }
    EOF
    
  4. このページの「Vault サーバのロールの設定」で説明するように、Vault サーバにロールを設定し、ロールをプロジェ クトまたはネームスペースに制限します。
  5. 以下のCI/CD変数を作成して、Vaultサーバの詳細を提供します:
    • VAULT_SERVER_URL - Vault サーバの URL (https://vault.example.com:8200 など)。必須。
    • VAULT_AUTH_ROLE - オプション。認証を試みる際に使用するロール。ロールが指定されない場合、Vaultは、認証方法が設定されたときに指定されたデフォルトのロールを使用します。
    • VAULT_AUTH_PATH - オプション。認証方法がマウントされているパス。デフォルトはjwt です。
    • VAULT_NAMESPACE - オプション。シークレットおよび認証の読み取りに使用するVault Enterprise ネームスペース。名前空間を指定しない場合、Vault はroot (“/”) 名前空間を使用します。この設定は、Vault Open Source では無視されます。
    note
    ユーザー・インタフェースでこれらの値を提供するためのサポートは、このイシューで追跡されています。

CIジョブでのVaultシークレットの使用

GitLab 13.4 と GitLab Runner 13.4 で導入されました

Vault サーバーの設定後、vault キーワードで定義することで Vault に保存されたシークレットを使うことができます:

job_using_vault:
  id_tokens:
    VAULT_ID_TOKEN:
      aud: https://gitlab.com
  secrets:
    DATABASE_PASSWORD:
      vault: production/db/password@ops  # translates to secret `ops/data/production/db`, field `password`
      token: $VAULT_ID_TOKEN

この例では:

  • production/db - シークレット
  • password 現場。
  • ops - シークレットエンジンが搭載されているパス。

GitLabがVaultからシークレットを取得した後、その値は一時ファイルに保存されます。このファイルへのパスはDATABASE_PASSWORD という CI/CD 変数に格納されます。fileという変数と同様です。

デフォルトの動作を上書きするには、file オプションを明示的に設定します:

secrets:
  id_tokens:
    VAULT_ID_TOKEN:
      aud: https://gitlab.com
  DATABASE_PASSWORD:
    vault: production/db/password@ops
    file: false
    token: $VAULT_ID_TOKEN

この例では、シークレット値を保持するファイルを指す代わりに、DATABASE_PASSWORD 変数に直接シークレット値を入れています。

サポートされている構文の詳細については、.gitlab-ci.yml リファレンスをお読みください。

Vault サーバ ロールの設定

CIジョブが認証を試みる際に、ロールを指定します。ロールを使用して、異なるポリシーをグループにまとめることができます。認証に成功すると、これらのポリシーが結果として生成されるVaultトークンにアタッチされます。

バウンドクレームは、JWTクレームにマッチする定義済みの値です。バウンデッドクレームを使うと、特定のGitLabユーザー、特定のプロジェクト、あるいは特定のGitリファレンスに対して実行されているジョブへのアクセスを制限することができます。必要な数のバウンデッド・クレームを持つことができますが、認証が成功するためにはそれらがすべて一致する必要があります。

bounded claim と GitLab のユーザーロールや 保護されたブランチなどの機能を組み合わせることで、特定のユースケースに合わせてこれらのルールを調整することができます。この例では、production リリースで使われるパターンにマッチする名前の protected タグで実行されるジョブに対してのみ認証を許可しています:

$ vault write auth/jwt/role/myproject-production - <<EOF
{
  "role_type": "jwt",
  "policies": ["myproject-production"],
  "token_explicit_max_ttl": 60,
  "user_claim": "user_email",
  "bound_claims_type": "glob",
  "bound_claims": {
    "project_id": "42",
    "ref_protected": "true",
    "ref_type": "tag",
    "ref": "auto-deploy-*"
  }
}
EOF
caution
project_idnamespace_idのような提供されたクレームのいずれかを使用して、常にプロジェクトやネームスペースにロールを制限してください。 これらの制限がなければ、この GitLab インスタンスで生成された JWT はすべて、このロールを使用して認証を許可される可能性があります。

IDトークンJWTクレームの完全なリストについては、HashiCorp Vaultチュートリアルの「How It Works」セクションの「Authenticating and Reading Secrets With HashiCorp Vault」をお読みください。

また、生成される Vault トークンには、有効期限、IP アドレス範囲、使用回数などの属性を指定できます。オプションの完全なリストは、Vault のJSON ウェブトークンメソッドのロールの作成に関するドキュメントにあります。

自己署名Vaultサーバの使用

Vaultサーバが自己署名証明書を使用している場合、ジョブログに以下のエラーが表示されます:

ERROR: Job failed (system failure): resolving secrets: initializing Vault service: preparing authenticated client: checking Vault server health: Get https://vault.example.com:8000/v1/sys/health?drsecondarycode=299&performancestandbycode=299&sealedcode=299&standbycode=299&uninitcode=299: x509: certificate signed by unknown authority

このエラーを解決するには、2つのオプションがあります:

  • GitLab RunnerサーバーのCAストアに自己署名証明書を追加します。Helmチャートを使用してGitLab Runnerをデプロイした場合は、独自のGitLab Runnerイメージを作成する必要があります。
  • VAULT_CACERT 環境変数を使って、GitLab Runner が証明書を信頼するように設定します:
    • systemd を使って GitLab Runner を管理している場合は、GitLab Runner 用の環境変数を追加する方法を参照してください。
    • GitLab Runner をHelm チャートを使ってデプロイした場合:
      1. GitLab にアクセスするためのカスタム証明書を用意し、GitLab 用の証明書ではなく Vault サーバー用の証明書を追加するようにしてください。GitLab インスタンスも自己署名証明書を使用している場合は、同じSecret で両方を追加できるはずです。
      2. values.yaml ファイルに以下の行を追加します:

        ## Replace both the <SECRET_NAME> and the <VAULT_CERTIFICATE>
        ## with the actual values you used to create the secret
                 
        certsSecretName: <SECRET_NAME>
                 
        envVars:
          - name: VAULT_CACERT
            value: "/home/gitlab-runner/.gitlab-runner/certs/<VAULT_CERTIFICATE>"