OIDC を使って CircleCI から簡単に HashiCorp Vault と認証+シークレットアクセスできる Orb を書いてみた


書いてみたのはこれ:

https://circleci.com/developer/orbs/orb/smaeda-ks/orb-hashicorp-vault-cli

これを使えば、CircleCI の環境変数に静的に保存していたシークレットから卒業できそうですね。

CircleCI の OIDC サポート

つい最近サポートされたみたいです。

https://circleci.com/changelog/#oidc-tokens-in-jobs

https://circleci.com/docs/2.0/openid-connect-tokens/

GitHub Actions や GitLab では以前から既にサポートされていて、GitHub Actions での例は以前にも記事に書きました:

https://zenn.dev/jrsyo/articles/e954e907dcedeb

今回は CircleCI で、Orb を使って同様の workflow を実現してみます。

Organization ID を確認

Vault 側で OIDC の設定を構成するためには、利用している CircleCI アカウントの Organization ID が必要になります。Organization ID を確認するには、CircleCI のコンソールから Organization Settings ページに行くことで確認できます。

※最低1つの Context が作成されていないと Organization ID が表示されません。

Vault の設定

基本的には前回書いた GitHub Actions OIDC の時とほとんど同じです。

JWT Auth Method を有効にする

$ vault auth enable jwt
Success! Enabled jwt auth method at: jwt/

有効にした JWT auth method config の bound_issueroidc_discovery_url フィールドを設定します。ここで先ほど確認した Organization ID が必要になります。${CIRCLECI_ORG_ID} を自分のものと置き換えてください。

$ vault write auth/jwt/config \
  bound_issuer="https://oidc.circleci.com/org/${CIRCLECI_ORG_ID}" \
  oidc_discovery_url="https://oidc.circleci.com/org/${CIRCLECI_ORG_ID}"
Success! Data written to: auth/jwt/config

JWT auth backend role の作成

$ vault write auth/jwt/role/circleci-dev -<<EOF
{
  "role_type": "jwt",
  "user_claim": "sub",
  "bound_claims": {
    "aud": "${CIRCLECI_ORG_ID}"
  },
  "policies": ["circleci-dev"],
  "ttl": "1h"
}
EOF

利用できる claim の種類は GitHub などと比べると少し少ないようですが、一覧は下記から確認できます:

https://circleci.com/docs/2.0/openid-connect-tokens/

今回の例では aud のみを bound_claims 条件に含めているので、自分の Orgnization ID からの要求であれば全て受け付けます。もう少し条件を絞りたい場合は、例えば代わりに oidc.circleci.com/project-id を使って、特定の Project ID からの要求のみ信頼する、といった制御もできるかと思います。

今回は詳細は割愛しますが、上記の例では、policies で指定されているように、circleci-dev という ACL Policy が別に存在し、目的の secret path への必要な permission が正しく grant されている前提です。

$ vault kv put secret/circleci/dev password=foo

$ vault policy write circleci-dev -<<EOF
path "secret/data/circleci/dev" {
  capabilities = ["read"]
}
EOF

実際に Orb を使ってみる

では実際に workflow から Vault へ OIDC 経由で認証し、任意のシークレットを取得してみます。

description: |
  Install Vault binary, authenticate using OIDC, and get secrets.
usage:
  version: 2.1
  orbs:
    orb-hashicorp-vault-cli: smaeda-ks/orb-hashicorp-vault-[email protected]
  jobs:
    my-job:
      machine: true
      steps:
        - checkout
        # Install Vault
        - orb-hashicorp-vault-cli/install
        # Authenticate using OIDC and obtain token
        # This will automatically set VAULT_TOKEN env variable
        - orb-hashicorp-vault-cli/auth-oidc:
            vault-address: "http://localhost:8200"
            vault-role: "circleci-dev"
        - run:
            name: Get secret
            command: |
              # export secret using $BASH_ENV
              # so it can be referenced by subsequent steps within the job
              FOO=$(vault kv get -field=password secret/circleci/dev)
              echo "export SECRET_FOO=${FOO}" >> $BASH_ENV
  workflows:
    use-my-orb:
      jobs:
        - my-job

今回作った Orb は、installauth-oidc の2つのコマンドのみ提供しています。

install コマンドを使うと、現在利用している executor 上に Vault の実行ファイルをダウンロードし、パスを通してくれます。これによって以降の step では vault コマンドが利用可能になります。

auth-oidc コマンドを使うと、CircleCI から払い出された JWT トークンを使って、リモートの Vault と認証し、token を取得します。この時取得した token は自動的に VAULT_TOKEN 環境変数にセットされるため、auth-oidc を実行後は Vault にログイン済みの状態で vault コマンドを利用可能になっています。
なお、vault-addressvault-role は required なパラメーターなので、必ず指定が必要です。vault-role には、事前に作成した JWT auth backend の role を指定します。今回のケースでは circleci-dev ですね。有償版 Vault を利用している場合には、Namespace などもオプションで指定できるので、詳細はドキュメントをご覧ください:

https://circleci.com/developer/orbs/orb/smaeda-ks/orb-hashicorp-vault-cli#commands-auth-oidc

これら2つのコマンドを実行したのち、上記の例では最後の step で、secret/circleci/dev path に保存されている password のデータを取得しています。実際の workflow では、これを更に後続の step から利用することになるでしょう。

その他

今回作った Orb は私の個人の Namespace に紐付いており、CircleCI から verify されている訳ではないので、CircleCI の org 設定で未承認 Orb の利用を許可しておく必要があります。