跳到主要内容

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)を一度対応しておくことが、複数の認証パイプラインを維持するより効率的です。


参考リンク​