LoginSignup
2
0

概要

Gitlab Runner の処理にて、HashiCorp Vault JWT認証を行い、vault secretを利用します。
Gitlab でシークレットレス管理を行うための参考情報となります。

前提条件

  • Gitlab CI/CD、Hashirocp Vault の基本操作を理解していること。
    ※本記事ではgitlabやvaultの細かな操作方法についての説明は行いません。
  • gitlab runner から vault へのアクセスが可能であること。
    ※vault へのアクセス制限がある場合は、self-managed runner を利用してください。
  • vault から gitlab へのアクセスが可能であること。
  • 本記事の内容は、HashiCorp Vault v1.15.4 BSL版、および Gitlab SaaS 16.8.0 /Self-Managed 15.11.13 のいずれもFreeプラン環境での動作を確認しています。

処理の流れ

本記事では、下記のパイプラインを作成します。

  • vault_auth
    Vault認証を行い、認証Tokenを後続処理に渡す
  • kv_job
    認証Tokenを用いて、Vault KV Secret (ID/Passwordなど)を読み取り、処理を実行
  • vault_get_secret_aws
    認証Tokenを用いて、Vault AWS Secret (一時IAM AccessKey) を取得し、後続処理に渡す
  • aws_job
    一時IAM AccessKeyを用いて、aws関連処理を実行
  • vault_revoke_lease
    Vault AWS Secret(一時IAM AccessKey)の破棄
  • vault_revoke_token
    Vault 認証Tokenの破棄

Vault 環境準備

あらかじめ、vaultに、secret、policy、auth の設定を追加します。

kv secret および policy の追加

# kv secret (version 1) 追加
vault secrets enable -path=kv/myapp -description="kv for myapp" kv

vault kv put kv/myapp/system -<<EOF
{
  "hostname": "system",
  "ipaddress": "192.168.0.1",
  "login_user": "user01",
  "login_password": "P@ssw0rd"
}
EOF

# kv secret 確認
vault kv list kv/myapp
vault kv get kv/myapp/system

# policy 追加
vault policy write kv_myapp_policy -<<EOF
# kv/myapp/ 以下のkey-value 管理
path "kv/myapp/*" {
  capabilities = [ "create", "read", "update", "delete", "list" ]
}
EOF

# policy 確認
vault policy list
vault policy read kv_myapp_policy

aws secret および policy の追加


# aws secret の追加 ※別途AWS側でIAM UserまたはIAM Role の設定が必要
# 詳しくはこちら https://qiita.com/saitamanokusa/items/24888fdcabaa7b153be5#aws-secret
# ※aws の利用の用途がない場合は飛ばしてください
vault secrets enable -path=aws/myapp -description="aws for myapp" aws

access_key="xxxxxxxxxxx"
secret_key="xxxxxxxxxxx"
region="ap-northeast-1"
vault write aws/myapp/config/root access_key="$access_key" secret_key="$secret_key" region=$region
vault read aws/myapp/config/root

vault write aws/myapp/config/lease lease=15m lease_max=1h
vault read aws/myapp/config/lease

vault write aws/myapp/roles/s3_full credential_type=iam_user policy_arns=arn:aws:iam::aws:policy/AmazonS3FullAccess
vault list aws/myapp/roles
vault read aws/myapp/roles/s3_full

# policy 追加
vault policy write aws_myapp_policy -<<EOF
# aws/myapp/ 以下の管理
path "aws/myapp/*" {
  capabilities = ["create", "read", "update", "list" , "delete"]
}
path "sys/leases/lookup/aws/myapp/creds/*" {
  capabilities = ["create", "update"]
}
path "sys/leases/revoke/aws/myapp/creds/*" {
  capabilities = [ "read","update"]
}
EOF

# policy 確認
vault policy list
vault policy read aws_myapp_policy

JWT認証の追加

# gitlab.com JWT認証用の auth を追加
vault auth enable -path jwt/gitlab.com -description="for GitLab CI/CD on gitlab.com" jwt

# JWKSエンドポイントなど  ※Self-Managed版はドメイン名を修正してください
vault write auth/jwt/gitlab.com/config \
    jwks_url="https://gitlab.com/-/jwks" \
    bound_issuer="https://gitlab.com"

vault read auth/jwt/gitlab.com/config

# ※bound_claimsは環境に合わせて設定値を修正してください
vault write auth/jwt/gitlab.com/role/myrole - <<EOF
{
  "role_type": "jwt",
  "user_claim": "user_email",
  "bound_claims_type": "string",
  "bound_claims": {
    "iss": "https://gitlab.com",
    "project_path": "gitalab_sample/vault_lab"
  },
  "policies": [
    "aws_myapp_policy",
    "kv_myapp_policy"
  ],
  "ttl": "15m",
  "token_explicit_max_ttl": "60m"
}
EOF

vault list auth/jwt/gitlab.com/role
vault read auth/jwt/gitlab.com/role/myrole

vaultの習得は、チュートリアルが充実していますので、そちらをご参照ください。
https://developer.hashicorp.com/vault/tutorials
主なコマンド操作はこちらにも記載があります。
https://qiita.com/saitamanokusa/items/24888fdcabaa7b153be5

gitlab 資材の配置

下記のファイルを用意します。

$ tree -a
.
├── .gitlab-ci.yml
└── script
    ├── vault_auth_jwt.sh
    ├── vault_get_secret_aws.sh
    ├── vault_revoke_lease.sh
    └── vault_revoke_token.sh

.gitlab-ci.yml

rules などは環境に合わせて追加してください。

.gitlab-ci.yml
default:
  image: hashicorp/vault:1.15.4
  before_script:
    - apk add bash curl jq

variables:
  VAULT_ADDR: "https://vault.saitamanokusa.net:8200"
  VAULT_SKIP_VERIFY: "true"
  VAULT_AUTH_PATH: jwt/gitlab.com
  VAULT_AUTH_ROLE: myrole
  VAULT_AWS_SECRET_PATH: aws/myapp
  VAULT_AWS_SECRET_ROLE: s3_full

vault_auth:
  id_tokens:
    VAULT_AUTH_JWT:
      aud: https://gitlab.com
  artifacts:
    reports:
      dotenv: vault.env
  script:
    - bash ./script/vault_auth_jwt.sh

kv_job:
  needs:
    - vault_auth
  script:
    - res=$(vault kv get -format=json kv/myapp/system)
    - myapp_hostname=$(echo "$res" | jq -r '.data.hostname')
    - myapp_ipaddress=$(echo "$res" | jq -r '.data.ipaddress')
    - myapp_user=$(echo "$res" | jq -r '.data.login_user')
    - myapp_password=$(echo "$res" | jq -r '.data.login_password')
    - echo "hostname:$myapp_hostname"
    - echo "ipaddress:$myapp_ipaddress"
    - echo "user:$myapp_user"
    - echo "password:$myapp_password"

vault_get_secret_aws:
  needs:
    - vault_auth
  artifacts:
    reports:
      dotenv: vault.env
  before_script:
    - apk add bash curl jq aws-cli
  script:
    - env|grep VAULT
    - bash ./script/vault_get_secret_aws.sh

aws_job:
  needs:
    - vault_get_secret_aws
  before_script:
    - apk add bash curl jq aws-cli
  script:
    - env|grep AWS
    - aws s3 ls

vault_revoke_lease:
  needs:
    - vault_get_secret_aws
    - aws_job
  script:
    - bash ./script/vault_revoke_lease.sh

vault_revoke_token:
  needs:
    - vault_auth
    - kv_job
    - vault_revoke_lease
  script:
    - bash ./script/vault_revoke_token.sh

script

script/vault_auth_jwt.sh
#!/usr/bin/env bash

set -euo pipefail

# vault auth
if ! res=$(vault write -format=json "auth/${VAULT_AUTH_PATH}/login" role="$VAULT_AUTH_ROLE" jwt="$VAULT_AUTH_JWT"); then
    echo "vaultの 応答がありません" >&2
    exit 1
fi

# curl コマンドの場合
# if ! res=$(curl -sk -X PUT \
#     -d "{\"jwt\":\"${VAULT_AUTH_JWT}\",\"role\":\"${VAULT_AUTH_ROLE}\"}" \
#     "${VAULT_ADDR}/v1/auth/${VAULT_AUTH_PATH}/login"); then
#     echo "vaultの 応答がありません" >&2
#     exit 1
# fi

vault_token=$(echo "$res" | jq -r '.auth.client_token')
[ ! "$vault_token" ] || [ "$vault_token" = "null" ] && { echo -e "vaultの応答に問題があります\n${res}" >&2 && exit 1; }

# 標準出力でvaultのレスポンスを返す
echo "$res" | jq -r

# 後続処理用に変数を定義
if [ "${GITLAB_CI:=}" ]; then echo "VAULT_TOKEN=$vault_token" >>"vault.env"; fi

exit
script/vault_get_secret_aws.sh
#!/usr/bin/env bash

set -euo pipefail

# vault secret 取得
if ! res=$(vault read -format=json "${VAULT_AWS_SECRET_PATH}/creds/${VAULT_AWS_SECRET_ROLE}"); then
    echo "vaultの 応答がありません" >&2
    exit 1
fi

# curl コマンドの場合
# if ! res=$(curl -s -k \
#     -H "X-Vault-Request: true" -H "X-Vault-Token: $VAULT_TOKEN" \
#     "${VAULT_ADDR}/v1/${VAULT_AWS_SECRET_PATH}/creds/${VAULT_AWS_SECRET_ROLE}"); then
#     echo "vaultの 応答がありません" >&2
#     exit 1
# fi

# 動作確認および後続処理用に値を取得
vault_lease_id=$(echo "$res" | jq -r '.lease_id')
access_key=$(echo "$res" | jq -r '.data.access_key')
secret_key=$(echo "$res" | jq -r '.data.secret_key')
security_token=$(echo "$res" | jq -r '.data.security_token')
[ "$security_token" = "null" ] && security_token=""
# 応答内容に問題がある場合はエラーを返す
[ ! "$access_key" ] || [ "$vault_lease_id" = "null" ] && { echo -e "vaultの応答に問題があります\n${res}" >&2 && exit 1; }

# 即応答を返すと、取得したAccessKeyで認証エラーが出るため、
# aws認証が通るようになるまで待機
export AWS_ACCESS_KEY_ID="$access_key"
export AWS_SECRET_ACCESS_KEY="$secret_key"
export AWS_SESSION_TOKEN="$security_token"

declare count=0
while ! aws sts get-caller-identity >/dev/null 2>&1; do
    sleep 1 >/dev/null 2>&1
    count=$((count + 1))
    [[ $count -gt 60 ]] && { echo "aws 認証タイムアウト" >&2 && exit 1; }
done

# 認証が不安定であるため追加で5秒待機(credential_type=assumed_role利用時は不要)
sleep 5 >/dev/null 2>&1

# 標準出力でvault のレスポンスを返す
echo "$res" | jq

# 後続処理用に変数を定義
if [ "${GITLAB_CI:=}" ]; then
    {
        echo "VAULT_TOKEN=$VAULT_TOKEN"
        echo "VAULT_LEASE_ID=$vault_lease_id"
        echo "AWS_ACCESS_KEY_ID=$access_key"
        echo "AWS_SECRET_ACCESS_KEY=$secret_key"
        echo "AWS_SESSION_TOKEN=$security_token"
    } >>"vault.env"
fi

exit
script/vault_revoke_lease.sh
#!/usr/bin/env bash

set -euo pipefail

# vault revoke処理
if ! res=$(vault lease revoke "${VAULT_LEASE_ID}"); then
    echo "vaultの 応答がありません" >&2
    exit 1
fi

# curl コマンドの場合
# if ! res=$(curl -s -k -X PUT \
#     -H "X-Vault-Request: true" -H "X-Vault-Token: ${VAULT_TOKEN}" \
#     -d '{"sync":false}' \
#     "${VAULT_ADDR}/v1/sys/leases/revoke/${VAULT_LEASE_ID}"); then
#     echo "vaultの 応答がありません" >&2
#     exit 1
# fi

# 標準出力でvaultのレスポンスを返す
echo "$res"

exit
script/vault_revoke_token.sh
#!/usr/bin/env bash

set -euo pipefail

# token reveoke
if ! res=$(vault token revoke -self); then
  echo "vaultの 応答がありません" >&2
  exit 1
fi

# curl コマンドの場合
# if ! res=$(curl -s -k -X PUT \
#   -H "X-Vault-Request: true" -H "X-Vault-Token: ${VAULT_TOKEN}" \
#   "${VAULT_ADDR}/v1/auth/token/revoke-self"); then
#   echo "vaultの 応答がありません" >&2
#   exit 1
# fi

# 標準出力でvaultのレスポンスを返す
echo "$res"

exit

動作チェック

vault設定および資材の準備ができましたら、実際にpipelineを実行してみてください。

image.png

もし vault_auth がエラーとなる場合は、runner → vault → gitlab のアクセス制限や、bound_claims の設定をご確認ください。
vault_get_secret_aws がエラーとなる場合は、AWS IAM User/IAM Roleに割り当てたPolicyのチェックや、sleep コマンドの時間を調整してみてください。

【参考】vault 認証roleのbound_claims設定について

本記事で利用するJWT認証では、vault jwt認証のrole - bound_claimsにて、認証元の正当性をチェックします。
しかし初めて利用する場合は、bound_claims に何を設定すればいいかが分かりづらいかと思います。
この設定値のチェック方法について解説します。

下記のような.gitlab-ci.ymlでパイプラインを実行してみてください。

.gitlab-ci.yml
default:
  image: alpine:3.18

get_jwt:
  id_tokens:
    VAULT_AUTH_JWT:
      aud: https://gitlab.com
  artifacts:
    reports:
      dotenv: vault.env
  script:
    - echo "VAULT_AUTH_JWT=$VAULT_AUTH_JWT">>vault.env

check_jwt:
  needs:
    - get_jwt
  script:
    - echo $VAULT_AUTH_JWT

後続ジョブcheck_jwtにて、JWTの値が出力されますので、これをコピーしてください。

image.png

あとは、こちらのサイトで、取得したJWTの値を貼り付け、Payloadを確認してみてください。
https://jwt.io/

vault 認証時に渡される情報が確認できます。

image.png

この値を参考に、namespace_pathproject_path など適切な値を使って、bound_claimsを設定します。

最後に

今回ご紹介した方法では、key-value および aws accesskey のみ利用していますが、vault には他にも便利なsecret機能が多くあります。
これらを利用することで、Gitlab のプロジェクト上にシークレット情報を一切持たない構成も可能です。
シークレットの一元管理やセキュリティ対策を行う際の、参考としていただければ幸いです。

参考

https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html
https://docs.gitlab.com/ee/ci/examples/authenticating-with-hashicorp-vault/
https://developer.hashicorp.com/vault/docs/auth/jwt
https://developer.hashicorp.com/vault/api-docs/auth/jwt

secrets:はGitlab Premium以上の機能です

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0