PythonのDockerfileをセキュアにするためのベストプラクティス

# はじめに

PythonのDockerfileを作成する際、ネット上で適切な情報が見つからず、試行錯誤することがあります。そこで、ここでまとめてみます。

# 完成品
```Dockerfile
# 開発用ステージ
FROM python:3.11.9-slim-bookworm AS developer

ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

WORKDIR /app

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
    wget=1.21.3-1+b2 \
    && apt-get -y clean \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# ビルド用ステージ
FROM python:3.11.9-slim-bookworm AS builder
WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# 最終用ステージ
FROM gcr.io/distroless/python3-debian12:nonroot AS production

WORKDIR /app

COPY --from=builder /usr/local/lib/python3.11 /usr/local/lib/python3.11

ENV PYTHONPATH=/usr/local/lib/python3.11/site-packages
COPY --from=builder /app/ .

EXPOSE 8000

USER nonroot

CMD ["/app/opt/main.py"]
```

# 解説

この解説は、Dockerコンテナのセキュリティを強化するためのベストプラクティスを説明しています。

## 不要な特権を避ける

このDockerfileでは、特権を避けるために、最終ステージであるproductionステージでDistrolessイメージを使用しています。Distrolessイメージは、最小限のパッケージとセキュリティを向上させる設定のみを含み、不要な特権を持たないように設計されています。

1. **コンテナを root で実行しないようにする**
最終ステージのproductionステージで、USER命令を使用してnonrootユーザーに切り替えています。これにより、コンテナ内のプロセスが root 権限で実行されないようになります。Distrolessイメージでは、デフォルトで非rootユーザーが利用可能です。

```Dockerfile:Dockerfile
FROM gcr.io/distroless/python3-debian12:nonroot AS production
```

2. **特定のUIDにバインドしない**
Distrolessイメージのnonrootユーザーを使用することで、特定のUIDにバインドする必要がありません。イメージ内で事前に設定された非rootユーザーを使用することで、セキュリティを強化できます。

3. **実行可能ファイルを root が所有し、書き込み不可とする**
最終ステージでの実行可能ファイル（ここではmain.py）は、Distrolessイメージの非rootユーザーであるnonrootによって実行されます。これにより、実行可能ファイルがroot所有で書き込み不可になり、セキュリティが向上します。Distrolessイメージは、ファイルシステムへの書き込みを制限するため、不正な変更を防ぐ役割も果たします。

```Dockerfile:Dockerfile
USER nonroot
```

## 攻撃対象を減らす

+ マルチステージビルドを使用して、不要なパッケージや依存関係を含まない最終イメージを作成しています。これにより、攻撃対象が減り、セキュリティが向上します
+ 不要なパッケージやツールを含まず、最小限のセットアップだけを提供するDistrolessイメージを使用しています

1. **マルチステージビルドを活用する**
このDockerfileでは、開発用、ビルド用、そして本番用の3つの異なるステージを使用しています。マルチステージビルドを活用することで、最終的なイメージに必要なものだけを含め、攻撃面を減らすことができます。

```Dockerfile:Dockerfile
python:3.11.9-slim-bookworm AS developer
'''
python:3.11.9-slim-bookworm AS builder
'''
FROM gcr.io/distroless/python3-debian12:nonroot AS production
```

2. **distroless イメージを使うか、ゼロからビルドする**
最終的な本番用のイメージには、Distrolessイメージを使用しています。Distrolessイメージは、攻撃面を最小限に抑えるため目的で設計されており、必要なコンポーネントのみが含まれています。このアプローチは、セキュリティを強化するために推奨されます。

```Dockerfile:Dockerfile
FROM gcr.io/distroless/python3-debian12:nonroot AS production
```

3. **イメージを頻繁に更新する**
このDockerfileには、定期的なイメージ更新のメカニズムは含まれていませんが、セキュリティ上の観点から、イメージを頻繁に更新することが重要です。セキュリティパッチや新しいバージョンの依存関係がリリースされた場合、それらを適用するためにイメージを定期的に再構築する必要があります。

4. **暴露されたポートに注意する**
コンテナが公開するポートを制限し、必要なポートのみを公開します。不必要なポートを公開すると、攻撃者がコンテナにアクセスする可能性が高まります。

## 機密データの漏洩を防ぐ

機密情報（例：シークレットや認証情報）をDockerfile内にハードコードしないことは、セキュリティ上のベストプラクティスです。代わりに、Dockerのビルドコンテキスト外部から機密情報を注入して下さい。

1. **Dockerfileの命令にシークレットや認証情報を入れないようにする**
例えばパッケージのインストール時に、認証情報が必要な場合は、その情報を環境変数などを通じて安全に渡す必要があります。

2. **ADDよりもCOPYを優先する**
一般的に、ファイルやディレクトリをDockerイメージにコピーする場合は、ADDよりもCOPYを使用することが推奨されます。COPYは単純にファイルをコピーするだけで、解凍やURLからのダウンロードなどを行わず、セキュリティリスクが少ないです。

3. **Dockerのコンテキストを意識し、.dockerignoreを使用する**
Dockerfileのビルドコンテキストには、ビルドされるイメージに含めたくないファイルやディレクトリが含まれます。.dockerignoreファイルを使用することで、不要なファイルやディレクトリをビルドコンテキストから除外し、セキュリティを向上させることができます。このDockerfileには、.dockerignoreファイルが含まれていないため、適切な場所に設定することが重要です。

```txt:.dockerignore
# Git
.git
.gitignore
.gitattributes


# CI
.codeclimate.yml
.travis.yml
.taskcluster.yml

# Docker
docker-compose.yml
Dockerfile
.docker
.dockerignore

# Byte-compiled / optimized / DLL files
**/__pycache__/
**/*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Virtual environment
.env
.venv/
venv/

# PyCharm
.idea

# Python mode for VIM
.ropeproject
**/.ropeproject

# Vim swap files
**/*.swp

# VS Code
.vscode/
```

# 参考文献

https://sysdig.jp/blog/dockerfile-best-practices/