概要
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 などは環境に合わせて追加してください。
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
#!/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
#!/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
#!/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
#!/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を実行してみてください。
もし 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
でパイプラインを実行してみてください。
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の値が出力されますので、これをコピーしてください。
あとは、こちらのサイトで、取得したJWTの値を貼り付け、Payloadを確認してみてください。
https://jwt.io/
vault 認証時に渡される情報が確認できます。
この値を参考に、namespace_path
や project_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以上の機能です