跳到主要内容

Docker Hardened Images と .NET

Docker Hardened Images とは

Docker Hardened Images(DHI) は、Docker 社が提供するセキュリティ強化済みのコンテナイメージです。通常の公式ベースイメージと比較して、不要なパッケージやツールを徹底的に取り除いた最小構成で提供されます。

主な特徴

項目内容
CVE継続的なパッチ適用によりほぼゼロを維持
SBOM署名付きソフトウェア部品表(Software Bill of Materials)を提供
ProvenanceSLSA レベル 3 のビルド来歴検証
ライセンスApache 2.0(Community プランは無料)
ディストリビューションAlpine / Debian の両方をサポート

通常の公式イメージとの主な違い

DHI は「最小限に絞った」設計思想のため、以下のツール・機能が意図的に除外されています。

  • apt-get / apk などのパッケージマネージャ
  • bash / sh などのシェル
  • curl / wget などのネットワークツール
  • 各種デバッグユーティリティ

これにより攻撃対象面(Attack Surface)を大幅に削減できますが、Dockerfile 内でのツール追加が必要な場合はマルチステージビルドで対処する必要があります。


.NETプロジェクトでの課題と対処

課題の全体像

.NET アプリケーションを DHI へ移行する際、典型的に以下の問題が発生します。

  1. パッケージインストール不可 — SDK/Runtime イメージどちらも apt-get が使えない
  2. ASP.NET Core ランタイムの不足 — DHI .NET ランタイムイメージは .NET ランタイムのみを含み、ASP.NET Core の共有フレームワークが含まれない場合がある
  3. ネイティブライブラリの不足 — 画像処理など OS ライブラリに依存するパッケージが動作しない
  4. 追加ランタイムの組み込み — フロントエンドビルドで 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 DesktopKubernetes ランタイムに DHI イメージを採用
Docker Sandbox (SBX)MCP サーバーとサンドボックステンプレートが DHI の強化プロセスを適用
エージェントDHI パッケージマネージャのみを許可するポリシー設定が可能

規制対応・認証

認証・規制状況
FIPS対応済み
STIG対応済み
EU CRA(Cyber Resilience Act)他対応検討中

DHI はすでに FIPS・STIG 認証を取得しています。一部の EU 規制では STIG 認証が要件を満たすと判断されるケースもあるため、追加作業なしで対応できる可能性があります。EU を含む複数地域でサービスを提供する日本の多国籍企業にとっても、最高共通規制水準(FIPS/STIG)を一度対応しておくことが、複数の認証パイプラインを維持するより効率的です。


参考リンク