【AWS CodePipeline】GitHubソースのVersion 1→ 2移行は大事故が起こる可能性があるため注意
tl;dr
- AWS CodePipelineのソースアクションである
GitHub バージョン1
を普通にGitHub バージョン2
へ切り替えると大事故が起こる可能性がある -
GitHub バージョン1
には不可分に一体な2つの設定がある- GitHubサイドでのwebhook設定
- CodePipelineのwebhookリソース (隠し設定)
- バージョン1→2で切り替えるときは上の2つの設定と等価な設定をトリガーへ追加すること
disclaimer (予防線)
以下では観測した事実をなるべく正しく記述し、同時に調べたことについても正しいように記述しましたが、一部調べきっていなかったり裏取りしていないことがあります。
具体的には、本件についてAWSサポートへ問い合わせておらず、AWSの公式ドキュメントもすべては読み込んでいません。
これは、今後CodePipeline + GitHubバージョン1から別の方法へ移行する方針であるため、深い調査をする価値が下がってしまったことが主な理由です。ドキュメントがかなりわかりづらく、すべてを読み取るのにかなりの時間がかかりそうだと思ったことも一因です。
上記の理由で過去の記事[1]ほどには調べ切っていないため、以下を読む際は一部に間違いがあったり、解釈に誤認がある可能性を踏まえて読んでください。
事故概要
12月の某日、開発組織内でGitHubのマシンユーザーの棚卸しが行われていました。
その中でAWS CodePipelineとGitHubの連携で使用されていることがわかり、折しもマシンユーザーに依存しない認証方式(バージョン2)があることもわかりました。そこで、SREチームが作業してバージョン2へ移行することでマシンユーザーを削除できるようにすることにしました。
私は2ヶ月前にチームへジョインしたばかりでしたが、SREとして5年ほど働いておりAWSを使って7年ほど仕事をしています。CodePipelineを触るのは久しぶりでしたが、あまり心配はしていませんでした。
作業に入り、development環境、staging環境での作業では特に問題が起こらなかったのですが、production環境で作業を行った後、今までGitHub上でReleaseを作成したらproduciton環境へデプロイされていたものが、mainブランチへpushしたらproduction環境へデプロイされるようになってしまったと報告が来ました。そのため慌てて全体へmainへマージしないようアナウンスします。
そして、私はそこから約12時間かけて復旧作業をすることになったのでした。
作業中、何をやったのか
GitHub Version 1 → GitHub Version 2の切り替え作業で行ったことは以下です。
- CodePipelineの画面で予めGitHubとの接続を作っておく
- パイプライン設定のソースのGitHubバージョン1の編集ボタンを押す
- リポジトリ名とブランチ名を控えておく (画像ではリポジトリ名は白で塗りつぶしています)
- GitHub (バージョン2) へ切り替える。接続は事前に作ったもの、リポジトリ名・ブランチ名は控えておいたものを貼り付ける
- 全体を保存
一見、一連の作業には違和感がないように見えます。一体何が問題だったのでしょうか?
GitHub Version 1ソースの背後に潜む2つの設定
結論から言うと、上で説明した一連の作業では、非常に重要な設定が削除されています。
それは次の2つです:
- GitHubサイドでのwebhook設定
- CodePipelineのwebhookリソース (隠し設定)
重要設定その1. GitHubサイドでのwebhook設定
GitHub バージョン1のソースでは、それに対応するようにGitHubリポジトリへwebhook設定が追加されます。
この設定は、CodePipeline側でソースを追加したときに自動で作成され、AWSのWeb UIだけを見ているとまったく気づくことができません。
そして、この設定はCodePipelineとの連携URLの設定であると同時にGitHub側のどのイベントが発生したときにCodePipeline側を発火させるのかも設定しています(下記参照)
重要設定その2. CodePipelineのwebhookリソース (隠し設定)
GitHubバージョン1のソースでは、それに対応するようにAWS CodePipeline内のwebhookというリソースが追加されます。
$ aws codepipeline list-webhooks
{
"webhooks": [
{
"definition": {
"name": "devabcdefghi--Source--bmsmsabcdefghi--12345678",
"targetPipeline": "dev-abcdefghi",
"targetAction": "Source",
"filters": [
{
"jsonPath": "$.action",
"matchEquals": "closed"
},
{
"jsonPath": "$.pull_request.merged",
"matchEquals": "true"
},
{
"jsonPath": "$.pull_request.base.ref",
"matchEquals": "main"
}
],
"authentication": "GITHUB_HMAC",
"authenticationConfiguration": {
"SecretToken": "****"
}
},
"url": "https://ap-northeast-1.webhooks.aws/trigger?t=eyJlbmNyeXB..................&v=1",
"lastTriggered": "2024-12-16T08:02:34.413000+09:00",
"arn": "arn:aws:codepipeline:ap-northeast-1:12345678901:webhook:devabcdefghi--Source--bmsmsabcdefghi--12345678",
"tags": []
}
]
}
この設定は、CodePipeline側でソースを追加したときに自動で作成され、AWSのWeb UIだけを見ているとまったく気づくことができません。
そして、このリソースの filters
という設定は非常に重要な役割を持っています。
これは、webhookが叩かれたとき、そのボディの中を評価して、それにマッチする場合のみ、CodePipelineの後続の処理を実行する、というものです。文字通り、ウェブフックの条件をフィルタリングする役割を果たしています[2]。
この2つの設定についてまとめると
- GitHubサイドでのwebhook設定
はGitHubが、どのイベント(Release? push ? pull request?)が発生したときにCodePipelineを発火するかの設定を保持し、
- CodePipelineのwebhookリソース (隠し設定)
は、来たイベントの詳細(ブランチ名や pre release なのか本releaseなのか)によって更に発火を続行するかを決めていました。
別の見方をします。
CodePipelineの基本的な考えでは、ソースで発生したイベントをフィルタリングする機能は、トリガーと呼ばれる箇所の役割です。
しかし、GitHubバージョン1ソースではこのトリガーで設定したい項目がCodePipelineのWeb UI上に収まっておらず、GitHub側のwebhook設定にはみ出していたり、CodePipelineのwebhook隠しリソースとして通常では編集できない位置にあると言えます。
私が冒頭のオペレーションミスを犯したのも、「CodePipeline的にはソースイベントのフィルタリングはトリガーとして設定されているのだから、ソースを変更してもフィルター設定は引き継がれるはず」と思い込んだことが原因でした。
しかし、ここまでの話は構造として非常にわかりにくくはあるもののそこまでひどいものではありません。
危うく本番環境がダウンしかけた要因、そして私が泣きながら12時間作業を行った要因は別のところにありました。
事故要因その1: 等価じゃないのに動いてしまう
あやうく本番環境をダウンさせかけた要因は、バージョン1からバージョン2への書き換えが等価ではないのに、動いてしまう ことです。
以下のソースの画面から見る限り、バージョン1とバージョン2は等価に見えます。
しかし、前述の通り、バージョン1はトリガーに当たる設定が全く別の場所にあるため、実はまったく等価ではありません。
これについての説明や警告がほぼなく、唯一の警告である以下も非常にわかりづらいため、それほど重要な警告ではないのだろうと作業を続行してしまいました[3]。
このソースアクションの変更にリソースの更新は必要ありません |
No resource updates needed for this source action change |
これらのメッセージの意味が30分調べてもわからなかった |
これで動かなくなってしまうのであればまだよかったのですが、まずいことにバージョン1では無視されていてブランチ名指定がバージョン2ではその通りに動いていしまうため、mainブランチのpushに対応して発火する設定になってしまいました。
この、間違っているのに何事もなく動いてしまうCodePipelineの設計・UIUX、フェイルセーフ的欠点が本番環境ダウン未遂の原因でした。
事故要因その2: 重要なリソースが消し去られてしまう
私が復旧に12時間の作業を必要としたのは、バージョン1→2の作業が前述の
- GitHubサイドでのwebhook設定
- CodePipelineのwebhookリソース (隠し設定)
を勝手に消してしまうためです。
なぜ12時間もの作業を必要としたかというと、パイプラインの数が50とかなり多かったことも一因ですが、両方の設定へ社内の開発がが手動オペレーションによる変更を加えられており、かつ、どの値にしたかが残っていなかったためです。
そのため、状況証拠や断片的な人の記憶・社内wiki上の記録などから推測し、設定する必要がありました。
それを50個のパイプラインそれぞれで行う必要があり、かつ本番環境のパイプラインでの設定ミスはダイレクトに障害につながるため、かなり慎重な調査と挙動確認が必要になりました。
再発対策
この問題を再発させないための根本対策を考えると、3つ挙げられます。
対策1: IaC
1つはIaCを使って以下の2リソースをコード管理することだと思います。
- GitHubサイドでのwebhook設定
- CodePipelineのwebhookリソース (隠し設定)
TerraformではGitHubのwebhookもコード管理できるので、Terraformが良いでしょう。
対策2: Githubバージョン2への移行
もう一つは、今回本来やりたかったGitHubバージョン2へ移行することです。
こうすることでパイプラインの発火条件はCodePipeline上のトリガーで完全に管理することができます。
今回の作業では背後の設定に気づかなかったため移行に失敗しましたが、削除されるリソースと等価な設定をトリガー項目へ追加してやれば基本的には移行できます。
ただし、今調べた限りではGitHubバージョン1では取れたReleaseなどのイベントがバージョン2では取れないようです。つまり完全な上位互換でもないようで、今回のケースではReleaseイベントを使ってデプロイしていたため、今回の障害対応ではトリガーを設定するのではなくバージョン1設定へロールバックする方向で対処しました。
※ ここについては深い調査をしていないため、実際に検討される際は各位での裏取り・検証などお願いします
対策3: GitHub Actions runners in AWS CodeBuildに移行する
実行管理をCodePipelineではなく、GitHub Actionsへ移す方式です。
実行インスタンス自体はCodeBuildにできるので、private subnetにあるDBへのアクセスなどは引き続き実現できます。
GitHubを中心に開発をしている場合、個人的にはCodePipelineよりGitHub Actionsのほうがメリットが大きいと感じているので、これが最もおすすめです。
- 過去に行った実行ログ: 「Self-hosted GitHub Actions runners in AWS CodeBuild」を使ったバッチ実行基盤 - エス・エム・エス エンジニア テックブログ
まとめ
ということで、本記事では素朴に認証方式を切り替えようとしたら大事故になった話を紹介しました。
GitHubバージョン1を使用しようとすると警告が出るようになったこともあり、同様の事故はすでに起こっているはずで、これから起こしてしまうところもあると思います。そこへの警告・情報共有・発生してしまったあとの対処ガイドとして、今回の事例を紹介しました。
-
AWS Identity Center下のIAMデータベース認証でも、トレーサビリティのある監査ログを記録する方法など ↩︎
-
この非常に重要な設定がWeb UI上で編集できないばかりか表示すらされていないことには正直唖然とさせられます。だからこそ、Githubバージョン2への移行を促しているという側面もあるのかもしれません ↩︎
-
言い訳になってしまうのですが、3年以上の経験があるSRE 2人で合計30分以上調べてもわかりませんでした ↩︎
Discussion