「GitHub Actions × AWS」のトレーサビリティ向上委員会
GitHub ActionsからAWSを操作できると便利です。OIDC(OpenID Connect)を使えばアクセスキーも不要で、セキュアに運用できます。しかし複数システムにまたがる特性上、トラブルシューティングは面倒です。そこで本記事ではトレーサビリティを向上させ、メンテナンスしやすくする手法を紹介します。トレーサビリティが向上すれば、運用はもっと楽になります。
GitHub ActionsとAWSは赤の他人
当たり前ですが、GitHub ActionsとAWSはまったく異なるシステムです。何を言っているんだという感じですが、トレーサビリティの観点では重要です。なぜなら複数のシステムを扱うときは、トレースできるよう設計しないとトレーサビリティは生まれないからです。
GitHub ActionsやAWSに限らず、普通は他所様のシステムなど知ったこっちゃありません。このお互いに知らんぷりを決め込んでいるシステムの仲立ちは、我々の仕事になります。まったくもって手のかかるヤツらですね。
Configure AWS Credentialsアクション
GitHub ActionsからAWSへアクセスするには、Configure AWS Credentialsアクションが便利です。認証方式にはもちろんOIDCを使います。OIDCでAWSへアクセスするには、事前にAWS側でOpenID Connect ProviderとIAMロールのセットアップが必要です。本記事では、これらのリソースは準備済みという前提で話を進めます[1]。
GitHub Actionsのワークフローでは、次のようにConfigure AWS Credentialsアクションを呼び出します[2]。これだけでAWSのクレデンシャルが払い出されます。
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/github-actions-role
aws-region: ap-northeast-1
各入力パラメータには次の値を指定します。
-
role-to-assume
:事前に作成しておいたIAMロールのARN -
aws-region
:デフォルトリージョン
ここで使用しているIAMロール名は「github-actions-role」です。AWSアカウントIDはコンフィギュレーション変数(Configuration Variables)へ事前に追加しておいたものを利用します。
単に動かすだけならこれで終わりですが、お世辞にもトレーサビリティが高いとはいえません。そこでこのコードをデフォルト設計として、少しずつ改善していきましょう。
トレーサビリティとはなにか
GitHub ActionsやAWSは、自身のシステムについてはよく知っています。問題は自身のシステムと相手側システムの関連付けです。ある事象が自システムで起きた時、相手側で何が起きていたかどうやって知ればよいでしょうか。ここで登場するのがトレーサビリティです。本記事ではトレーサビリティを次のように定義します。
- GitHub Actionsの実行ログから、AWS CloudTrail[3]のログをたどれる
- AWS CloudTrailのログから、GitHub Actionsの実行ログをたどれる
つまりどちらかのログを眺めているときに、関連する相手側のログを探し出せればOKです。このとき重要なのは、どれだけ簡単に相手側のログにたどり着けるかです。実運用を考慮すると、可能な限り詳細を知らなくてもたどれる状態が望ましいでしょう。なぜならログを参照するのは、内部実装に詳しい人とは限らないからです。
くどくど書きましたが、そろそろ実践に移りましょう。設計のポイントは大きく3つです。
- セッション名にGitHub Actionsの情報を付与する
- AWSアカウントIDをGitHub Actionsのログに書き出す
- AWS CloudTrailのURLをGitHub Actionsのステップサマリーへ書き出す
セッション名の設定
Configure AWS Credentialsアクションでは入力パラメータに、セッション名を指定できます。ここで設定したセッション名は、AWS CloudTrailが自動的にログへ含めてくれます。
そこでオススメなのは「セッション名にGitHub Actionsの情報を含めてしまう」ことです。そうすればAWS CloudTrailから、一意にGitHub Actionsの実行ログへ到達できます。セッション名には任意の値を組み込めますが、筆者は次のような実装にしています。
- name: Generate session name
id: session
run: |
repo="${GITHUB_REPOSITORY#${GITHUB_REPOSITORY_OWNER}/}"
echo "name=${repo}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "${GITHUB_OUTPUT}"
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-session-name: ${{ steps.session.outputs.name }}
......
Generate session nameステップでセッション名を生成し、Configure AWS Credentialsアクションのrole-session-name
キーへ渡します。セッション名は次のような構造で組み立て、GitHub Actionsとの関連付けに必要な情報を詰め込みます[4]。
これは相関IDの役割を担います。各要素の意味は次のとおりです。
- リポジトリ名:
<org-name>/<repo-name>
の「<repo-name>
」部分[5] - ワークフロー実行ID:GitHub Actionsのワークフロー実行時に生成される識別子
- ワークフロー試行回数:何度目のワークフロー実行か[6]
なおセッション名には任意の値を組み込めると述べましたが、使える文字種や長さには制限があります。使用可能文字は半角英数と特殊文字「=,.@-_
」で、最長64文字です[7]。
これをexampleというリポジトリで実装すると、たとえばexample-4181401234-1
のようなセッション名が生成されます。またAWS CloudTrailには、次のようなログが記録されます。
{
"eventVersion": "1.08",
"userIdentity": {
"type": "AssumedRole",
"principalId": "XXXXXXXXXXXX:example-4181401234-1",
"arn": "arn:aws:sts::123456789012:assumed-role/github-actions-role/example-4181401234-1",
"accountId": "123456789012",
"accessKeyId": "XXXXXXXXXX",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "XXXXXXXXXXXX",
"arn": "arn:aws:iam::123456789012:role/github-actions-role",
"accountId": "123456789012",
"userName": "github-actions-role"
},
......
}
},
"eventTime": "2023-02-15T07:02:13Z",
"eventSource": "sts.amazonaws.com",
"eventName": "GetCallerIdentity",
"awsRegion": "ap-northeast-1",
......
}
このようにAWS CloudTrailのログを見れば、GitHub Actionsの実行ログと関連付けが可能です。ただし可能なだけで、たどりやすいわけではありません。GitHub Actionsの実行ログへアクセスするには、次のような規則でURLを自力で組み立てます。
とはいえGitHub Actions側のログを一意に識別できるだけでも、何もやらないよりマシです。普段はAWSからGitHub Actionsへたどる頻度も多くないため、出発点としては悪くありません。
ちなみにConfigure AWS Credentialsアクションでは、セッション名を省略するとデフォルト値「GitHubActions
」が使われます。AWS側からGitHub Actionsの実行ログを一意に識別する情報は、何も記録されません。時刻で何となく当たりをつける運用になります。イマイチです。
AWSアカウントIDの出力
次にGitHub ActionsからAWSへたどりやすくします。あまり知られていませんが、Configure AWS Credentialsアクションを使うと、ログ出力時にAWSアカウントIDをマスクしてしまいます。
おそらくセキュリティ向上のため、このような設計になっていると思われます[8]。しかしトレーサビリティの観点ではイマイチです。複数AWSアカウントの運用は珍しくないためです。AWSアカウントIDがマスクされると、どのAWSアカウントを操作したか不明瞭になります。
そこでマスク処理を無効化しましょう。次のようにConfigure AWS Credentialsアクションのmask-aws-account-id
キーで設定します。
uses: aws-actions/configure-aws-credentials@v2
with:
mask-aws-account-id: false
......
ただしお気づきのとおり、マスク処理の有無にはトレードオフがあります。
- マスク処理の有効化
- メリット:万が一ログが漏れても、第三者にAWSアカウントIDが渡らない
- デメリット:どのAWSアカウントを操作したか分からなくなる
- マスク処理の無効化
- メリット:どのAWSアカウントを操作したか明確に分かる
- デメリット:万が一ログが漏れると、第三者にAWSアカウントIDが渡る
AWSアカウントIDをどれだけセンシティブに扱うかで、評価は変わります。個人的にはプライベートリポジトリであれば、AWSアカウントIDはログ出力したほうがよいと考えています。ログが漏れてる時点で、AWSアカウントIDだけ秘密にできても効果に疑問符がつくからです。どうせセキュリティ的にたいした効果がないなら、トレーサビリティを向上させるのは悪くない判断です[9]。
AWS CloudTrailのURL出力
先ほど紹介したセッション名を使うと実は、AWS CloudTrailのログがフィルタリングできます。AWSマネジメントコンソールにログインした状態で、次のURLをブラウザで開きましょう。
するとGitHub Actions経由のログだけにフィルタリングされます。かなり便利です。
URLは手動で組み立ててもよいですが、ステップサマリーに出力するのもオススメです。AWSアカウントIDと一緒に出力すれば、ログの場所が一目瞭然です。たとえば次のコードを追加します。
- name: Output step summary
env:
AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }}
SESSION_NAME: ${{ steps.session.outputs.name }}
CLOUDTRAIL_URL: https://console.aws.amazon.com/cloudtrail/home#/events
run: |
echo "## Authorized on AWS (${AWS_ACCOUNT_ID})" >> "${GITHUB_STEP_SUMMARY}"
echo "- ${CLOUDTRAIL_URL}?Username=${SESSION_NAME}" >> "${GITHUB_STEP_SUMMARY}"
ワークフローを実行するとGitHub Actionsのログページに、次のようなリンクが出てきます。
このリンクをクリックすれば、GitHub Actions経由の操作ログが簡単に確認できます。AWSアカウントへの事前ログインは必要ですが、かなりトラブルシューティングがしやすくなります。なおステップサマリーへの出力ではなく、プルリクエストのコメントに貼り付けても便利です。
ログ保存期間
少し脇道にそれますが、ログ保存期間に触れておきます。たとえば社内に「本番環境の操作ログは半年間保持する」などの規定がある場合は要注意です。AWS CloudTrailのログ保存期間は正しく設定しているのに、GitHub Actions側の設定を忘れるケースがあります。
GitHub Actionsのログ保存期間はデフォルトで90日です。これで足りない場合は、明示的な設定が必要です。ログ保存期間はリポジトリ単位で設定できます。Setting the retention period for a repositoryを参考に、適切なログ保存期間を設定しておきましょう[10]。
すべてを統合する
それではここまでの内容を統合します。まずは次の事前準備を行いましょう。
- OpenID Connect ProviderとIAMロールをAWSに作成する
-
AWS_ACCOUNT_ID
という名前でコンフィギュレーション変数を設定する
そのうえで次のようなコードを実装します。
name: AWS
on: push
jobs:
aws:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
id-token: write
steps:
- name: Generate session name
id: session
run: |
repo="${GITHUB_REPOSITORY#${GITHUB_REPOSITORY_OWNER}/}"
echo "name=${repo}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "${GITHUB_OUTPUT}"
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
mask-aws-account-id: false
role-session-name: ${{ steps.session.outputs.name }}
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/github-actions-role
aws-region: ap-northeast-1
- name: Output step summary
env:
AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }}
SESSION_NAME: ${{ steps.session.outputs.name }}
CLOUDTRAIL_URL: https://console.aws.amazon.com/cloudtrail/home#/events
run: |
echo "## Authorized on AWS (${AWS_ACCOUNT_ID})" >> "${GITHUB_STEP_SUMMARY}"
echo "- ${CLOUDTRAIL_URL}?Username=${SESSION_NAME}" >> "${GITHUB_STEP_SUMMARY}"
- name: Run AWS CLI
run: |
aws sts get-caller-identity
たいしたコード量ではありませんが、細かい配慮が随所に散りばめられています。ひとつひとつは小さな工夫ですが、デフォルト設計と比べれば大幅にトレーサビリティが向上しています。
まとめ
本記事ではGitHub ActionsとAWS連携時の、トレーサビリティ向上手法を3つ紹介しました。
- セッション名にGitHub Actionsの情報を付与する
- AWSアカウントIDをGitHub Actionsのログに書き出す
- AWS CloudTrailのURLをGitHub Actionsのステップサマリーへ書き出す
これにより次のことが実現できました。
- GitHub Actionsの実行ログから、AWS CloudTrailのログをたどれる
- AWS CloudTrailのログから、GitHub Actionsの実行ログをたどれる
特にGitHub ActionsからAWS CloudTrailのログへたどるのは簡単で、トラブルシューティングも楽になります。たいして手間がかからないわりに、効果はバツグンです。ぜひお試しください。
-
詳細はConfiguring OpenID Connect in Amazon Web Servicesなどを参照してください。 ↩︎
-
本記事では読みやすさを優先して、サードパーティアクションの呼び出しはタグで行っています。しかし本番運用ではcommit SHAで固定すべきです。詳細はSecurity hardening for GitHub ActionsのUsing third-party actionsを参照しましょう。 ↩︎
-
AWS CloudTrailはAWSの監査ログサービスです。AWS APIの実行を記録するサービスです。もちろんGitHub ActionsからAWS APIを実行した場合のログも残ります。詳細は公式ドキュメントを参照してください。 ↩︎
-
コード内の
GITHUB_
で始まる環境変数は、デフォルト環境変数です。GitHub Actions実行時に勝手に値がセットされます。 ↩︎ -
GitHub Actionsではなぜか、org名抜きのリポジトリ名がデフォルト環境変数で取得できません。そのためシェル芸を駆使して、無理やりリポジトリ名を取得しています。分かりづらいのは重々承知してるんですが、他に方法が…。 ↩︎
-
ワークフローは再実行できます。そのためGitHub Actionsの実行ログを一意に識別する際、必要な情報になります。 ↩︎
-
セッション名の仕様はAssumeRoleWithWebIdentity APIのRoleSessionNameに記載されています。 ↩︎
-
もちろん結論は取り巻く状況によって変わるでしょうが、少なくとも検討してみる価値はあります。 ↩︎
-
ログ保存期間はOrganization単位でも設定は可能です。手順はConfiguring the retention period for GitHub Actions artifacts and logs in your organizationにあります。あなたがOrganization管理者なら、まとめて設定するとより確実です。 ↩︎
Discussion