TECH PLAY

セキュリティ

イベント

マガジン

技術ブログ

本ブログは 2026 年 5 月 19 日に公開された AWS Blog、” CIRT insights: How to help prevent unauthorized account removals from AWS Organizations ” を翻訳したものです。 AWS Customer Incident Response Team (CIRT) は、お客様がアクティブなセキュリティインシデントから復旧するためのご支援を行っています。この活動の中で、特定の お客様の構成や設計 を悪用する、新しいまたは流行している攻撃手口を発見することがしばしばあります。 これらの手口を理解することは、アーキテクチャ上の意思決定への反映、対応計画の改善、そして実際にこのような状況が発生した場合の検出に役立ちます。 本投稿では、攻撃者がお客様アカウントの制御を奪取した後に取る新しいアプローチを取り上げます。具体的には、お客様の AWS Organizations 実装から該当アカウントを離脱させ、その構造が提供するポリシーや保護を回避する手口です。 本記事で説明する手口は、AWS サービスの脆弱性を利用するものではありません。代わりに、特定の構成や設計によって生じた予期しない機会を悪用し、AWS アカウント内のリソースを不正に使用するものです。 何が起きているのか このアプローチは、攻撃者が organizations:LeaveOrganization 権限の付与を持つクレデンシャルを使用するところから始まります。この権限は LeaveOrganization API コール へのアクセスを提供し、メンバーアカウントから呼び出されると、そのアカウントを Organization から離脱させようとします。 重要な点として、このアプローチでは侵害されたルートクレデンシャルが使われる場合もありますが、攻撃者は他の手段でアクセス権を昇格させ、必要な権限を取得したり、その権限を持つロールを引き受ける能力を獲得したり、現在のクレデンシャルにこの権限を付与する能力を獲得したりすることもできます。これが、認可に対して 最小権限のアプローチ を取ることが、お客様の環境を保護する上で極めて重要である理由です。詳細については、 AWS Identity and Access Management (IAM) ドキュメント と、 組織単位 (OU) 設計および サービスコントロールポリシー (SCP) 実装に関する AWS Organizations のガイダンスをご覧ください。 お客様の環境への影響 アカウントが Organization から離脱させられると、その Organization の一部として継承されていた制限 (破壊的なアクションを防止していた SCP、利用可能な AWS リージョンを制限していたもの、特定の API コールをブロックしていたもの等) が適用されなくなります。また、当該アカウントは一括請求 (Consolidated Billing) の対象外となるため、Organization の請求アラートやコスト異常検知も該当アカウントの活動をカバーしなくなります。 AWS CloudTrail の組織トレイルは離脱したアカウントからのイベント取得を停止し、委任管理者を介して管理されていた Amazon GuardDuty の検出結果も中央のセキュリティアカウントへ流れなくなります。 その結果しばしば発生するのは、Organization が当該アカウントへの可視性を失う一方で、そのアカウント内には引き続き Organization のリソースが残るという状況です。関連する Threat Technique Catalog のエントリを以下に示します。 T1078.A002: Account Root User : 侵害されたルートクレデンシャルを利用した初期アクセス T1078.004: Cloud Accounts : 侵害された IAM クレデンシャルを利用した初期アクセス T1098: Account Manipulation : 制御を維持するための権限昇格とアカウント設定の変更 T1666.A002: Leave AWS Organization : SCP やガバナンスコントロールを回避するため、メンバーアカウントを Organization から離脱させる T1562.008: Disable Cloud Logs : Organization からの離脱後、中央集約型ロギングの可視性が失われる この手口の検知 アカウントが Organization からの離脱を試みると、CloudTrail には少なくとも 2 つの API コールが記録されます。 organizations:AcceptHandshake と organizations:LeaveOrganization です。中央集約型のロギングを構成している場合、これらのイベントが侵害アカウントから観測される最後のイベントとなる可能性があります。Organization からの離脱後、デフォルトではアカウント内のイベントは自身の CloudTrail ログに記録されることになります。アカウントが Organization に参加または離脱する際に関連する CloudTrail イベントを以下に示します。これらのイベントは、AWS Organizations を管理するためにチームが利用する承認済みの運用ワークフローの一部でない限り、調査が必要です。 CloudTrail イベント 意味 LeaveOrganization メンバーアカウントが Organization から離脱しようとしている AcceptHandshake アカウントが別の Organization への参加招待を承諾している InviteAccountToOrganization Organization がアカウントを招待している RemoveAccountFromOrganization 管理アカウントがメンバーアカウントを削除している (メンバー自らが離脱する場合とは異なる) この手口を防ぐための推奨ステップ organizations:LeaveOrganization アクションを拒否する SCP を実装してください。AWS Organizations は この制御の実装に関する詳細なガイダンス を提供しており、具体的な SCP ポリシー JSON や、本番環境および開発環境のアカウントには保護を維持しつつ正当なアカウント移行を許容できる OU 構造の設計に関するアドバイスが含まれています。 SCP は、メンバーアカウント内で IAM ポリシーが許可できる範囲を制限するガードレールとして機能します。AWS Organizations をご利用のすべてのお客様には、この SCP が現在配置されているかを確認し、配置されていない場合には実装に向けた手順を踏むことを強く推奨いたします。この SCP は迅速にデプロイでき、運用上の影響も最小限です。メンバーアカウントを Organization から分離することを慎重に管理・検討するためのプロセスを提供します。 このアクションは、ルートだけでなく organizations:LeaveOrganization 権限を持つあらゆる侵害された IAM プリンシパルから発生し得るため、IAM 権限の最小権限原則は重要な補完的な制御となります。ユーザーやロールがポリシーの追加・削除・変更を行ったり、別のロールを引き受けたり、自身の権限を変更したりできる範囲を制限することで、不正な権限変更が行われる経路を減らすことができます。IAM ポリシーを定期的にレビューし、過度に広範な権限 (特に iam:AttachRolePolicy 、 iam:AttachUserPolicy 、 iam:PutRolePolicy 、および広範な信頼ポリシーを伴う sts:AssumeRole ) を確認することは、侵害されたプリンシパルが実行できる範囲を制限するのに役立ちます。 ルートアカウントのセキュリティは引き続き重要です。ルートの侵害がこのパターンの一般的な侵入経路となるためです。すべてのルートユーザーに対して多要素認証 (MFA) を有効化し、ルートアクセスキーを削除し、メンバーアカウントからルートクレデンシャルを完全に取り除く ルートアクセスの一元管理 を採用することで、リスクの軽減につながります。 今後について 本手口は、私たちが様々なエンゲージメントを通じて目にしている、より広範なテーマを浮き彫りにしています。攻撃者は AWS のガバナンスコントロールがどのように機能するかをますます認識しており、Organization が提供する制御からアカウントを切り離すための意図的な手段を取っています。AWS CloudTrail を無効化する、Amazon GuardDuty ディテクターを削除する、Organization からアカウントを離脱させるといった行為は、いずれも同じ戦略の派生形にあたります。すなわち、本来であれば攻撃者の活動を制約し、お客様による対応を支援するはずのガードレールと可視性から、お客様のアカウントを切り離すというものです。 これを防ぐための制御は本日時点で利用可能であり、実装も簡単です。 AWS Organizations サービスチームのガイダンス から始め、 DenyLeaveOrganizationSCP を実装することをお勧めします。本手口に対して、最も効果が大きく、かつ最も労力の少ない制御です。それ以外にも、OU 構造全体での SCP のカバレッジを見直すこと、すべてのメンバーアカウントでルートクレデンシャルと IAM 権限が適切に保護されていることを確認すること、検知・対応プロセスが本手口を考慮に入れていることを確かめることが、より強固なセキュリティ態勢に貢献します。 Threat Technique Catalog for AWS には、根底にある手口の検知ガイダンスが含まれています。 関連リソース Threat Technique Catalog for AWS – Matrix T1078.A002: Account Root User T1078.004: Cloud Accounts T1098: Account Manipulation T1666.A002: Leave AWS Organization AWS Organizations における不正なアカウント離脱を防止するための重要なセキュリティコントロール メンバーアカウントのルートアクセスを一元管理する AWS Organizations サービスコントロールポリシー Amazon GuardDuty AWS CloudTrail ユーザーガイド 本投稿に関するフィードバックがありましたら、下のコメントセクションにご投稿ください。 著者について Shannon Brazil Shannon は AWS Customer Incident Response Team (CIRT) のセキュリティエンジニアであり、デジタルフォレンジックとクラウドセキュリティ調査を専門としています。コミュニティでは 4n6lady として知られ、セキュリティ教育と次世代の防御者の育成に情熱を注いでいます。 Derek Ramirez Derek は AWS Customer Incident Response Team (CIRT) のセキュリティエンジニアです。サイバーセキュリティと、困難なインシデントレスポンスの課題への対処を支援する AI ツールの構築という、自身が情熱を注ぐ 2 つのことを組み合わせて取り組んでいます。オースティンのダウンタウンを走ったり、ゴルフのショートゲームに取り組んだり、Dallas Cowboys を熱心に応援したりしています。 Richard Billington Richard は AWS Customer Incident Response Team (アクティブなセキュリティイベント中に AWS のお客様をサポートするチーム) のアジア太平洋地域における Sr. Security Engineer です。 翻訳は Security Solutions Architect の 松崎 博昭 が担当しました。
ARMプロセッサのセキュリティ技術「トラストゾーン(TrustZone)」は、1つのCPU上に安全な「セキュアワールド」と通常の「ノーマルワールド」という2つの独立した実行環境を構築します。本記事では、このトラストゾーンが指紋認証や動画配信サービスのDRMといった身近な技術でどのように利用されているかを解説します。また、その構成要素であるCA(Client Apps)、TA(Trusted Apps)、Trusted OSの役割を説明し、セキュアなアプリケーション開発における重要性を紹介します。
こんにちは。タイミーのデータエンジニアリング部 DSグループでMLOpsを担当しているYukitomoです。 私たちのチームでは多くのPythonアプリをモノレポで管理していますが、Dependabotによる依存関係更新PRが多すぎることが運用課題でした。本記事では、Renovateへの移行によって「更新PRの粒度と数をコントロールできる運用」を実現するまでの設計判断と、Python + uv環境特有の注意点を共有します。 この記事の想定読者 Pythonのモノレポ環境で、複数のアプリケーションやライブラリを運用している方 Dependabotが生成する大量の更新PRの対応に疲弊しており、運用を効率化したい方 Renovateへの移行を検討している、または導入したが設定(packageRules)のベストプラクティスに悩んでいる方 パッケージマネージャーに uv を採用している(または検討している)方 要約(TL;DR:この記事でわかること) 本記事では、Python + uv環境でRenovateを運用する際の課題とその解決策(新しすぎるパッケージの除外設定、Google Cloud WIFにおけるブランチ名の文字数制限の回避など)を整理し、実践的なrenovate.json5の設定ノウハウを解説します。 背景 近年はサプライチェーン攻撃が現実的なリスクになっており、Trivyの侵害以降も Python モジュールや JS ライブラリを狙った攻撃が継続して観測されています。PyPI など外部エコシステムに依存する以上、これまで以上に「依存関係をどう安全に運用するか」を真面目に考える必要があります。 一方で、依存関係を「安全に」保つには、継続的にアップデートを回し続ける必要があります。ここで次の課題になるのが、運用対象が増えたときに更新対応のコストがスケールしてしまう点です。 私たちもDependabot運用の効率化を進めてきましたが *1 、アプリごとにパッケージ管理へ移行した結果、モノレポ内のpyproject.tomlが増えました。2026年5月時点では、DSグループだけでも約70のPythonアプリケーション/ライブラリを扱っています。Dependabotは脆弱性の検知とPR作成を行ってくれる一方で、依存関係ごとにPRが分割されます。そのため、対象が増えるほど対応コストが急増します。 そこでこの課題を解決するため、Renovateを導入し「更新をまとめて扱える運用」へ切り替える方針にしました。本記事では、公式ドキュメントや公開されている設定例を参考にしつつ、私たちが重視した設定ポイントを整理します。 この記事の前提 言語: Python 依存関係ファイル: pyproject.toml / uv.lock 動作環境: GitHub & Google Cloud 目的: Renovateで「脆弱性対応」と「定常アップデート」を破綻なく回す(PRの数と粒度をコントロールする) 設定ファイル(.renovaterc.json5) 設計方針 私たちが設定で重視したのは以下の3点です。 PRの粒度をコントロールする — patch / minor / vulnerability を適切にグルーピングし、PRの本数を削減する サプライチェーンリスクを軽減する — 公開直後のバージョンを即座に採用しない 小さく始める — まず許可リスト方式で必要な更新だけを有効化し、段階的に広げる 全体像 以下が設定ファイルの抜粋です(各設定の詳細は後述)。 // .renovaterc.json5 より一部抜粋 { extends : [ " config:best-practices " ] , minimumReleaseAge : " N days ", lockFileMaintenance : { enabled : true , branchTopic : " lfm ", // GCP WIF 127-byte limitに対応するためブランチ名を省略 minimumReleaseAgeBehaviour : " timestamp - optional " // 一時的な対応 } , vulnerabilityAlerts : { groupName : " maintenance ", groupSlug : " maint ", minimumReleaseAge : " 14 days " , } , packageRules : [ // packageFileDirをブランチ名に含めつつGCP WIF 127-byte limitに対応するためブランチ名を省略 { matchFileNames : [ " base_containers/base/** " ] , additionalBranchPrefix : " {{{replace 'base_containers/base/' 'b_b/' packageFileDir}}}/ " , } , // packageRuleを一旦無効化 { matchPackageNames : [ " ** " ] , enabled : false } , // グルーピング ---------------------------------------------------- // ルール 1: { matchUpdateTypes : [ " patch " ] , enabled : true , groupName : " maintenance ", groupSlug : " maint " , } , // ルール 2: { matchUpdateTypes : [ " minor " ] , matchJsonata : [ " isVulnerabilityAlert = false " ] enabled : true , groupName : " minor updates ", groupSlug : " minor ", dependencyDashboardApproval : true } , // ルール 3: { matchPackageNames : [ " ** " ] , matchJsonata : [ " isVulnerabilityAlert = true " ] enabled : true , // この2つは vulnerabilityAlerts で設定した値で上書きされます groupName : " maintenance ", groupSlug : " maint " , } , // マイナーレベルでの破壊的な更新の抑制 ただし脆弱性対応を除く { matchJsonata : [ " isBreaking = true and not(isVulnerabilityAlert) " ] , enabled : false , }, ] , // バージョンの更新 bumpVersions : [ { bumpType : " patch ", filePatterns : [ " {{packageFileDir}}/pyproject.toml " ] , matchStrings : [ " version \\ s*= \\ s* \" (?<version>[^ \" ]+) \" " ] } , { bumpType : " patch ", filePatterns : [ " {{packageFileDir}}/uv.lock " ] , matchStrings : [ " name = \" [^ \" ]+ \"\\ nversion = \" (?<version>[^ \" ]+) \"\\ nsource = \\ { (?:editable|virtual) = \"\\ . \" \\ } " ] } ] } 設定項目の説明 config:best-practices を土台にする Renovateは設定可能な項目が多く、ゼロから組むと必ず設定が肥大化します。そこでまずは extends: ["config:best-practices"] をベースにして、一般的に安全側なデフォルトを取り込みました。 ベース設定を取り込んだうえで、運用上の要所(PRの粒度、セキュリティ例外、ロックファイルの扱い)だけを packageRules で上書きしています。 minimumReleaseAge (新しすぎるリリースを避ける) サプライチェーン観点では「出たばかりのバージョンを即座に拾う」ことが常に正解とは限りません。そこで minimumReleaseAge を設定し、公開直後のバージョンを一定期間は自動採用しないようにしています。 ここで一つ注意点があります。 minimumReleaseAge が効くのは、Renovateが pyproject.toml のバージョン指定を直接書き換える通常の更新(Standard Update)だけです。 一方、 pyproject.toml には記載されず uv.lock にだけ現れる間接依存(依存パッケージがさらに依存しているパッケージ)の更新は、Renovateの lockFileMaintenance が担当します。 lockFileMaintenance は内部で uv lock を実行しますが、現時点ではRenovateから uv lock に --exclude-newer 等のオプションを渡す手段がありません *2 。つまり、間接依存に対しては minimumReleaseAge による「新しすぎるバージョンの除外」が効かないのです。 そこで、uv自身が持つ exclude-newer 機能で補完しています。各 pyproject.toml に以下のように記述することで、 uv lock 実行時にuv側で公開直後のバージョンを除外します *3 。 # pyproject.toml [tool.uv] exclude-newer = "n days ago" # uv 0.10+ で相対日付指定が可能 # uv.lock (uv lock 実行時、以下のように変換されて保存されます) [options] exclude-newer = "2026-04-07T08:39:48.633055Z" exclude-newer-span = "PnD" この二重構成により、直接依存は Renovate の minimumReleaseAge 、間接依存は uv の exclude-newer でそれぞれカバーし、すべての依存パッケージに対して「新しすぎるバージョンを即座に採用しない」制約を適用しています。 lockFileMaintenance (Google Cloud Workload Identity Federationの制約対策とminimumReleaseAgeBehaviourの調整) 明示的に有効化します。また、プライベートなモジュールを独自のArtifactoryやNexusなどのパッケージインデックスに配置・利用することはよくあると思います。我々はGoogle Cloudを利用しており、プライベートなパッケージは Google Artifact Registry に保管しています。この場合、RenovateからGoogle Cloudにアクセスが発生し、Workload Identity Federation (WIF) を使う構成だと、subject claimにブランチ名が入ります *4 。ここに127 bytes制約があり、Renovateが生成するブランチ名が長いと認証に失敗します。対策として、作成するブランチ名を短縮化するため、 branchTopic の値を短縮しています(デフォルトでは “lock-file-maintenance”)。 この値を調整して通常の更新とブランチ名を一緒にし、PRを一緒にできないかと試したのですが、 Grouping lockfile maintenance with other update types is not supported というエラーメッセージが出たため断念しました。 minimumReleaseAgeBehaviour は、本来はデフォルト値の "timestamp-required" のほうが自然です。ただし、(*2)のrenovate のPRがマージされるまでは "timestamp-optional" にしておかないと、 lockFileMaintenance のPRがRenovate botの承認待ちになってしまうため注意してください。 vulnerabilityAlerts AIエージェントの助けを借りてRenovateのコードを確認し、試行錯誤する中で気づいたのですが、脆弱性対応に関する一部の設定は、packageRules で指定してもグローバルの vulnerabilityAlerts の値で上書きされます。具体的には、後述するグルーピングルール3の内容や、前項の minimumReleaseAge の設定です。 packageRules 以下、設定の意図をダイジェスト順に説明します。 1) packageFileDirをブランチ名に追加 複数のモジュールの更新を集約するために packageFileDir を additionalBranchPrefix に利用しています。モノレポにおけるadditionalBranchPrefixの一般化( packageFileDir 由来のprefixを使う等)の詳細は別記事 *5 に委ねます。lockFileMaintenanceの項と同様、ブランチ名が長くなるため、Google Cloud Workload Identity Federation (WIF) の制約対策のためにブランチ名を短縮しています。 2) いったん全ルールを無効化して「許可リスト方式」にする { matchPackageNames: ["*"], enabled: false } 最初に全パッケージを enabled:false に落としてから、必要な更新だけを後続ルールで enabled:true に戻しています。Defaultを使いこなす方が安全とは思うのですが、まず最初は小さく、自分達でコントロールできる範囲から始めようとしてこのような設定としました。 3) PRのグルーピング(patch / minor / vulnerability) 別記事(*5)にもありますが依存関係更新の運用コストは「PRの数」と「レビューのコンテキスト切り替え」で決まるので、グルーピングが最重要です。 グルーピングルール1: patch更新: まとめて1本(メンテナンス枠) グルーピングルール2: minor更新: まとめて1本(ただし dependencyDashboardApproval:true で人間の許可待ちにする) グルーピングルール3: vulnerability: patchと同じグループに合流させ、優先して処理できるようにする Minor更新は、まずはダッシュボードで確認する運用にします。運用がうまく回りそうであれば、将来的にpatch groupingに合流させる想定です。 また、renovateのconfigは後ろの条件が前の条件を上書きするため、グルーピングルール3は最後に配置する必要があります。4)で扱う isBreaking に関する packageRule も同様に、後ろに配置してください。 4) 0.y.z系の“実質breaking”を抑止する SemVer上はminorでも、 0.y.z のようにリリースされていない場合、minor更新がbreakingになり得ます。そこで isBreaking = true を検知した更新は原則止めています。 ここは「通常アップデートは保守的に、ただし脆弱性対応は止めない」方針にしたいため、脆弱性アラート( isVulnerabilityAlert:true )にはこの抑止が効かないようにしています(=脆弱性があるものはIsBreakingでも検出させる)。 bumpVersions RenovateをGitHubで動作させるには、1) GitHub Actionsとして renovatebot/github-action を利用する方法と、2) 作成元のMend社が提供するMend Renovate Appを利用する方法があります。設定が簡単なことからタイミーでは後者を利用しているのですが、それ故の制約もあり、 postUpgradeTasks のように任意のコマンドを実行するようなことができず、コマンド実行できれば簡単なversionの更新もそのままでは実現できません *6 。解決策として、 bumpVersions を使い、正規表現で pyproject.toml と uv.lock の両方を同時に更新しています。なおbumpVersionsは オフィシャルドキュメントの最初にはpackageRulesの外の記述例があるのですが、マッチ表現と組み合わせpackageRulesの中に記述することもできます *7 。上記のサンプルではpackageRulesの外で記述していますが、我々はlockFileMaintenanceとそれ以外でbump up の方法を一部変えるため、 matchUpdateTypes を lockFileMaintenance とそれ以外で分離し、packageRulesの中に両方を記述しています。 // 応用例: lockFileMaintenanceと通常の更新を別々に記述する場合 (packageRulesの中に記述する) packageRules : [   : { matchUpdateTypes : [ " lockFileMaintenance " ] , bumpVersions : [ .. ] } , { matchUpdateTypes : [ " major ", " minor ", ... ] , bumpVersions : [ .. ] } ] まとめ Dependabotの「依存ごとにPRが分割される」性質は、対象が増えるほど脆弱性対応の運用コストを押し上げる。 Renovateは packageRules を適切に設定することで、上記の運用コストの削減を行うことができる。 Python + uv の場合、 lockFileMaintenance のオプションとして指定できない exclude-newer については pyproject.toml に直接記述することで問題を回避できる。 Mend Renovate Appを利用する場合においても、bumpVersionsを利用することで pyproject.toml / uv.lock それぞれのversionの更新を実現できる。 We’re Hiring! 現在、タイミーでは、データサイエンスやエンジニアリングの分野で、共に成長し、革新を推し進めてくれる新たなチームメンバーを積極的に探しています!  現在募集中のポジションは こちら です! また、気軽な雰囲気での カジュアル面談 も随時行っておりますので、ぜひお気軽にエントリーしてください。↓ 「話を聞きたい」と思われた方は、是非一度 カジュアル面談 でお話ししましょう! References *1 : https://tech.timee.co.jp/entry/2024/10/15/101953 *2 : uv lock コマンド自体は --exclude-newer オプションを持つのですが、Renovateからこのオプションを2026年5月13日現在渡せていません。他のパッケージマネージャーに関しても同様でIssueとして登録されており、uvに関しては既にPRも出ているようですがリリースはまだされていません。 https://github.com/renovatebot/renovate/issues/41652 *3 : 0.10より古いバージョンのuvでも exclude-newerは記述できるのですが ”N days ago” のような相対的な評価がこのバージョンから記述できるようになりメンテナンスが非常に楽になっています。 https://github.com/astral-sh/uv/releases/tag/0.10.0 *4 : https://docs.cloud.google.com/iam/docs/troubleshooting-workload-identity-federation#error-google-subject-too-long *5 : 近日公開予定 *6 : uvを利用する場合、 pyproject.toml / uv.lock に記述された自身のversionの更新はuv version --bump patchのように実現できます。 *7 : https://docs.renovatebot.com/configuration-options/#bumpversions

動画

書籍