概要
cloudfront等で、AWS WAFによってIPアクセス制限がかかっているリソースに対して、github actions hostedな環境からリクエストを送りたい場合があると思います。
github actionsが利用するグローバルIPの範囲は公開されていて、こちら全てをIP許可リストに追加することも可能ですが、保守面やセキュリティ観点としても推奨されるものではないと思います。
GitHub の IP アドレスはときどき変更されます。 IP アドレスによる許可はお勧めしません
GitHubのIPアドレスについて - GitHub Docs
そこで、実行中のgithub actions環境のIPを取得し、動的にIP許可リストに追加する方法を紹介します。
ちなみに、そもそもIPでなく専用のHTTPヘッダーを利用してWAFを突破するような方法も考えられますが、ここでは割愛します。
前提
以下はterraformの例ですが、WAF IPセットと許可ルールによるリソース構成になっている場合を想定します。
resource "aws_wafv2_ip_set" "my_project_github_actions" { provider = aws.virginia name = "github-actions" scope = "CLOUDFRONT" ip_address_version = "IPV4" // 許可IPリストはgithub actions内の実行時に動的に追加/削除するためignoreする lifecycle { ignore_changes = [ description, addresses, ] } } resource "aws_wafv2_web_acl" "my_project" { provider = aws.virginia name = "my-project-waf" scope = "CLOUDFRONT" ~~中略~~ rule { name = "allow-github-actions-ip" priority = 1 action { allow { } } statement { ip_set_reference_statement { arn = aws_wafv2_ip_set.my_project_github_actions.arn } } } ~~中略~~
結果
~~ 中略 ~~ jobs: sample: runs-on: ubuntu-latest timeout-minutes: 10 env: WAF_IPSET_SCOPE: {YOUR_WAF_IPSET_SCOPE} WAF_IPSET_REGION: {YOUR_WAF_IPSET_REGION} WAF_IPSET_ID: {YOUR_WAF_IPSET_ID} WAF_IPSET_NAME: {YOUR_WAF_IPSET_NAME} permissions: id-token: write contents: read steps: - uses: actions/checkout@v3 - name: authenticate to aws uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ secrets.AWS_DEFAULT_REGION }} role-to-assume: ${{ secrets.AWS_IAM_ROLE_ARN }} - name: Get IP address id: get-ip run: echo "ip=$(curl -s https://checkip.amazonaws.com)" >> $GITHUB_OUTPUT shell: bash - name: Add IP address to ipset shell: bash run: | current_ip=${{ steps.get-ip.outputs.ip }}/32 # このワークフローを並列で同時実行しても動くように、元から存在するIPをリストから消さないように更新する lock_token=$(aws wafv2 get-ip-set --scope $WAF_IPSET_SCOPE --region $WAF_IPSET_REGION --id $WAF_IPSET_ID --name $WAF_IPSET_NAME | jq -r '.LockToken') existing_ips=$(aws wafv2 get-ip-set --scope $WAF_IPSET_SCOPE --region $WAF_IPSET_REGION --id $WAF_IPSET_ID --name $WAF_IPSET_NAME | jq -r '.IPSet.Addresses[]') aws wafv2 update-ip-set --scope $WAF_IPSET_SCOPE --region $WAF_IPSET_REGION --id $WAF_IPSET_ID --name $WAF_IPSET_NAME --addresses $existing_ips $current_ip --lock-token $lock_token sleep 10 # ipsetの更新が反映されるまで待つ # ~~ IP制限しているリソースへのリクエスト付きの処理 ~~ - name: Delete IP address from ipset if: always() shell: bash run: | ip_to_remove=${{ steps.get-ip.outputs.ip }}/32 lock_token=$(aws wafv2 get-ip-set --scope $WAF_IPSET_SCOPE --region $WAF_IPSET_REGION --id $WAF_IPSET_ID --name $WAF_IPSET_NAME | jq -r '.LockToken') existing_ips=$(aws wafv2 get-ip-set --scope $WAF_IPSET_SCOPE --region $WAF_IPSET_REGION --id $WAF_IPSET_ID --name $WAF_IPSET_NAME | jq -r '.IPSet.Addresses[]') # このワークフローを並列で同時実行しても動くように、元から存在するIPをリストから消さないように更新する updated_ips=() for ip in $existing_ips; do if [ "$ip" != "$ip_to_remove" ]; then updated_ips+=("$ip") fi done if [ ${#updated_ips[@]} -eq 0 ]; then updated_ips_string='[]' else updated_ips_string=$(printf "%s " "${updated_ips[@]}") fi aws wafv2 update-ip-set --scope $WAF_IPSET_SCOPE --region $WAF_IPSET_REGION --id $WAF_IPSET_ID --name $WAF_IPSET_NAME --addresses $updated_ips_string --lock-token $lock_token
設定値は以下です
・YOUR_WAF_IPSET_SCOPE: CLOUDFRONT か REGIONAL
・YOUR_WAF_IPSET_REGION: ipsetの存在するリージョン(CLOUDFRONTの場合、globalではなくus-east-1を指定する必要がありました)
・YOUR_WAF_IPSET_ID: 添付図参照
・YOUR_WAF_IPSET_NAME: 添付図参照
解説
・処理の流れは大きく以下になってます
- 実行環境のIP取得
- 1で取得したIPをipsetにinsert
- IP制限環境へのリクエスト
- 2で登録したIPをipsetから削除
・aws wafv2 では、v1やセキュリティグループの仕様とは違って、ipsetのリストへそのまま任意のIPを挿入/削除することはできないようでした。 (おそらく、AWSの内部的なデータ整合性の観点だと思われます)
そのため、一度入れ替え更新のようなコードになっています。
This operation completely replaces the mutable specifications that you already have for the IP set with the ones that you provide to this call. To modify an IP set, do the following: 1. Retrieve it by calling GetIPSet 2. Update its settings as needed 3. Provide the complete IP set specification to this call