有名テック企業の技術ブログを、ひとつのフィードで。
フィード
31件
こんにちは、株式会社タイミーでMLOpsエンジニアをしているKYです。普段はMLプラットフォームの構築・運用を担当しています。 実務の中でコンテナイメージのサプライチェーンセキュリティ強化を進めており、その一環として Docker 社が提供する「Docker Hardened Images(DHI)」の実装を辿る機会がありました。 その際、実際の定義ファイルを見て、少し驚きました。コンテナのビルド定義といえば「Dockerfile」が当たり前だと思っていたのですが、DHI の定義はなんと YAML で書かれていたのです。 「なぜ Dockerfile ではないのか?」と定義の読み方を追いかけていくうちに、BuildKit のアーキテクチャに行き着きました。この記事では、DHI の仕組みを通じて、私たちが普段の Dockerfile 運用で押さえるべきポイントを再確認したいと思います。 ビルド定義の主役は「Frontend」 BuildKit は、「定義を解釈する部分(Frontend)」と「実際にビルドを実行する部分(Backend)」に分離しています。Frontend は、入力(Dockerfile や YAML)を BuildKit の中間表現(LLB)に変換する役割を持ちます。 ここで鍵になるのが、ファイル先頭のコメント行 # syntax=... です。BuildKit はまずこの1行を読み、どの Frontend で後続を解釈するかを決めます。つまり、Docker 公式が推奨しているのに見落とされがちな以下の1行は、単なるコメントではなく「このファイルは公式の Dockerfile Frontend で解釈してほしい」という宣言です。 # syntax=docker/dockerfile:1 一方で DHI の定義ファイルを開くと、YAML の1行目に次の指定があります。 # syntax=dhi.io/build:2-debian13 YAML も # をコメントとして扱うため、BuildKit から見れば「# syntax= から始まるビルド定義」という意味で入口は同じ。その後の中身を YAML として解釈するのは、差し替えられた DHI Frontend の仕事というわけです。 DHI は何をしているのか:YAML をコンパイルする DHI の定義ファイル(YAML)は、RUN apt-get... のようにといった手順を重ねるのではなく、「最終的に何を入れるか」という状態を宣言します。 【DHI の YAML 定義例(実際の定義ファイルからの抜粋)】 # syntax=dhi.io/build:2-debian13 name: Debian 13 Base image: dhi.io/debian-base variant: runtime platforms: - linux/amd64 - linux/arm64 dates: release: "2025-08-09" end-of-life: "2028-08-09" contents: packages: - '!libelogind0' - '!mawk' - '!original-awk' - base-files - bash - ca-certificates - coreutils # ... 以下、ベースに含めるパッケージの列挙が続く accounts: run-as: nonroot users: - name: nonroot uid: 65532 gid: 65532 cmd: - /bin/bash いくつかのフィールドに注目してみます。 contents.packages: !mawk のように ! プレフィックスを付けると「明示的に含めない」パッケージを宣言できます。削除手順を書くのではなく、最初から「入れない」と表明する点が Dockerfile との大きな違いです。 accounts.run-as: nonroot: 実行ユーザーを非 root に固定する宣言で、Dockerfile の USER 命令に相当します。Dockerfile のように RUN useradd ... といったユーザ作成手順を書く必要はなく、「誰で動かすか」という状態だけが残る点が特徴です。 dates.end-of-life: イメージのライフサイクル終了日まで定義に含まれており、運用上の管理情報もビルド定義の一部として扱われています。 このように、DHI の YAML は「どう作るか」ではなく「何が入っていて、誰が動かすか」を宣言しています。そしてここで重要なのは、BuildKit が YAML を直接ビルドしているわけではないという点です。 DHI の Frontend がこの YAML を読み込んで中間表現(LLB)へコンパイルし、あとは通常通り BuildKit がビルドを実行します。つまり、DHI の YAML は「別言語」ではなく、Frontend を差し替えて得た “別の入力形式” なのです。 たとえば不要パッケージの除外ひとつとっても、Dockerfile では apt-get remove → autoremove → キャッシュ削除と手順を重ねる必要があります。一方、DHI なら - '!mawk' の1行で意図が完結します。手順(How)ではなく意図(What)だけが残るため、セキュリティ監査や再現性の面で有利です。DHI が宣言的定義を採用しているのは、こうした相性の良さがあるからです。 忘れられがちな Dockerfile の公式推奨設定 今後、DHI のような宣言的フロントエンドがすぐに主流になるかは未知数であり、当面は既存の Dockerfile 運用が続くでしょう。 しかし、DHI が示す「Frontend は明示し、選ぶものである」という観点は重要です。まずは Docker 公式が推奨する以下の2行を、忘れずに Dockerfile の先頭へ記述しましょう。 # syntax=docker/dockerfile:1 # check=error=true # syntax=... 使用する Frontend を固定し、手元の環境と CI の違いによるビルド結果の揺れを防ぎます。 # check=error=true BuildKit の静的解析(lint)を強め、警告レベルの記述を CI で弾けるようにします。 これらを習慣づけるだけで、「Frontend を明示し、品質を保つ」文化に確実に近づきます。 まとめ DHI から学べる本質は、BuildKit は Frontend を自由に差し替えられるという点にあります。この視点を持つと、DHI は単なるセキュアなベースイメージではなく、ビルド定義の抽象度を一段上げる試みとして見えてきます。 「手順を書く」から「状態を宣言する」への移行は、Infrastructure as Code で何度か見てきた流れと重なって見えます。DHI を触ってみて、その発想がコンテナビルドの入力形式にも持ち込まれていることを実感しました。 将来的にビルドのパラダイムがどう変わるにせよ、まずは見逃されがちな # syntax=... と # check=... をきちんと置くこと。タイミーでも Cloud Run / Vertex AI Pipelines の DHI 移行を進める中で、Frontend 指定の差がビルド結果の揺れに直結する場面に何度か遭遇し、この2行の重要性を改めて感じました。DHI がもたらした視点を持ちつつ、足元の運用を公式のベストプラクティスで堅牢にする。これが、現実的で安全なコンテナ運用の第一歩です。 参考文献 Docker Hardened Images - カタログリポジトリ Debian 13 Base 定義ファイル(13.yaml) — 記事中の YAML 定義例の抽出元 Custom Dockerfile syntax - Docker Docs Build hardened images - Docker Docs We're Hiring! サプライチェーンセキュリティや ML 基盤の足回りに興味を持っていただけたなら、ぜひ一緒に働きませんか。タイミーでは、ML プラットフォームの構築・運用やサプライチェーンセキュリティの強化に取り組むエンジニアを募集しています! 少しでも興味を持っていただけましたら、ぜひ以下のリンクから詳細をご覧ください。 MLOpsエンジニア シニアMLOpsエンジニア 募集ポジション一覧
こんにちは、株式会社タイミーで MLOps エンジニアをしている KY です。普段は ML プラットフォームの構築・運用を担当しています。 私たちのチームでは、機械学習エンジニアやデータサイエンティストが開発に集中できるよう、VS Code のリモート開発(Remote SSH および Dev Container)を活用した開発環境を提供しています。本記事では、その中でも 共通 Dev Container Feature によるガードレール にフォーカスし、各チームが自分たちで開発環境を立ち上げられることを前提にしながら、セキュア・バイ・デフォルトをどう実現しているかをご紹介します。 なぜ Dev Container Feature にガードレールを寄せるのか この記事を書こうと思ったきっかけは、もともと機械学習エンジニアやデータサイエンティスト向けだった開発環境を、データアナリストをはじめとする別職種のメンバーにも広げ始めたことでした。ユーザー層が広がるにつれ、「どこまでを各自の設定に任せ、どこからを仕組みで縛るか」をあらためて考え直す必要が出てきた、というのが出発点です。あわせて、組織として求められるセキュリティレベルも年々高まってきています。 ML プラットフォーム特有の事情として、ユーザーの専門領域が幅広い、という点があります。機械学習エンジニアやデータサイエンティストはモデリングやデータ分析を主戦場としており、依存パッケージの脆弱性管理やコンテナの権限設計といった領域は、本来の業務の中心ではないことが多いです。だからこそ、これらをユーザー個々の習熟度に委ねるのではなく、プラットフォーム側で初期値を配る方針を取りました。各チームがセルフサービスで開発環境を立ち上げられ、特別な設定をしなくても初期状態でセキュリティのベースラインが担保される状態を目指しています。推奨パスに乗るだけで安全に進められる、いわゆる「ゴールデンパス」の発想であり、セキュア・バイ・デフォルトを仕組みで成立させるアプローチです。 この方針を devcontainer.json レベルで素直に表現できる仕組みが Dev Container Feature でした。Feature を1行足すだけで宣言的にガードレールが適用されるため、「各チームが自律的に環境を立ち上げつつ、危険な操作だけは仕組みで塞ぐ」という設計とよく噛み合っています。 共通 Dev Container Feature によるガードレール 私たちの開発環境では、共通化した Dev Container Feature(以下、共通 Feature)を配っています。まず、ベースイメージと Feature の役割は明確に分けています。Docker Hardened Images(以下、DHI)をベースにした開発用イメージでは、各種開発ツール(Python / uv / gcloud / Claude Code など)をインストールしておきます。共通 Feature では、それらツールの設定ファイル配置とガードレール適用のみを担います。 この前提のもと、各チームの devcontainer.json は以下のようにシンプルで、ベースイメージを指定し、共通 Feature を追加するだけで、後述するガードレールがまとめて適用されます。 { "image": "asia-northeast1-docker.pkg.dev/<PROJECT>/<CUSTOM_DHI_PATH>:<TAG>", "features": { "asia-northeast1-docker.pkg.dev/<PROJECT>/<CUSTOM_FEATURE_PATH>:<TAG>": {} } } こうしてレイヤーを分けておくと、ツールの入れ物とポリシーの適用が混ざらずに整理されるため、よりセキュアに締めやすいという体感があります。たとえばポリシー側だけを Renovate で継続的に更新していけるので、イメージの差し替えと独立してセキュリティ設定の追従・レビューを回せます。なお、ベースイメージ側で押さえるべきリスク(OS パッケージの脆弱性など)と、Feature 側で押さえるべきリスク(ツールの権限・設定)をどう切り分けるかといった論点もあります。ただし本記事のスコープ外のため、詳細は割愛します。 この Feature がプロビジョニング時に各種設定ファイルを配置し、ガードレールを自動で効かせます。実際には複数のツール設定を同じ方式で配布していますが、本記事では代表例として AI エージェントの制御を取り上げます。 Claude Code などの AI エージェントの制御 昨今、Claude Code のような AI コーディングエージェントが普及していますが、無制限の権限を与えると破壊的変更や意図しないデータ送信のリスクがあります。共通 Feature は /etc/claude-code/managed-settings.json を自動生成し、システムレベルで制御を行います。 { "strictKnownMarketplaces": [ { "source": "github", "repo": "<ORGANIZATION>/<REPOSITORY>" } ], "allowedMcpServers": [ { "name": "<APPROVED_MCP_NAME>", "command": "..." } ], "permissions": { "deny": [ "Bash(sudo:*)", "Bash(gcloud:*)", "Read(~/.config/**)" ] } } ※ 実際の設定から一部を抜粋しています。 プラグインマーケットプレイスと MCP サーバーは、社内で承認されたもののみに制限しています(ホワイトリスト形式)。また、sudo や gcloud などの権限昇格・クラウド操作、~/.config/ 配下の機密情報へのアクセスといった危険な操作は、Deny リストでブロックしています。ユーザー側の settings.json では上書きできない managed settings として配置しているため、「うっかり緩めてしまう」ことを構造的に防げます。 Feature に寄せることの嬉しさ これらを共通 Feature として提供していることで、以下のようなメリットが得られています。 各チームの devcontainer.json は Feature を1行足すだけでよく、セキュリティ設定の知識なしにベースラインを満たせる。 Feature のバージョンを上げるだけで、全社的にガードレールを一括更新できる(Renovate で自動 PR される)。 設定の出所が Feature に集約されているため、監査やレビューの対象が明確になる。 実際に運用してみると、Renovate の PR を1本マージするだけで全チームの Claude Code 設定が同時に更新されるのは、想像していた以上に運用が軽くなったと感じています。 補足:周辺で効かせている多層防御 共通 Feature だけで全てを押さえようとせず、周辺の仕組みと組み合わせて多層防御を成立させています。ベースイメージには DHI を採用してコンテナ起動時点でのベースラインを引き上げ、ホストとなる Remote SSH 用 VM 側にも同等のポリシーを展開し、依存関係は Dependabot / Renovate で継続的に追従させる、という具合です。 おわりに 今回は、MLOps チームが 共通 Dev Container Feature を使って、ML 開発環境のガードレールをどのように設計・運用しているかをご紹介しました。 振り返ってみると、ツールは DHI イメージ、設定は共通 Feature、更新は Renovate と責務を分けておくと、それぞれに対するレビューや更新のサイクルを独立して回しやすいのが大きな利点でした。ガードレール自体を作ることよりも、ガードレールを錆びさせない構造に落とすことが、各チームの自律性を損なわずにベースラインを引き上げていくうえでの要だったように思います。 参考文献 Claude Code - System settings: /etc/claude-code/managed-settings.json に関する公式ドキュメント Dev Containers - Features: Dev Container Feature の仕様 こうした「セキュア・バイ・デフォルトな ML 開発環境」を、より多くのチームと一緒に磨き込んでいきたいと考えています。 We're Hiring! タイミーでは、ML プラットフォームの構築・運用やセキュアな開発環境の整備に一緒に取り組んでいただけるエンジニアを募集しています! 少しでも興味を持っていただけましたら、ぜひ以下のリンクから詳細をご覧ください。 MLOpsエンジニア シニアMLOpsエンジニア 募集ポジション一覧
こんにちは。昨年度まで社会人大学院生(修士課程)として学び、無事卒業した Hunachi です 🙌 研究生活の中で、SICS 2026 と DEIM 2026 に参加し、論文の執筆や発表、ポスター発表をしてきました。 私の研究内容は「Android搭載端末での pKVM 環境を使ったセキュアな声紋認証の実装と評価」です 👀 このブログでは、 私が研究で扱っている pKVM ってなに? どんな研究をしていたのか(ざっくり) 学会に参加したり、論文を書いて発表してみての感想 社会人大学院生をしてみた感想 以上の4 本立てで、私の研究や大学院生活について紹介していきます。 SCISは函館開催でした。その時に食べたごっこ汁 🐟 pKVM ってなに? モバイル端末でも「セキュアな実行環境」が欲しい 最近のスマートフォンでは、生体認証・決済・オンデバイス AI(Gemini Nano など)と、機密性の高い処理を端末上で動かす場面がどんどん増えていますよね。 Android でのセキュアな環境としては 2014 年から Trusty TEE(Trusted Execution Environment)という ARM TrustZone ベースの隔離環境が使われてきました。Android の一般的なアプリが動作する環境(REE: Rich Execution Environment)とは、ハードウェアレベルで分離されたセキュアな環境です。そのため、堅牢なセキュリティを実現できます。 ただし TEE には以下の弱点があります。 利用できるメモリが数 MB 程度ととても小さい 開発のハードルがそれなりに高い 端末のベンダーによってセキュリティの質がまちまち 特に利用できるメモリが少ないので、DNN モデルなどを動かすのは大変困難です 😖 pKVM の登場 そこで Android 13 から導入された Android Virtualization Framework(AVF) の中核として、pKVM(Protected KVM) という仮想化技術が組み込まれました。 ざっくり言うと、 ベースは Linux 由来の KVM(Kernel-based Virtual Machine) そこに「ホスト OS からも触れない VM(Protected VM, pVM)」という概念を載せる 端末の物理メモリ容量いっぱいまで使える隔離環境が手に入る という、Trusty TEE のメモリ制約を解消した比較的新しい技術です 🚀 ちなみに数年前、「Pixel で root を取らずに Linux(Arch や Ubuntu)が動かせる」という話題、目にした方もいるんじゃないでしょうか。Danny Lin 氏の Nestbox というアプリで Android 上に Linux VM を立ち上げるものです(参考記事)。この基盤になっているのがまさに pKVM で、「ホスト OS から保護された VM」という枠組みを使えば、セキュリティ用途だけでなく汎用的な OS だってホストできてしまう、というのを実証した一例です。 pKVM のアーキテクチャをざっくり ARM のアーキテクチャでは、特権レベルが Exception Level(EL) という階層で分かれています。pKVM 環境に関する階層分けはこのようになっています。 EL2: pKVM ハイパーバイザ EL1: Android Host OS と Protected VM EL0: ユーザアプリケーション EL2 で動く pKVM がステージ 2 ページテーブルを使って、ホスト OS からの pVM メモリへのアクセスを物理的に遮断します。さらに IOMMU を使うことで、DMA デバイス経由の不正アクセスもブロックしてくれます。 また、pKVM上で動かすプログラムはC/C++で書く必要がありますが、TEE向けアプリの開発に比べれば容易です。 セキュアな環境を成り立たせる仕組み pKVM(AVF)の凄いところは、ただメモリを隔離するだけじゃない点です。 pvmfw(Protected VM Firmware)がペイロードの署名を検証して改ざん検知 DICE(Device Identifier Composition Engine)プロトコルで pVM ごとのシークレットを導出 DICEで導出したシークレットからsealing secretを生成し、さらにsealing keyを作成して永続データなどを暗号化 pVM 終了時にはハイパーバイザがメモリページをゼロクリアして残留防止 つまり、コードの正当性 → 起動時のシークレット → 永続データ → 終了時の残留防止 まで一貫してハイパーバイザがケアしてくれる、という設計です。 そして 2025 年 8 月、Google が pKVM で SESIP Level 5 認証を取得したと発表しました 🎉 SESIP(Security Evaluation Standard for IoT Platforms)は IoT デバイス向けセキュリティ評価基準で、Level 5 は最高レベルです。大規模消費者向けに展開されるソフトウェアセキュリティシステムとして取得したのは世界初で、最新かつかなりセキュアな技術であることがわかります(Google Online Security Blog)。 私の研究をざっくり やったこと ここからは自分の研究をかなりざっくり紹介します。 タイトルは「Google Tensor 搭載端末の pKVM におけるセキュアな音声処理および声紋認証の実装手法と課題の検討」です。 論文はこちらから読めます 👉 DEIM2026 3D-01 すごく簡単に言うと、 (pKVM環境)上で話者識別のDNNモデルを動かし、実用可能な処理速度で動作する声紋認証システムアプリを実現 pKVM のメモリアクセス特性を細かく測定 提案システムの認証精度・処理時間・pKVMのVM 起動時間などを多角的に評価 を行った論文です。 そしてありがたいことに、この発表で DEIM 2026 学生プレゼンテーション賞 をいただきました 🎉 一緒に研究を進めてくれた共著の先生方、コメントをくださった皆さん、本当にありがとうございました 🙇 まだまだ改善の余地がたくさんある研究内容ですが、興味のある方は論文を読んでもらえると嬉しいです 🙇 学会の感想 SICS に参加した感想 SICSは、以前は暗号系の発表が多かったようですが、最近は傾向が変わってきたようです。セキュリティ関連の発表では、高レイヤの話も多く見られました。特にLLMのセキュリティや研究方法に関する講演や発表が印象的でした。最先端のLLMの研究をしている日本人研究者もいることや、LLMのセキュリティの研究がどこまで進んでいるかの話を聞くことができ、面白かったです。 DEIM に参加した感想 たくさんの学生さんが参加している学会で、色々な研究の発表やポスター発表を見ることができました。特に土日にリモート開催だったので、社会人の私にとって大変嬉しかったです。LINEヤフーさんのDBの話なども興味深く聞かせていただきました。 最近の研究は、やはりLLM関連が多く、自分も研究でLLMも扱えるよう、ある程度は詳しくならないといけないと思いました。 論文執筆・発表・ポスター発表をしてみた感想 学部時代の研究をそのまま続けなかったこともあり、成果が出せる研究テーマにたどり着くまで時間がかかり、とても大変でした。一方で、先生方の助言やAIの活用により、先行研究や最新技術の調査を効率化できました。その結果、成果を出せてよかったです。 また論文を執筆するにあたり、慣れない部分については、AIに手助けしてもらいながら執筆しました。4年前の学部時代や高専時代に論文を書いた時と比べて、LaTeXのエラーに悩まされる時間や、誤字脱字の修正にかかる時間が、ほぼゼロになりました。本当に楽な時代になったなと感じます。 発表では厳しめの質問をいただくこともありましたが、それ以上に嬉しいこともありました。似た研究をしている方が少ないにもかかわらず、特にDEIMでは私の研究に興味を持って質問してくださる方が多く、とても嬉しかったです。 人に自分の研究内容を伝えることは、社会人におけるプレゼンテーションを行う際にも活かせるなと思いました。 社会人大学院生(修士課程)をしてみた感想 大学の教授やD進している同期、夫の家事サポートがあったからこそ、卒業できました。関係者の皆さんに感謝しかありません。 人におすすめできるかというと、とても忙しい生活スタイルになるため、研究が趣味な人以外にはおすすめしにくいです。ただ、AIの活用で調査や文章執筆が容易になった今の時代だからこそ、「チャレンジは可能だ」と思います。 私の感じたメリット・デメリット メリットは、金銭的な問題で困りにくいことです。いろいろな理由があり、猫と暮らしている自分には働かないという選択肢がなかったため、社会人学生を選びました。働きつつ学生でいることを許してくれた大学の教授には感謝しかありません。そのおかげで猫と暮らしつつ学費も安定して払うことができました。 デメリットは以下のとおりです。 大学以外のことをするプライベートな時間がかなり少なくなること 研究に時間を費やす必要があるのはもちろんのこと、学会や授業の参加で有給が消費されます 仕事や大学が忙しい時期には睡眠時間以外はパソコンの前にいる、というような不健康な生活が日常になること 学生らしい生活ができないこと 私の場合は、大学に行く時間が取れず在宅で研究を行なっていた関係で、友人と研究室でおしゃべりしたり、飲み会や合宿への参加などはできませんでした。 また私は、学部時代に大学院の授業単位を取得できる制度を活用していたため、大きな問題はありませんでした。ただし、大学や単位の取得状況によっては、授業のために有給を使う必要が出てくるかもしれません。さらに、大学生らしい生活が送れないのはもったいないと感じるため、個人的には可能であれば通常の大学院生として通うほうがよいと思います。 ※ 私の大学生活のほとんどはコロナでオンラインだった関係で大学生活をまともにしたことがないので意見が偏っている可能性もあります。 ただ、事情があり社会人になる必要がある人やすでに社会人の方で、研究をしたい・続けたい人は十分頑張ってみる価値があると思うので応援しています 🚩 おわりに 引き続きpKVMや研究関連の勉強は続けようと思っています 🧑🎓 最後まで読んでくださってありがとうございました!
こんにちは。タイミーのデータエンジニアリング部 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) = \"\\.\" \\}"] } ] } 設定項目の説明 <h3 id="configbest-practices-を土台にす
はじめに はじめまして、プラットフォームエンジニアリング本部に所属している徳富(@yannKazu1)です。 みなさん、サプライチェーン攻撃って気にしてますか? npm パッケージの乗っ取り(ua-parser-js 事件)、GitHub Actions の改ざん(tj-actions/changed-files 事件)、依存パッケージへのバックドア混入(xz-utils 事件)……。ここ数年、OSS を取り巻くセキュリティの前提がガラッと変わってきています。正直、「いつ・どこから仕掛けられるかわからない」状況です。 しかもサプライチェーン攻撃って、攻撃側のコストが低いわりに被害範囲が広いのが厄介なんですよね。 そんなわけで、ECS Fargate 環境におけるサプライチェーン攻撃対策を整理してみようと思ったのですが、いきなり全部を洗い出そうとしてもカオスになるだけ。何かいいフレームワークはないかな……と探していたところ、Kubernetes の 4C セキュリティモデル(Cloud → Cluster → Container → Code) の考え方がそのまま使えそうだったので、これをベースにチェックシート的に整理してみました。 「うちの環境だとどこが手薄いんだろう?」を考えるときの参考にしてもらえればと思います。 おことわり: これをやれば完璧!というものではないです。あくまで「見通しよく整理するための道具」として 4C モデルを借りているだけなので、実際にどこまでやるかは環境やリスク許容度に応じて取捨選択してください。 整理に使う 2 つの軸 軸 1:4C セキュリティモデル —「どこを守るか」 Kubernetes の公式ドキュメントで紹介されている、クラウドネイティブセキュリティを 4 つの同心円レイヤー で捉えるモデルです。 参考: クラウドネイティブセキュリティの概要 | Kubernetes ┌─────────────────────────────────────────┐ │ Cloud(クラウド基盤) │ │ ┌─────────────────────────────────────┐ │ │ │ Cluster(オーケストレーター) │ │ │ │ ┌─────────────────────────────────┐ │ │ │ │ │ Container(コンテナランタイム) │ │ │ │ │ │ ┌─────────────────────────────┐ │ │ │ │ │ │ │ Code(アプリケーション) │ │ │ │ │ │ │ └─────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────┘ │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────┘ ポイントは 各レイヤーが外側のレイヤーの上に構築されている ということ。どれだけアプリのコードを堅牢にしても、基盤レイヤーのセキュリティが低い水準では守りきれません。だからこそ、特定のレイヤーだけに頼るのではなく、すべてのレイヤーを固める 多層防御(Defense in Depth) が基本方針になります。 ECS Fargate への読み替えはこんな感じです。 4C レイヤー K8s での意味 ECS Fargate での対応 サプライチェーン攻撃での主な攻撃面 Cloud クラウド基盤 AWS アカウント・IAM・ネットワーク IAM キー漏洩、ECR への不正 push Cluster オーケストレーター ECS クラスター・CI/CD パイプライン CI/CD アクションの改ざん、パイプライン侵入 Container コンテナランタイム Docker イメージ・Fargate タスク ベースイメージの汚染、OS パッケージへのバックドア混入、実行時の不正プロセス Code アプリケーションコード ソースコード・依存パッケージ パッケージ乗っ取り、typosquatting、悪意ある PR 軸 2:対策の目的 —「何のために守るか」 4C モデルは「どこを守るか」を整理するフレームワークですが、それだけだと対策が偏りがちです。そこでもうひとつ、 「何のために守るか」 という軸を加えてみます。今回は、セキュリティ対策を以下の 4 つの目的に分類して整理してみました。 目的 説明 考え方 🛡 予防(Prevention) 攻撃を未然に防ぐ そもそも悪いものを入れさせない 🔍 検知(Detection) 攻撃や脆弱性を発見する 入り込んだ・紛れ込んだことに気づく 🧱 封じ込め(Containment) 侵入後の被害を最小化する やられても被害を広げさせない 🔎 調査(Investigation) 何が起きたかを追跡する 事後に原因と影響範囲を特定する よくある落とし穴は 「予防」ばかりに意識が向いて、他が手薄になる こと。完璧な予防は不可能なので、入り込まれた後にどう気づいて・どう被害を抑えて・どう調べるか、まで含めて考えるのが多層防御の本質です。 この記事の構成 本記事では 目的(予防・検知・封じ込め・調査)を大項目 にして、それぞれの中で 4C のどのレイヤーに対する対策か を整理していきます。 🛡 予防(Prevention)— そもそも入れさせない 攻撃を未然に防ぐための対策です。「入口を塞ぐ」イメージですね。 Cloud:VPC Endpoint 概要: AWS サービスへの通信をインターネットを経由せずに VPC 内で完結させる。 防げる攻撃: 侵害されたタスクからの外部 AWS アカウントへのデータ持ち出し (Endpoint Policy で自社アカウントに限定) マルウェア感染後の C2 通信・情報送信 (Egress 全遮断下でも AWS サービスは利用可能) 漏洩した IAM 認証情報による外部からの不正アクセス (バケットポリシーで aws:SourceVpce を指定) 設定のポイント: S3 Gateway Endpoint(無料)は必須 ECR、SSM、Secrets Manager、CloudWatch Logs 用の Interface Endpoint を検討 Endpoint Policy で aws:PrincipalAccount を制限 リソース側ポリシーで aws:SourceVpce を指定 Cluster:CI/CD パイプラインのハードニング 概要: GitHub Actions など CI/CD で使うサードパーティアクションを、改ざんされない形で固定する。 防げる攻撃: GitHub Actions の改ざん(tj-actions/changed-files 事件のように、人気アクションのリポジトリが侵害されてタグが書き換えられるケース) バージョンタグの上書きによる 意図しないコードの実行 設定のポイント: GitHub Actions は通常 uses: actions/checkout@v4 のようにタグやブランチで指定しますが、これらは 後から書き換え可能 です。tj-actions/changed-files 事件(2025 年 3 月)では、攻撃者がメンテナーの認証情報を侵害し、既存タグを悪意あるコミットに向け直すことで、汚染されたアクションを使う CI でシークレットがビルドログに書き出されるという被害が広範囲に発生しました。一方、commit SHA でピンニングしていたユーザーは影響を受けませんでした(侵害期間中に対象 SHA へ更新していなければ)。 対策として、commit SHA でピンニングする のが推奨されます。 # Before(タグ指定 - 書き換えられる可能性あり) - uses: actions/checkout@v4 # After(commit SHA でピンニング - 改ざんされない) - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 Dependabot や Renovate Bot で SHA の自動更新を設定 permissions: で各ジョブの GITHUB_TOKEN 権限を最小化 OIDC を使った AWS 認証に切り替え、長期クレデンシャルを廃止 パブリックリポジトリでは特に注意:ビルドログが公開されるため、ログ経由のシークレット漏洩のインパクトが大きい サプライチェーン攻撃との関連: これは Container レイヤーの digest ピンニングと同じ思想です。「同じ名前で違うものを掴まされる」攻撃を防ぐには、暗号学的なハッシュで内容を固定するのが基本になります。 Cluster:Secrets の安全な注入 概要: DB パスワードや API キー等の機密情報を、コードへの直書きではなく SSM Parameter Store や Secrets Manager から注入する。 防げる攻撃: ソースコードやコンテナイメージへのシークレット埋め込み を防止 Git リポジトリの漏洩時に クレデンシャルが直接露出するリスク を排除 KMS 暗号化により、AWS アカウントが侵害されても 暗号化キーなしでは復号不可能 設定のポイント: "secrets": [ { "name": "DATABASE_PASSWORD", "valueFrom": "/fargate/myapp/database-password" } ] Cluster:ECS Exec の制御 概要: ECS Exec(コンテナへの対話的アクセス)を必要時以外は無効化する。 防げる攻撃: IAM クレデンシャルが漏洩した場合の コンテナへの直接侵入 本番コンテナへの 不正なコマンド実行 設定のポイント: ECS Exec の制御は、サービス(または RunTask 呼び出し)レベルと IAM レベルの二重で行うのが確実です。enableExecuteCommand はタスク定義のフィールドではなく、サービス(CreateService / UpdateService)または RunTask のパラメータです。 サービス定義(または RunTask 呼び出し)で無効化:enableExecuteCommand = false を明示。そもそもサービス側で受け付けない状態にしておく。AWS CLI でも aws ecs update-service --no-enable-execute-command のように切り替え可能です。 IAM で ecs:ExecuteCommand を Deny:ECS Exec 専用の API なので、これを Deny するのが最も直接的。なお ECS Exec は内部的に SSM Session Manager の通信レイヤー(ssmmessages API)を利用するため、より厳密に制御したい場合は、タスクロールに ssmmessages:* 系の権限が紛れ込んでいないかも確認する 必要時のみ一時的に有効化する運用フロー:踏み台用の専用サービスを別途用意し、調査時のみそちらを起動する運用が安全。 補足: 後述の封じ込めセクションで紹介する readonlyRootFilesystem: true と ECS Exec は 両立しない 点に注意してください。SSM agent がコンテナファイルシステムへの書き込みを必要とするため、ルートファイルシステムを読み取り専用にすると ECS Exec が動きません(AWS 公式ドキュメントでも明記されています)。本番では readonlyRootFilesystem: true + ECS Exec 無効化、調査用の踏み台サービスでは ECS Exec 有効化、と用途で使い分けるのが現実的です。 Container:ベースイメージの digest ピンニング 概要: Dockerfile のベースイメージ指定を、タグだけでなく digest(SHA256 ハッシュ)で固定する。 防げる攻撃: ベースイメージのタグ上書き攻撃(同一タグに悪意あるイメージを push) レジストリが侵害された場合の イメージ改ざんの検知 設定例: # Before(タグのみ) FROM ruby:3.3.0-bookworm # After(digest ピンニング) FROM ruby:3.3.0-bookworm@sha256:2e1e76e5b2... 運用のポイント: Dependabot や Renovate Bot で digest の自動更新を設定する digest がまだ付いていないイメージに digest を自動付与する pinning 自体は、現時点では Renovate のほうが運用面で先行(pinDigests プリセット)。Dependabot 側でも 2026 年 2 月に PR #14071 が dependabot-core 本体にマージされ、docker_pin_digests という experiment flag として実装されました。ただし experiment flag は Dependabot サービス側で段階的に展開されるため、GitHub.com の Dependabot で「タグだけのイメージに digest を新規付与する挙動」がデフォルトで使えるかはタイミング次第です(フラグの有効化状況によっては自分の環境で効かないこともある点に注意)。 現時点で「すでに digest 付き」のイメージに対する digest 更新は Dependabot でも問題なくできます。タグだけで運用しているイメージを一括で digest ピン化したい