第6章:セキュリティとベストプラクティス
はじめに
コンテナ環境のセキュリティは、従来の仮想マシンとは異なるアプローチが必要です。本章では、Inceptionプロジェクトで適用すべきセキュリティ対策とベストプラクティスを学びます。
---
1. コンテナセキュリティの基本
1.1 脅威モデル
コンテナ環境の脅威:
1. コンテナエスケープ
- カーネル脆弱性を利用
- ホストシステムへの侵入
2. 不正なイメージ
- マルウェアを含むイメージ
- 古い脆弱なパッケージ
3. ネットワーク攻撃
- コンテナ間の不正アクセス
- 外部からの攻撃
4. 設定ミス
- 過剰な権限
- シークレットの露出
5. サプライチェーン攻撃
- ベースイメージの改ざん
- ビルドパイプラインの侵害
1.2 防御の多層化
+------------------+
| Application | ← 入力検証、認証
+------------------+
| Container | ← 非root、read-only
+------------------+
| Runtime | ← seccomp、AppArmor
+------------------+
| Host Kernel | ← カーネル更新
+------------------+
| Host System | ← アクセス制御
+------------------+
原則:
- 最小権限の原則
- 防御の多層化
- 定期的な更新
- 監視とログ
---
2. イメージセキュリティ
2.1 ベースイメージの選択
# 良い例: 最小限のベースイメージ
FROM alpine:3.18
# 悪い例: フル機能のイメージ
FROM ubuntu:latest
# 比較
Alpine:
+ サイズ: 約5MB
+ 攻撃対象面が小さい
+ セキュリティフォーカス
- 一部互換性問題
Debian slim:
+ 互換性が高い
+ 約30MB
+ 安定性
Distroless:
+ 最小限(シェルなし)
+ 攻撃対象面が最小
- デバッグが困難
2.2 パッケージ管理
# 良い例: バージョン固定、キャッシュ削除
FROM alpine:3.18
RUN apk update && \
apk add --no-cache \
nginx=1.24.0-r6 \
openssl=3.1.2-r0 && \
rm -rf /var/cache/apk/*
# 悪い例: バージョン未指定
FROM alpine
RUN apk add nginx
2.3 イメージスキャン
# Trivyによるスキャン
trivy image nginx:alpine
# 出力例
nginx:alpine (alpine 3.18.3)
Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 1, CRITICAL: 0)
┌──────────────┬───────────────┬──────────┬────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Fixed Version │
├──────────────┼───────────────┼──────────┼────────────────────────────┤
│ libcrypto3 │ CVE-2023-XXXX │ HIGH │ 3.1.2-r1 │
│ libssl3 │ CVE-2023-YYYY │ MEDIUM │ 3.1.2-r1 │
└──────────────┴───────────────┴──────────┴────────────────────────────┘
# CI/CDでの使用
trivy image --exit-code 1 --severity HIGH,CRITICAL myimage:latest
2.4 マルチステージビルド
# ビルドステージ(開発ツール含む)
FROM alpine:3.18 AS builder
RUN apk add --no-cache curl wget make gcc
WORKDIR /build
COPY . .
RUN make
# 実行ステージ(最小限)
FROM alpine:3.18
RUN apk add --no-cache libc6-compat
COPY --from=builder /build/app /app
USER nobody
CMD ["/app"]
# 効果:
# - ビルドツールが最終イメージに含まれない
# - 攻撃対象面の削減
# - イメージサイズの縮小
---
3. 実行時セキュリティ
3.1 非rootユーザー
FROM alpine:3.18
# ユーザー作成
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -s /bin/sh -D appuser
# 必要なディレクトリのパーミッション
RUN mkdir -p /app/data && \
chown -R appuser:appgroup /app
# ユーザー切り替え
USER appuser
WORKDIR /app
CMD ["./myapp"]
# docker-compose.yml での指定
services:
wordpress:
user: "1000:1000"
# または
user: nobody:nogroup
3.2 Read-onlyファイルシステム
services:
nginx:
read_only: true
tmpfs:
- /tmp
- /var/run
- /var/cache/nginx
volumes:
- wordpress_data:/var/www/html:ro # 読み取り専用
# Dockerfile での対応
FROM alpine:3.18
# 書き込みが必要なディレクトリを作成
RUN mkdir -p /tmp /var/run /var/cache/nginx && \
chmod 1777 /tmp
# read-onlyで起動可能に
3.3 Capabilities制限
services:
nginx:
cap_drop:
- ALL # すべてのcapabilityを削除
cap_add:
- NET_BIND_SERVICE # 1024以下のポートにバインド
- CHOWN # 必要に応じて
# 必要なcapabilityの確認
docker run --rm -it --cap-drop ALL nginx
# エラーが出たら必要なcapabilityを追加
# 一般的なサービスに必要なcapability
Nginx: NET_BIND_SERVICE, CHOWN, SETGID, SETUID
PHP-FPM: CHOWN, SETGID, SETUID
MariaDB: CHOWN, SETGID, SETUID, DAC_OVERRIDE
3.4 Seccompプロファイル
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": [
"read", "write", "open", "close", "stat", "fstat",
"mmap", "mprotect", "munmap", "brk",
"accept", "bind", "listen", "socket",
"epoll_create", "epoll_wait", "epoll_ctl"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
services:
nginx:
security_opt:
- seccomp:./nginx-seccomp.json
---
4. シークレット管理
4.1 環境変数の問題
# 悪い例: docker-compose.ymlにハードコード
services:
mariadb:
environment:
- MYSQL_ROOT_PASSWORD=secretpassword123 # NG!
# 良い例: .envファイルから読み込み
services:
mariadb:
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
4.2 .envファイルの保護
# .env ファイル
MYSQL_ROOT_PASSWORD=your_secure_password
MYSQL_DATABASE=wordpress
MYSQL_USER=wpuser
MYSQL_PASSWORD=wp_secure_password
# パーミッション設定
chmod 600 .env
# .gitignore に追加
echo ".env" >> .gitignore
echo "*.env" >> .gitignore
4.3 Docker Secrets(Swarmモード)
# docker-compose.yml
version: "3.8"
secrets:
mysql_root_password:
file: ./secrets/mysql_root_password.txt
mysql_password:
file: ./secrets/mysql_password.txt
services:
mariadb:
secrets:
- mysql_root_password
- mysql_password
environment:
- MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_root_password
- MYSQL_PASSWORD_FILE=/run/secrets/mysql_password
4.4 Inceptionでのシークレット管理
# ディレクトリ構造
srcs/
├── .env # 環境変数(gitignore)
├── .env.example # テンプレート(コミット可)
└── secrets/ # シークレットファイル
└── .gitkeep
# .env.example
DOMAIN_NAME=login.42.fr
MYSQL_DATABASE=wordpress
MYSQL_USER=wpuser
# パスワードは空欄
MYSQL_PASSWORD=
MYSQL_ROOT_PASSWORD=
WP_ADMIN_USER=admin
WP_ADMIN_PASSWORD=
WP_ADMIN_EMAIL=admin@42.fr
# 初回セットアップスクリプト
#!/bin/bash
# setup_secrets.sh
if [ ! -f .env ]; then
cp .env.example .env
# ランダムパスワード生成
MYSQL_PASSWORD=$(openssl rand -base64 24)
MYSQL_ROOT_PASSWORD=$(openssl rand -base64 24)
WP_ADMIN_PASSWORD=$(openssl rand -base64 24)
sed -i "s/^MYSQL_PASSWORD=.*/MYSQL_PASSWORD=${MYSQL_PASSWORD}/" .env
sed -i "s/^MYSQL_ROOT_PASSWORD=.*/MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}/" .env
sed -i "s/^WP_ADMIN_PASSWORD=.*/WP_ADMIN_PASSWORD=${WP_ADMIN_PASSWORD}/" .env
chmod 600 .env
echo "Secrets generated in .env"
fi
---
5. ネットワークセキュリティ
5.1 ネットワーク分離
networks:
frontend:
driver: bridge
name: inception_frontend
backend:
driver: bridge
name: inception_backend
internal: true # 外部アクセス不可
services:
nginx:
networks:
- frontend
- backend
ports:
- "443:443"
wordpress:
networks:
- backend # frontendには接続しない
mariadb:
networks:
- backend # 内部ネットワークのみ
5.2 ポート制限
services:
nginx:
ports:
- "443:443" # HTTPS のみ
# - "80:80" # HTTP 禁止(Inception要件)
wordpress:
# ports なし(内部通信のみ)
expose:
- "9000" # コンテナ間のみ
mariadb:
# ports なし(外部公開しない)
expose:
- "3306"
5.3 TLS設定の強化
# nginx.conf
ssl_protocols TLSv1.2 TLSv1.3; # 古いプロトコル無効化
ssl_prefer_server_ciphers on;
# 強力な暗号スイートのみ
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
# HSTS(本番環境向け)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# セキュリティヘッダー
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "default-src 'self';" always;
5.4 ファイアウォール設定
# UFW(Ubuntu Firewall)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 443/tcp
sudo ufw enable
# Docker との連携
# /etc/docker/daemon.json
{
"iptables": true
}
# Docker が iptables を管理
# 明示的なポート公開のみ許可
---
6. ログとモニタリング
6.1 ログドライバ設定
services:
nginx:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "service"
tag: "{{.Name}}"
6.2 ログの集約
# 本番環境向け
services:
nginx:
logging:
driver: syslog
options:
syslog-address: "udp://logserver:514"
tag: "nginx"
6.3 ヘルスチェック
services:
nginx:
healthcheck:
test: ["CMD", "curl", "-f", "https://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
mariadb:
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
6.4 リソース監視
# コンテナリソース使用状況
docker stats
# 出力例
CONTAINER ID NAME CPU % MEM USAGE / LIMIT NET I/O BLOCK I/O
a1b2c3d4e5f6 nginx 0.10% 10MiB / 100MiB 1.5kB / 1kB 0B / 0B
b2c3d4e5f6a1 wordpress 0.50% 50MiB / 256MiB 2kB / 1.5kB 0B / 0B
c3d4e5f6a1b2 mariadb 1.20% 100MiB / 512MiB 1kB / 2kB 0B / 4kB
---
7. リソース制限
7.1 メモリ制限
services:
nginx:
deploy:
resources:
limits:
memory: 128M
reservations:
memory: 64M
wordpress:
deploy:
resources:
limits:
memory: 256M
reservations:
memory: 128M
mariadb:
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
7.2 CPU制限
services:
nginx:
deploy:
resources:
limits:
cpus: '0.5'
reservations:
cpus: '0.25'
7.3 プロセス数制限
services:
wordpress:
ulimits:
nproc: 100
nofile:
soft: 20000
hard: 40000
---
8. Inceptionセキュリティチェックリスト
8.1 イメージ
□ ベースイメージにタグを指定(:latest禁止)
□ 不要なパッケージを削除
□ キャッシュをクリア
□ 脆弱性スキャンを実行
□ マルチステージビルドを検討
8.2 実行時
□ 非rootユーザーで実行
□ 不要なcapabilityを削除
□ read-onlyファイルシステムを検討
□ tmpfsで一時ファイル
□ リソース制限を設定
8.3 ネットワーク
□ 443ポートのみ公開
□ TLSv1.2/1.3のみ許可
□ 内部ネットワークを分離
□ network: host 未使用
□ --link 未使用
8.4 シークレット
□ .envファイルのパーミッション(600)
□ .gitignoreに.envを追加
□ 強力なパスワードを使用
□ パスワードをログに出力しない
8.5 監視
□ ヘルスチェックを設定
□ ログローテーションを設定
□ リソース使用状況を監視
---
9. セキュリティ強化版docker-compose.yml
services:
nginx:
build:
context: ./requirements/nginx
dockerfile: Dockerfile
container_name: nginx
ports:
- "443:443"
volumes:
- wordpress_data:/var/www/html:ro
networks:
- inception
depends_on:
wordpress:
condition: service_healthy
restart: always
# セキュリティ強化
read_only: true
tmpfs:
- /tmp
- /var/run
- /var/cache/nginx
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
- CHOWN
- SETGID
- SETUID
deploy:
resources:
limits:
memory: 128M
cpus: '0.5'
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "https://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
wordpress:
build:
context: ./requirements/wordpress
dockerfile: Dockerfile
container_name: wordpress
volumes:
- wordpress_data:/var/www/html
networks:
- inception
depends_on:
mariadb:
condition: service_healthy
environment:
- DOMAIN_NAME=${DOMAIN_NAME}
- WORDPRESS_DB_HOST=mariadb
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- WP_TITLE=${WP_TITLE}
- WP_ADMIN_USER=${WP_ADMIN_USER}
- WP_ADMIN_PASSWORD=${WP_ADMIN_PASSWORD}
- WP_ADMIN_EMAIL=${WP_ADMIN_EMAIL}
- WP_USER=${WP_USER}
- WP_USER_EMAIL=${WP_USER_EMAIL}
- WP_USER_PASSWORD=${WP_USER_PASSWORD}
restart: always
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
deploy:
resources:
limits:
memory: 256M
cpus: '1.0'
healthcheck:
test: ["CMD", "php-fpm82", "-t"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
mariadb:
build:
context: ./requirements/mariadb
dockerfile: Dockerfile
container_name: mariadb
volumes:
- mariadb_data:/var/lib/mysql
networks:
- inception
environment:
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
restart: always
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
- DAC_OVERRIDE
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
volumes:
wordpress_data:
driver: local
driver_opts:
type: none
o: bind
device: /home/${USER}/data/wordpress
mariadb_data:
driver: local
driver_opts:
type: none
o: bind
device: /home/${USER}/data/mariadb
networks:
inception:
driver: bridge
name: inception
---
まとめ
本章で学んだこと:
- 脅威モデル: コンテナ環境特有の脅威
- イメージセキュリティ: スキャン、マルチステージ
- 実行時セキュリティ: 非root、capability、seccomp
- シークレット管理: .env、Docker Secrets
- ネットワーク: 分離、TLS強化
- 監視: ログ、ヘルスチェック
- リソース制限: メモリ、CPU
- チェックリスト: 評価前の確認事項
これでInceptionプロジェクトのセキュリティ対策は完了です。次はAppendixで、コンテナ技術の歴史と現代的な応用を学びます。