コンテナセキュリティとは?
コンテナセキュリティとは、Docker、Kubernetes などのコンテナ技術を使用したアプリケーションやインフラストラクチャを、セキュリティ脅威から保護するための包括的な対策のことです。
コンテナ技術は、アプリケーションの開発・デプロイ・運用を劇的に改善しましたが、同時に従来とは異なる新しいセキュリティリスクも生み出しています。
コンテナ環境特有のセキュリティリスク
- 脆弱なベースイメージ
- 不適切な権限設定
- 機密情報の平文保存
- ネットワーク分離の不備
- コンテナエスケープ攻撃
- レジストリへの不正アクセス
- オーケストレーション設定ミス
これらのリスクは、従来の仮想マシンベースのセキュリティ対策だけでは不十分であり、コンテナ固有のセキュリティアプローチが必要になります。
コンテナセキュリティの脅威ランドスケープ
1. コンテナイメージの脅威
脆弱なベースイメージ
# 危険な例:古いOSイメージ
FROM ubuntu:16.04
RUN apt-get update && apt-get install -y \
wget \
curl \
vim
# より安全な例:最新の軽量イメージ
FROM ubuntu:22.04-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
wget=1.21.3-1ubuntu1 \
curl=7.81.0-1ubuntu1.4 \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
悪意のあるイメージ
# 信頼できないレジストリからのpull(危険)
docker pull suspicious-registry.com/malware:latest
# Docker Hub公式イメージの使用(推奨)
docker pull node:18-alpine
docker pull nginx:1.23-alpine
docker pull postgres:15-alpine
レイヤーに含まれる機密情報
# 危険な例:機密情報がレイヤーに残る
FROM node:18
COPY . /app
RUN echo "DATABASE_PASSWORD=super_secret_password" > /app/.env
RUN rm /app/.env # 削除してもレイヤーに残る!
# 安全な例:multi-stage buildと適切な秘匿情報管理
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
# 環境変数やDockerシークレットで機密情報を管理
2. ランタイムセキュリティ脅威
コンテナエスケープ
# 特権コンテナでの危険な例
docker run --privileged -it ubuntu:latest /bin/bash
# コンテナ内からホストファイルシステムへアクセス
mount /dev/sda1 /mnt
chroot /mnt
# ホストシステムを完全に制御可能
不適切な権限設定
# 危険な例:rootユーザーで実行
FROM node:18
COPY . /app
WORKDIR /app
CMD ["node", "server.js"] # rootで実行される
# 安全な例:非特権ユーザーで実行
FROM node:18-alpine
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
COPY . /app
WORKDIR /app
RUN chown -R nextjs:nodejs /app
USER nextjs
CMD ["node", "server.js"]
3. オーケストレーション設定の脅威
Kubernetes設定ミス
# 危険な例:過度な権限
apiVersion: v1
kind: Pod
metadata:
name: vulnerable-pod
spec:
containers:
- name: app
image: myapp:latest
securityContext:
privileged: true # 危険
runAsUser: 0 # rootユーザー
allowPrivilegeEscalation: true
hostNetwork: true # ホストネットワーク使用
hostPID: true # ホストPID名前空間
# 安全な例:最小権限の原則
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
resources:
limits:
memory: "256Mi"
cpu: "500m"
requests:
memory: "128Mi"
cpu: "250m"
Docker セキュリティのベストプラクティス
1. セキュアなDockerfile作成
# セキュアなDockerfileの例
FROM node:18-alpine AS builder
# セキュリティアップデート
RUN apk update && apk upgrade && apk add --no-cache \
dumb-init \
&& rm -rf /var/cache/apk/*
# 非特権ユーザーの作成
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
WORKDIR /app
# 依存関係のみを先にコピー(レイヤーキャッシュ最適化)
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# アプリケーションコードをコピー
COPY --chown=nextjs:nodejs . .
# 本番用ビルド(最小限の依存関係)
FROM node:18-alpine AS production
# セキュリティアップデート
RUN apk update && apk upgrade && apk add --no-cache dumb-init
# 非特権ユーザー作成
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
WORKDIR /app
# 必要なファイルのみをコピー
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/package*.json ./
COPY --from=builder --chown=nextjs:nodejs /app/src ./src
# 非特権ユーザーに切り替え
USER nextjs
# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
# シグナル処理のためdumb-initを使用
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "src/server.js"]
# 不要なポートは公開しない
EXPOSE 3000
2. Docker Compose セキュリティ設定
# docker-compose.yml
version: '3.8'
services:
app:
build: .
read_only: true # ルートファイルシステムを読み取り専用
tmpfs:
- /tmp
- /var/tmp
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
user: "1001:1001"
environment:
- NODE_ENV=production
secrets:
- db_password
- api_key
networks:
- app_network
depends_on:
db:
condition: service_healthy
db:
image: postgres:15-alpine
read_only: true
tmpfs:
- /var/run/postgresql
- /tmp
security_opt:
- no-new-privileges:true
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
POSTGRES_DB: myapp
POSTGRES_USER: myapp
secrets:
- db_password
volumes:
- db_data:/var/lib/postgresql/data:Z
networks:
- app_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myapp"]
interval: 30s
timeout: 10s
retries: 3
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
volumes:
db_data:
driver: local
networks:
app_network:
driver: bridge
internal: true # 外部ネットワークへの直接アクセスを防止
3. Docker セキュリティスキャン
# Docker Scout(公式)でのセキュリティスキャン
docker scout cves myapp:latest
docker scout recommendations myapp:latest
# Trivy でのスキャン
trivy image myapp:latest
trivy image --severity HIGH,CRITICAL myapp:latest
# Clair でのスキャン
clairctl analyze -l myapp:latest
# Snyk でのスキャン
snyk container test myapp:latest
自動化スキャンスクリプト
#!/bin/bash
# container-security-scan.sh
set -euo pipefail
IMAGE_NAME=$1
SEVERITY_THRESHOLD=${2:-MEDIUM}
echo "🔍 Scanning container image: $IMAGE_NAME"
# Trivy スキャン
echo "Running Trivy scan..."
trivy image --exit-code 1 --severity HIGH,CRITICAL "$IMAGE_NAME"
# Docker Scout スキャン
echo "Running Docker Scout scan..."
docker scout cves "$IMAGE_NAME"
# イメージの設定チェック
echo "Checking image configuration..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
goodwithtech/dockle:latest "$IMAGE_NAME"
# ベストプラクティスチェック
echo "Running best practice checks..."
hadolint Dockerfile
echo "✅ Security scan completed for $IMAGE_NAME"
Kubernetes セキュリティ
1. Pod Security Standards
# Pod Security Policy の代替(Kubernetes 1.25+)
apiVersion: v1
kind: Namespace
metadata:
name: secure-namespace
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
2. Network Policies
# ネットワーク分離ポリシー
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-specific-communication
namespace: production
spec:
podSelector:
matchLabels:
app: web-app
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: api-gateway
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- to: [] # DNS解決のため
ports:
- protocol: UDP
port: 53
3. RBAC(Role-Based Access Control)
# 最小権限の原則に基づくRBAC設定
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production
subjects:
- kind: ServiceAccount
name: monitoring-service
namespace: production
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: monitoring-service
namespace: production
automountServiceAccountToken: false # 不要な場合は無効化
4. シークレット管理
# 外部シークレット管理(External Secrets Operator)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: production
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "myapp"
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: production
spec:
refreshInterval: 30s
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: db-secret
creationPolicy: Owner
template:
type: Opaque
data:
username: "{{ .username }}"
password: "{{ .password }}"
data:
- secretKey: username
remoteRef:
key: database
property: username
- secretKey: password
remoteRef:
key: database
property: password
セキュリティツールとモニタリング
1. Falco(ランタイム脅威検知)
# Falco設定例
apiVersion: v1
kind: ConfigMap
metadata:
name: falco-config
data:
falco.yaml: |
rules_file:
- /etc/falco/falco_rules.yaml
- /etc/falco/k8s_audit_rules.yaml
time_format_iso_8601: true
json_output: true
json_include_output_property: true
outputs:
rate: 1
max_burst: 1000
syslog_output:
enabled: true
file_output:
enabled: true
keep_alive: false
filename: /var/log/falco.log
stdout_output:
enabled: true
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: falco
spec:
selector:
matchLabels:
app: falco
template:
metadata:
labels:
app: falco
spec:
serviceAccount: falco
hostNetwork: true
hostPID: true
containers:
- name: falco
image: falcosecurity/falco:latest
securityContext:
privileged: true
volumeMounts:
- mountPath: /host/var/run/docker.sock
name: docker-socket
- mountPath: /host/dev
name: dev-fs
readOnly: true
- mountPath: /host/proc
name: proc-fs
readOnly: true
- mountPath: /host/boot
name: boot-fs
readOnly: true
- mountPath: /host/lib/modules
name: lib-modules
readOnly: true
- mountPath: /host/usr
name: usr-fs
readOnly: true
- mountPath: /etc/falco
name: config-volume
volumes:
- name: docker-socket
hostPath:
path: /var/run/docker.sock
- name: dev-fs
hostPath:
path: /dev
- name: proc-fs
hostPath:
path: /proc
- name: boot-fs
hostPath:
path: /boot
- name: lib-modules
hostPath:
path: /lib/modules
- name: usr-fs
hostPath:
path: /usr
- name: config-volume
configMap:
name: falco-config
2. OPA Gatekeeper(ポリシー実行)
# セキュリティポリシーの定義
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredsecuritycontext
spec:
crd:
spec:
names:
kind: K8sRequiredSecurityContext
validation:
type: object
properties:
runAsNonRoot:
type: boolean
runAsUser:
type: integer
minimum: 1000
fsGroup:
type: integer
minimum: 1000
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredsecuritycontext
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := "Container must run as non-root user"
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.runAsUser < 1000
msg := "Container must run as UID >= 1000"
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredSecurityContext
metadata:
name: must-have-security-context
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["production", "staging"]
parameters:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
3. Container Runtime Security
# gVisor(サンドボックス化ランタイム)の使用
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
---
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
runtimeClassName: gvisor # gVisorを使用
containers:
- name: app
image: myapp:latest
4. 継続的セキュリティ監視
# Prometheus メトリクスを使用したセキュリティ監視
import time
from prometheus_client import Counter, Histogram, start_http_server
# セキュリティメトリクス
security_events = Counter('security_events_total', 'Total security events', ['type', 'severity'])
container_vulnerabilities = Counter('container_vulnerabilities_total', 'Container vulnerabilities', ['image', 'severity'])
policy_violations = Counter('policy_violations_total', 'Policy violations', ['policy', 'namespace'])
def monitor_security_events():
"""セキュリティイベントの監視"""
while True:
# Falcoアラートの処理
falco_alerts = get_falco_alerts()
for alert in falco_alerts:
security_events.labels(
type=alert['rule'],
severity=alert['priority']
).inc()
# 脆弱性スキャン結果の処理
vuln_results = get_vulnerability_scan_results()
for result in vuln_results:
container_vulnerabilities.labels(
image=result['image'],
severity=result['severity']
).inc()
# ポリシー違反の監視
violations = get_policy_violations()
for violation in violations:
policy_violations.labels(
policy=violation['policy'],
namespace=violation['namespace']
).inc()
time.sleep(60) # 1分間隔で監視
def get_falco_alerts():
"""Falcoアラートの取得(実装例)"""
# Falco APIやログファイルから取得
return []
def get_vulnerability_scan_results():
"""脆弱性スキャン結果の取得"""
# Trivy, Twistlock等のAPIから取得
return []
def get_policy_violations():
"""ポリシー違反の取得"""
# OPA Gatekeeperログから取得
return []
if __name__ == '__main__':
start_http_server(8000)
monitor_security_events()
CI/CD パイプラインでのセキュリティ統合
1. GitLab CI セキュリティパイプライン
# .gitlab-ci.yml
stages:
- security-scan
- build
- test
- security-test
- deploy
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
# コンテナイメージスキャン
container-scanning:
stage: security-scan
image: aquasecurity/trivy:latest
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
allow_failure: false
# Dockerfileセキュリティチェック
dockerfile-security:
stage: security-scan
image: hadolint/hadolint:latest-debian
script:
- hadolint Dockerfile
allow_failure: false
# シークレットスキャン
secret-detection:
stage: security-scan
image: trufflesecurity/trufflehog:latest
script:
- trufflehog git file://. --only-verified
allow_failure: false
# Kubernetes設定スキャン
k8s-security-scan:
stage: security-test
image: kubesec/kubesec:latest
script:
- kubesec scan k8s/*.yaml
allow_failure: false
# ランタイムセキュリティテスト
runtime-security-test:
stage: security-test
image: docker:latest
services:
- docker:dind
script:
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
aquasecurity/trivy image --exit-code 1 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
allow_failure: false
2. GitHub Actions セキュリティワークフロー
# .github/workflows/security.yml
name: Container Security
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build -t ${{ github.repository }}:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ github.repository }}:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: Scan with Snyk
uses: snyk/actions/docker@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
image: '${{ github.repository }}:${{ github.sha }}'
args: --severity-threshold=high
- name: Docker Scout
uses: docker/scout-action@v1
with:
command: cves
image: '${{ github.repository }}:${{ github.sha }}'
only-severities: critical,high
write-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
k8s-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Security scan with Kubesec
run: |
curl -sSX POST --data-binary @k8s/deployment.yaml \
https://v2.kubesec.io/scan
- name: Scan with Polaris
run: |
curl -L https://github.com/FairwindsOps/polaris/releases/download/4.2.0/polaris_linux_amd64.tar.gz \
| tar -xz
./polaris audit --audit-path k8s/
実際のセキュリティインシデント事例
ケーススタディ1:脆弱なベースイメージによるデータ漏洩
# 問題のあったDockerfile
FROM ubuntu:18.04 # EOLで脆弱性多数
RUN apt-get update && apt-get install -y openssh-server
COPY app.py /app/
ENV DATABASE_PASSWORD=production_password # 平文で保存
CMD ["python", "/app/app.py"]
影響:
- CVE-2021-3156(sudo脆弱性)によりroot権限取得
- 環境変数から本番データベース認証情報が漏洩
- 約10万件の顧客データが流出
対策:
# 修正後のDockerfile
FROM ubuntu:22.04-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
python3=3.10.6-1~22.04 \
&& rm -rf /var/lib/apt/lists/*
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --chown=appuser:appuser app.py /app/
USER appuser
# 機密情報はDocker Secretsで管理
CMD ["python3", "/app/app.py"]
ケーススタディ2:Kubernetes RBAC設定ミス
# 問題のあった設定
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: developer-binding
subjects:
- kind: User
name: developer@company.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin # 過度な権限付与
apiGroup: rbac.authorization.k8s.io
影響:
- 開発者アカウントの侵害
- クラスター全体への管理者権限取得
- 暗号通貨マイニングマルウェアの展開
対策:
# 修正後の設定
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: development
name: developer-role
rules:
- apiGroups: ["", "apps"]
resources: ["pods", "deployments", "services"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: developer-binding
namespace: development
subjects:
- kind: User
name: developer@company.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: developer-role
apiGroup: rbac.authorization.k8s.io
セキュリティ成熟度の向上
レベル1:基礎対策(即座に実装)
# 最低限のセキュリティチェックリスト
✓ 最新のベースイメージ使用
✓ 非rootユーザーでの実行
✓ 不要なパッケージの削除
✓ 脆弱性スキャンの実施
✓ 機密情報の環境変数化
レベル2:中級対策(3-6ヶ月で実装)
# 中級セキュリティチェックリスト
✓ Multi-stage build の採用
✓ イメージ署名の実装
✓ Network Policies の設定
✓ Pod Security Standards の適用
✓ RBAC の最小権限設定
✓ 継続的脆弱性監視
レベル3:上級対策(6ヶ月-1年で実装)
# 上級セキュリティチェックリスト
✓ ランタイムセキュリティ監視(Falco)
✓ ポリシーエンジン(OPA)の導入
✓ サービスメッシュセキュリティ
✓ Zero-trust ネットワーク
✓ 自動インシデント対応
✓ コンプライアンス自動監査
まとめ
コンテナセキュリティは、現代のクラウドネイティブ開発において不可欠な要素です。
重要なポイント:
- セキュアなイメージ構築:最新・最小・非特権の原則
- ランタイム保護:継続的な監視と脅威検知
- ネットワーク分離:マイクロセグメンテーション
- シークレット管理:外部システムでの一元管理
- 自動化:CI/CDパイプラインでのセキュリティ統合
- 継続的改善:脅威インテリジェンスの活用
特に重要なのは、セキュリティを開発プロセスの最初から組み込む(Shift-Left)ことです。後からセキュリティを追加するのではなく、設計段階からセキュリティを考慮することで、より堅牢でコスト効率の良いシステムを構築できます。
コンテナ技術は進化し続けており、新しい脅威も常に出現しています。継続的な学習と最新のセキュリティ動向への追従が、安全なコンテナ環境の維持には不可欠です。
コメント