Docker Hardened Images と .NET
Docker Hardened Images とは
Docker Hardened Images(DHI) は、Docker 社が提供するセキュリティ強化済みのコンテナイメージです。通常の公式ベースイメージと比較して、不要なパッケージやツールを徹底的に取り除いた最小構成で提供されます。
主な特徴
| 項目 | 内容 |
|---|---|
| CVE | 継続的なパッチ適用によりほぼゼロを維持 |
| SBOM | 署名付きソフトウェア部品表(Software Bill of Materials)を提供 |
| Provenance | SLSA レベル 3 のビルド来歴検証 |
| ライセンス | Apache 2.0(Community プランは無料) |
| ディストリビューション | Alpine / Debian の両方をサポート |
通常の公式イメージとの主な違い
DHI は「最小限に絞った」設計思想のため、以下のツール・機能が意図的に除外されています。
apt-get/apkなどのパッケージマネージャbash/shなどのシェルcurl/wgetなどのネットワークツール- 各種デバッグユーティリティ
これにより攻撃対象面(Attack Surface)を大幅に削減できますが、Dockerfile 内でのツール追加が必要な場合はマルチステージビルドで対処する必要があります。
.NETプロジェクトでの課題と対処
課題の全体像
.NET アプリケーションを DHI へ移行する際、典型的に以下の問題が発生します。
- パッケージインストール不可 — SDK/Runtime イメージどちらも
apt-getが使えない - ASP.NET Core ランタイムの不足 — DHI .NET ランタイムイメージは .NET ランタイムのみを含み、ASP.NET Core の共有フレームワークが含まれない場合がある
- ネイティブライブラリの不足 — 画像処理など OS ライブラリに依存するパッケージが動作しない
- 追加ランタイムの組み込み — フロントエンドビルドで Node.js が必要な場合、SDK イメージに追加できない
これらはすべてマルチステージビルドで解決できます。
マルチステージビルドのパターン
ステージ構成の全体像
完全な Dockerfile 例
以下は .NET 8 + ASP.NET Core Web API(フロントエンドに Vue.js を含む)の構成例です。
# syntax=docker/dockerfile:1
# ────────────────────────────────────────────────────────────────
# Stage 1: ネイティブライブラリのビルド
# DHI ランタイムには apt-get がないため、標準イメージで先にビルド
# ────────────────────────────────────────────────────────────────
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS gdiplus-builder
RUN apt-get update \
&& apt-get install -y --no-install-recommends libgdiplus libc6-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN cd /usr/lib && ln -s libgdiplus.so gdiplus.dll
# ────────────────────────────────────────────────────────────────
# Stage 2: DHI Node イメージから Node.js バイナリを取得
# ────────────────────────────────────────────────────────────────
FROM dhi.io/node:22-debian13-dev AS node-source
# ────────────────────────────────────────────────────────────────
# Stage 3: ビルド
# DHI SDK には apt-get がないため Node.js を Stage 2 からコピー
# ────────────────────────────────────────────────────────────────
FROM dhi.io/dotnet:8.0-sdk-debian13 AS build
# npm / npx はシンボリックリンクなので、ライブラリツリーごとコピーしてリンクを再作成
COPY --from=node-source /usr/bin/node /usr/bin/node
COPY --from=node-source /usr/lib/nodejs /usr/lib/nodejs
RUN ln -s /usr/lib/nodejs/npm/bin/npm-cli.js /usr/bin/npm && \
ln -s /usr/lib/nodejs/npm/bin/npx-cli.js /usr/bin/npx
WORKDIR /src
# プロジェクトファイルを先にコピーして restore をキャッシュ
COPY ["MyApp.Api/MyApp.Api.csproj", "MyApp.Api/"]
COPY ["MyApp.Services/MyApp.Services.csproj", "MyApp.Services/"]
COPY ["NuGet.config", "."]
RUN dotnet restore "MyApp.Api/MyApp.Api.csproj"
COPY . .
WORKDIR "/src/MyApp.Api"
RUN dotnet build "MyApp.Api.csproj" -c Release -o /app/build
# フロントエンドビルド
WORKDIR "/src/MyApp.Api/ClientApp"
RUN npm install
RUN npm run build
# ────────────────────────────────────────────────────────────────
# Stage 4: パブリッシュ
# ────────────────────────────────────────────────────────────────
WORKDIR "/src/MyApp.Api"
FROM build AS publish
RUN dotnet publish "MyApp.Api.csproj" --no-restore -c Release -o /app/publish
# ────────────────────────────────────────────────────────────────
# Stage 5: 最終イメージ(DHI Runtime)
# ────────────────────────────────────────────────────────────────
FROM dhi.io/dotnet:8.0-debian13 AS final
# ASP.NET Core 共有フレームワーク(DHI ランタイムに含まれない場合)
COPY --from=build /opt/dotnet/shared/Microsoft.AspNetCore.App \
/opt/dotnet/shared/Microsoft.AspNetCore.App
# libgdiplus 本体
COPY --from=gdiplus-builder /usr/lib/libgdiplus* /usr/lib/
COPY --from=gdiplus-builder /usr/lib/gdiplus.dll /usr/lib/gdiplus.dll
# libgdiplus が依存するネイティブライブラリ
COPY --from=gdiplus-builder /lib/x86_64-linux-gnu/libcairo.so.2* /lib/x86_64-linux-gnu/
COPY --from=gdiplus-builder /lib/x86_64-linux-gnu/libglib-2.0.so.0* /lib/x86_64-linux-gnu/
COPY --from=gdiplus-builder /lib/x86_64-linux-gnu/libjpeg.so.62* /lib/x86_64-linux-gnu/
COPY --from=gdiplus-builder /lib/x86_64-linux-gnu/libtiff.so.6* /lib/x86_64-linux-gnu/
COPY --from=gdiplus-builder /lib/x86_64-linux-gnu/libgif.so.7* /lib/x86_64-linux-gnu/
COPY --from=gdiplus-builder /lib/x86_64-linux-gnu/libpng16.so.16* /lib/x86_64-linux-gnu/
COPY --from=gdiplus-builder /lib/x86_64-linux-gnu/libX11.so.6* /lib/x86_64-linux-gnu/
COPY --from=gdiplus-builder /lib/x86_64-linux-gnu/libfontconfig.so.1* /lib/x86_64-linux-gnu/
COPY --from=gdiplus-builder /lib/x86_64-linux-gnu/libfreetype.so.6* /lib/x86_64-linux-gnu/
COPY --from=gdiplus-builder /etc/fonts /etc/fonts
WORKDIR /app
COPY --from=publish /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.Api.dll"]
各ステージの詳細解説
Stage 1: ネイティブライブラリビルダー
なぜ必要か: System.Drawing や PDF 生成ライブラリ(例: SkiaSharp の代替として libgdiplus を使うケース)はネイティブ共有ライブラリに依存します。DHI ランタイムには apt-get がないため、標準イメージ上でビルドして .so ファイルを抽出します。
ポイント:
apt-get clean && rm -rf /var/lib/apt/lists/*でイメージレイヤーを最小化gdiplus.dllのシンボリックリンクは .NET の P/Invoke がgdiplus.dllという名前で検索するため必要- 依存する
.soファイルはすべて明示的にコピーする必要がある(lddコマンドで確認可能)
Stage 2: Node.js ソース
なぜ必要か: DHI SDK イメージには apt-get がないため、フロントエンドビルドに必要な Node.js を通常の方法でインストールできません。対応する DHI Node イメージから直接バイナリを取得します。
ポイント:
/usr/bin/npmと/usr/bin/npxは相対パスのシンボリックリンクのため、リンク先のライブラリツリー(/usr/lib/nodejs)ごとコピーする必要がある- コピー後、ビルドステージ内でシンボリックリンクを再作成する
# npm は内部で require('../lib/cli.js') を使う相対パス解決のため
# ライブラリツリーをそのままコピーしてシンボリックリンクを再作成
RUN ln -s /usr/lib/nodejs/npm/bin/npm-cli.js /usr/bin/npm && \
ln -s /usr/lib/nodejs/npm/bin/npx-cli.js /usr/bin/npx
Stage 3 & 4: ビルド・パブリッシュ
標準的なマルチステージビルドと同様ですが、いくつかの注意点があります。
- dotnet restore の前にプロジェクトファイルのみをコピーすることで、依存関係変更時のみ restore が再実行されるようにキャッシュを最適化
--no-restoreオプションを publish に付けることで二重実行を防ぐ- SonarQube などのコード解析ツールは JDK が必要なため、DHI SDK イメージ上では実行できない。CI パイプライン上で別途実行する
Stage 5: 最終イメージ(DHI Runtime)
ASP.NET Core 共有フレームワークのコピー:
DHI の .NET ランタイムイメージ(dhi.io/dotnet:8.0-debian13)は .NET ランタイムのみを含み、ASP.NET Core の共有フレームワーク(Microsoft.AspNetCore.App)が含まれない場合があります。ASP.NET Core Web API を実行するには、SDK ステージからコピーします。
COPY --from=build /opt/dotnet/shared/Microsoft.AspNetCore.App \
/opt/dotnet/shared/Microsoft.AspNetCore.App
依存ライブラリの調査方法
最終イメージにコピーすべき .so ファイルを調査するには、ビルダーステージ内で ldd を使います。
# コンテナを起動して調査
docker run --rm --entrypoint bash mcr.microsoft.com/dotnet/aspnet:8.0 \
-c "apt-get update -q && apt-get install -y libgdiplus && ldd /usr/lib/libgdiplus.so"
出力例:
libgdiplus.so => /usr/lib/libgdiplus.so
libglib-2.0.so.0 => /lib/x86_64-linux-gnu/libglib-2.0.so.0
libcairo.so.2 => /lib/x86_64-linux-gnu/libcairo.so.2
libjpeg.so.62 => /lib/x86_64-linux-gnu/libjpeg.so.62
...
この出力を元に、コピーすべきファイルを COPY --from=gdiplus-builder で列挙します。
DHI ロードマップ
システムパッケージの拡充
DHI はイメージ単体からシステムパッケージの提供へと拡張しています。イメージのプロベナンス(出所証明)を壊さずに追加できる OS システムパッケージをイメージ外でも個別提供します。
| ディストリビューション | 状況 |
|---|---|
| Alpine | 対応済み |
| Debian | 対応済み |
| Fedora(DNF / RPM 系) | 近日対応予定 |
RHEL は Red Hat のライセンス制約により対象外ですが、Rocky Linux や AlmaLinux などの互換ディストリビューションでの提供が予定されています。
Hardened Libraries(強化ライブラリ)
システムパッケージに続き、強化ライブラリの提供も予定されています。上流サプライチェーンへのポイズニング攻撃(悪意あるパッケージを正規パッケージとして公開する攻撃)の検出を目的として、まず npm から提供開始予定です。
- クールダウン期間: 新しいパッケージバージョンの公開後、一定期間をおいて異常を検出してから採用
- AI diff 管理: バージョン間の差分を AI で解析し、上流ポイズニングイベントを早期検出
- エージェントが DHI パッケージマネージャのみからパッケージ取得するよう制限することで、汎用 npm からの悪意あるパッケージ混入リスクを排除できる
Secure Build Services(オンプレミス強化ビルド)
機密コンテンツを Docker に送信することをためらう組織向けに、Secure Build Services を製品化予定です。
| 項目 | 内容 |
|---|---|
| 実行環境 | 社内 Kubernetes 上で動作 |
| 成果物 | Docker と同じ証明・公証プロセスを経たイメージ |
| ソース | プライベートレジストリからのソース取得に対応 |
これにより、機密性の高いカスタマイズ内容を Docker に送らずとも、DHI と同等のセキュリティ保証を得られます。
Docker エコシステムとの統合
| 製品 | DHI との統合 |
|---|---|
| Docker Desktop | Kubernetes ランタイムに DHI イメージを採用 |
| Docker Sandbox (SBX) | MCP サーバーとサンドボックステンプレートが DHI の強化プロセスを適用 |
| エージェント | DHI パッケージマネージャのみを許可するポリシー設定が可能 |
規制対応・認証
| 認証・規制 | 状況 |
|---|---|
| FIPS | 対応済み |
| STIG | 対応済み |
| EU CRA(Cyber Resilience Act)他 | 対応検討中 |
DHI はすでに FIPS・STIG 認証を取得しています。一部の EU 規制では STIG 認証が要件を満たすと判断されるケースもあるため、追加作業なしで対応できる可能性があります。EU を含む複数地域でサービスを提供する日本の多国籍企業にとっても、最高共通規制水準(FIPS/STIG)を一度対応しておくことが、複数の認証パイプラインを維持するより効率的です。