コンテナセキュリティ — ECS・EKS・ECR
ECSタスクロール、EKS IRSA/Pod Identity、ECRスキャン、コンテナランタイムセキュリティを徹底解説
概要
コンテナセキュリティはAWS SCS-C03試験の重要テーマです。ECS(Elastic Container Service)、EKS(Elastic Kubernetes Service)、ECR(Elastic Container Registry)それぞれに異なるセキュリティモデルと設定があります。
本講義では、実運用で求められるセキュリティ設計パターンを解説します。
ECS タスクロール vs 実行ロール フロー
Loading diagram...
ECR → ECS/EKS コンテナセキュリティパイプライン
Loading diagram...
ECS セキュリティ
1. タスク実行ロール(Task Execution Role)vs タスクロール(Task Role)
ECSの認証周りで最も誤解が多い部分です。二つのロールの役割を明確に分けることが設計の鍵です。
タスク実行ロール(Task Execution Role)
何をするロール?
- ECSエージェント自身が動作するために必要な権限
- ECRからイメージをプル(PullImage)
- CloudWatch Logsへのログ出力
- Secrets Manager / Parameter Storeからシークレット取得(タスク定義内のシークレット定義時)
- KMS復号(暗号化されたシークレット取得時)
タスクロール(Task Role)
何をするロール?
- コンテナ内のアプリケーションが実行時に必要とする権限
- S3へのアクセス
- DynamoDBへのクエリ
- SNSへのメッセージパブリッシュ
- SQSからのメッセージ受信
IAM信頼ポリシー設定例
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
タスク定義での指定
{
"family": "secure-app",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123456789012:role/ecsTaskRole",
"containerDefinitions": [
{
"name": "app",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/secure-app:latest",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/secure-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
試験で狙われるポイント:
- 「S3アクセス権限を付与したいがECS Execで接続できない」→タスクロール vs 実行ロールの混同
- 「ECRからイメージがプルできない」→実行ロールにPushではなくPull権限が必要
2. Fargate vs EC2起動タイプのセキュリティ差異
| 項目 | Fargate | EC2 |
|---|---|---|
| ホストOS管理 | AWS管理(見えない) | ユーザー管理(AMI、パッチ) |
| 隔離レベル | マルチテナント(Nitro隔離) | 単一ホスト内で複数タスク |
| SSH/RDP | 不可 | EC2 Instance Connectで可 |
| リソース制限 | 厳密(CPU/Memory固定) | 柔軟(ホスト制約) |
| セキュリティパッチ | AWS自動適用 | ユーザーが管理 |
| コスト | 割高 | 割安 |
Fargateセキュリティの特徴:
- コンテナのみ管理: ホストOS管理の責任がない(共有責任モデル)
- Nitro隔離: ハイパーバイザーレベルの強力な隔離
- イミュータブル: 起動後、ホストレベルの変更不可
EC2セキュリティの責任:
- AMIの管理: セキュリティパッチ、脆弱性スキャン
- ホストセキュリティグループ: インスタンスレベルのネットワーク制御
- エージェント管理: CloudWatch Agent, SSM Agentの導入・維持
3. awsvpc ネットワークモード(推奨)
ECSのネットワークモードはセキュリティと機能性に直結します。現在、awsvpcはECS on Fargateで必須、EC2でも推奨です。
ネットワークモード比較
| モード | セキュリティグループ | ENI割当 | ポート動的割当 | ホストネットワーク |
|---|---|---|---|---|
| awsvpc | 直接適用(推奨) | 1タスク1ENI | 可能 | タスク専用ENI |
| bridge | ホストへのポート割当 | ホスト共有 | ポート衝突リスク | ホストNetNS |
| host | なし | なし | なし | ホスト直結 |
awsvpcセキュリティグループ設定例
{
"family": "web-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "nginx",
"image": "nginx:latest",
"portMappings": [
{
"containerPort": 80,
"hostPort": 80,
"protocol": "tcp"
}
]
}
]
}
ECS Serviceで適用するセキュリティグループ:
{
"serviceName": "web-service",
"cluster": "production",
"taskDefinition": "web-app",
"desiredCount": 3,
"networkConfiguration": {
"awsvpcConfiguration": {
"subnets": [
"subnet-0a1b2c3d",
"subnet-4e5f6g7h"
],
"securityGroups": [
"sg-0123456789abcdef0"
],
"assignPublicIp": "DISABLED"
}
}
}
セキュリティグループのベストプラクティス:
- インバウンド: 必要なポートのみ開放(例:ALBからの80/443)
- アウトバウンド: 明示的に定義(デフォルト許可から段階的に制限)
- タスク間通信: 別SGで制御(マイクロサービス間通信)
4. Secrets Manager / Parameter Store からのシークレット注入
Secrets Managerの使用(本番推奨)
データベース認証情報、APIキーなどはタスク定義に埋め込まず、ランタイム時に注入します。
{
"family": "app-with-secrets",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "app",
"image": "myapp:latest",
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db-password-aBcDe:password::"
},
{
"name": "API_KEY",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/api-key-XyZ123"
}
]
}
]
}
Parameter Storeの使用(設定値向け)
{
"containerDefinitions": [
{
"name": "app",
"environment": [
{
"name": "ENVIRONMENT",
"value": "production"
}
],
"secrets": [
{
"name": "LOG_LEVEL",
"valueFrom": "/app/config/log-level"
}
]
}
]
}
実行ロールに必要な権限
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/*"
},
{
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:us-east-1:123456789012:key/*",
"Condition": {
"StringEquals": {
"kms:ViaService": "secretsmanager.us-east-1.amazonaws.com"
}
}
}
]
}
試験で狙われるポイント:
- Secrets Managerのシークレット取得時、KMS権限も必要(AES-256で暗号化)
- 環境変数とsecretsの混同:環境変数は暗号化されない
5. ECS Exec(Session Manager連携)
ECS ExecはSSM Session Managerを利用したエージェント不要なセキュアなコンテナアクセス手段です。
ECS Execの前提条件
- タスク実行ロール:
ssmmessages:*権限が必要
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel"
],
"Resource": "*"
}
]
}
- CloudWatch Logs: ECS Execセッションログ出力
{
"containerDefinitions": [
{
"name": "app",
"image": "app:latest",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "exec"
}
}
}
]
}
ECS Exec実行例
# Fargateタスクへのアクセス
aws ecs execute-command \
--cluster production \
--task arn:aws:ecs:us-east-1:123456789012:task/production/abc123def456 \
--container app \
--interactive \
--command "/bin/sh"
監査ログ
{
"enableExecuteCommand": true,
"executeCommandConfiguration": {
"logging": "OVERRIDE",
"logConfiguration": {
"cloudWatchLogGroupName": "/ecs/exec-logs",
"s3BucketName": "ecs-exec-logs",
"s3KeyPrefix": "logs/"
}
}
}
セキュリティ上の利点:
- SSH/RDPのようなネットワークポート不要
- IAM × Session Manager × CloudWatch Logs で完全な監査証跡
- エフェメラルキーによる認証(キー管理不要)
6. タスク定義のセキュリティ設定
readonlyRootFilesystem(読み取り専用ルートFS)
{
"containerDefinitions": [
{
"name": "immutable-app",
"image": "app:latest",
"readonlyRootFilesystem": true,
"mountPoints": [
{
"containerPath": "/tmp",
"sourceVolume": "tmp"
},
{
"containerPath": "/var/run",
"sourceVolume": "var-run"
}
]
}
],
"volumes": [
{
"name": "tmp",
"emptyDir": {}
},
{
"name": "var-run",
"emptyDir": {}
}
]
}
効果:
- アプリケーションが誤って整合性を損なうファイル変更を実行不可
- マルウェア感染時の永続化阻止
noNewPrivileges
{
"containerDefinitions": [
{
"name": "secure-app",
"image": "app:latest",
"linuxParameters": {
"initProcessEnabled": true,
"capabilities": {
"drop": ["ALL"],
"add": ["NET_BIND_SERVICE"]
}
}
}
]
}
効果:
- setuidバイナリ実行による権限昇格を阻止
capability drop(Linux Capabilities)
{
"linuxParameters": {
"capabilities": {
"drop": [
"ALL"
],
"add": [
"CHOWN",
"DAC_OVERRIDE",
"NET_BIND_SERVICE"
]
}
}
}
| Capability | 役割 | デフォルト |
|---|---|---|
| ALL | すべての権限 | 許可(dropで削除) |
| NET_BIND_SERVICE | 1024以下のポートバインド | 削除 |
| SYS_ADMIN | 管理権限 | 削除推奨 |
| SETFCAP | ファイルcapability設定 | 削除推奨 |
| NET_RAW | RAWソケット生成 | 削除推奨 |
EKS セキュリティ
1. クラスターエンドポイントアクセス
EKSのAPIエンドポイント(kube-apiserver)へのアクセス制御は、クラスター全体のセキュリティモデルを決定します。
エンドポイントアクセスパターン
| パターン | 説明 | ユースケース |
|---|---|---|
| Public only | パブリックエンドポイントのみ | 開発環境、POC(⚠️ 本番非推奨) |
| Private only | プライベートエンドポイントのみ | オンプレ統合、閉域網 |
| Both | 両方有効(推奨) | 本番環境(段階的にPrivate移行) |
Terraform設定例
resource "aws_eks_cluster" "main" {
name = "production"
version = "1.29"
vpc_config {
# APIエンドポイント設定
endpoint_private_access = true
endpoint_public_access = true
# プライベート制御
public_access_cidrs = [
"203.0.113.0/24", # 本社オフィス
"203.0.113.100/32" # VPN Gateway
]
subnet_ids = var.subnet_ids
}
}
段階的なPrivate移行
フェーズ1(現在): 両方有効、PublicはCIDR制限
public_access_cidrs = ["203.0.113.0/24"]
フェーズ2(1ヶ月後): Public無効化
endpoint_public_access = false
# 必ずBastion/Proxy経由でPrivateエンドポイントにアクセス
試験で狙われるポイント:
- PublicのみでもセキュリティGは必要(CIDR制限)
- Private onlyの場合、kubectlクライアントはVPC内またはVPN接続が必須
2. RBAC(Kubernetes)と IAM の統合
aws-auth ConfigMap(従来方式)
Kubernetes RBAC はIAM ロール/ユーザーのマッピングによって実現されます。
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
mapRoles: |
- rolesArn: arn:aws:iam::123456789012:role/eks-node-group-role
username: system:node:{{EC2PrivateDNSName}}
groups:
- system:bootstrappers
- system:nodes
- rolesArn: arn:aws:iam::123456789012:role/eks-admin-role
username: admin
groups:
- system:masters
- rolesArn: arn:aws:iam::123456789012:role/eks-developer-role
username: developer
groups:
- developers
mapUsers: |
- userarn: arn:aws:iam::123456789012:user/alice@company.com
username: alice
groups:
- developers
mapAccounts: |
- "111122223333"
- "444455556666"
EKS Access Entries(新方式、推奨)
apiVersion: eks.amazonaws.com/v1
kind: AccessEntry
metadata:
name: developer-access
spec:
principalArn: arn:aws:iam::123456789012:role/eks-developer-role
type: EC2_LINUX
accessPolicies:
- arn: arn:aws:eks::aws:access-policy/AmazonEKSViewPolicy
- arn: arn:aws:eks::aws:access-policy/AmazonEKSEditPolicy
RBAC ClusterRole設定例
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: developer-read-only
rules:
# Podの閲覧
- apiGroups: [""]
resources: ["pods", "pods/logs", "pods/status"]
verbs: ["get", "list", "watch"]
# Deploymentの閲覧
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
# Secret情報は閲覧禁止
- apiGroups: [""]
resources: ["secrets"]
verbs: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: developer-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: developer-read-only
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
IAM × Kubernetes RBAC の精密制御:
- IAM: AWS APIへのアクセス(EC2起動、S3等)
- K8s RBAC: Kubernetesリソース(Pod、Secret等)へのアクセス
3. Pod Security Standards / Pod Security Admission
Kubernetes 1.25以降、Pod Security Policy(非推奨)の代わりにPod Security Standards(PSS)とPod Security Admissionが採用されました。
Pod Security Standards の3レベル
| レベル | 説明 | セキュリティ態勢 |
|---|---|---|
| Restricted | 厳格なセキュリティポリシー | 本番環境向け |
| Baseline | デフォルト(最小限の制限) | 開発環境向け |
| Privileged | 制限なし(非推奨) | 特殊な理由がある場合のみ |
Pod Security Admission 設定
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# Restricted: 最も厳格
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# audit: 違反をログ記録(alerting用)
pod-security.kubernetes.io/audit: restricted
# warn: ユーザーに警告表示
pod-security.kubernetes.io/warn: restricted
Restricted レベルの制約
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true # rootで実行禁止
runAsUser: 1000 # 非rootユーザーを指定
fsGroup: 2000 # PVC所有グループ
seccompProfile:
type: RuntimeDefault # seccompプロファイル
containers:
- name: app
image: app:latest
securityContext:
allowPrivilegeEscalation: false # 権限昇格禁止
readOnlyRootFilesystem: true # 読み取り専用FS
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
volumes:
- name: tmp
emptyDir: {}
試験で狙われるポイント:
- Restrictedはnonrootユーザーが強制
- readOnlyRootFilesystemとemptyDirの組み合わせ
4. IRSA(IAM Roles for Service Accounts)
EKSでPodがAWS APIにアクセスする際、Kubernetes ServiceAccountに IAM ロールをバインドします。これがIRSA(IAM Roles for Service Accounts)です。
IRSA のしくみ
Pod → Service Account → OIDC Provider → IAM Role → AWS API
OIDC Provider セットアップ
# クラスターのOIDC Provider URLを取得
OIDC_ID=$(aws eks describe-cluster \
--name production \
--query "cluster.identity.oidc.issuer" \
--output text | cut -d '/' -f 5)
echo $OIDC_ID # Example: EXAMPLED539D4633E53DE1B716D3041E
# OIDC IDプロバイダーを作成
aws iam create-open-id-connect-provider \
--url https://oidc.eks.us-east-1.amazonaws.com/id/$OIDC_ID \
--client-id-list sts.amazonaws.com
IAM Role の作成(信頼ポリシー)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:default:app-sa",
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com"
}
}
}
]
}
ServiceAccount アノテーション
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/app-irsa-role
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
serviceAccountName: app-sa # ServiceAccountを指定
containers:
- name: app
image: app:latest
env:
# STS認証トークンは自動的にマウント
- name: AWS_ROLE_ARN
value: arn:aws:iam::123456789012:role/app-irsa-role
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
Terraform でのIRSA設定
# OIDC Providerの自動作成
module "irsa_oidc" {
source = "terraform-aws-modules/eks/aws//modules/irsa-oidc"
version = "~> 20.0"
create = true
url = module.eks.cluster_oidc_issuer_url
tags = {
Name = "prod-irsa"
}
}
# Podが使用するIAM Role
module "pod_role" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.0"
role_name_prefix = "app-irsa-"
assume_role_policy_statements = [
{
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals = {
type = "Federated"
identifiers = [module.irsa_oidc.arn]
}
conditions = [
{
test = "StringEquals"
variable = "${replace(module.irsa_oidc.url, "https://", "")}:sub"
values = ["system:serviceaccount:default:app-sa"]
}
]
}
]
# S3へのアクセス権限
role_policy_statements = [
{
effect = "Allow"
actions = ["s3:GetObject", "s3:ListBucket"]
resources = ["arn:aws:s3:::my-bucket/*"]
}
]
tags = {
Name = "app-irsa-role"
}
}
IRSAの利点:
- 短寿命トークン: STSは5分〜1時間の短い有効期限
- 細粒度制御: Pod単位でIAM権限を制御
- 監査容易: CloudTrail で誰がどのPodからアクセスしたかを追跡可能
- キー管理不要: IAMロール自動ローテーション
5. EKS Pod Identity(新方式)
2023年に導入された、IRSAをさらにシンプルにした認証方式です。
IRSA vs EKS Pod Identity
| 比較項目 | IRSA | Pod Identity |
|---|---|---|
| OIDC Provider | 手動構築必要 | AWS管理 |
| 信頼ポリシー | 複雑(条件指定) | シンプル |
| Token発行 | OIDC Provider | EKS Pod Identity Agent |
| セットアップ難易度 | 中程度 | 簡単 |
| 本番対応 | フル対応 | フル対応 |
EKS Pod Identity セットアップ
# Step 1: Pod Identity Addon を有効化
aws eks create-addon \
--cluster-name production \
--addon-name eks-pod-identity-agent \
--addon-version v1.3.0-eksbuild.1
# Step 2: IAM ロール作成
aws iam create-role \
--role-name app-pod-identity-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "pods.eks.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Step 3: Pod Identity Association を作成
aws eks create-pod-identity-association \
--cluster-name production \
--namespace default \
--service-account app-sa \
--role-arn arn:aws:iam::123456789012:role/app-pod-identity-role
Pod Identity用 Manifest
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
# IRSA時のようなアノテーション不要
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
serviceAccountName: app-sa
containers:
- name: app
image: app:latest
# 環境変数の設定不要(Pod Identity Agent が自動処理)
EKS Pod Identity の優位性:
- OIDC構築が不要
- 環境変数の設定が不要(Agent が自動化)
- シンプルで保守性向上
試験で狙われるポイント:
- IRSAはまだ主流だが、Pod Identity は新トレンド
- 両者の違いを理解する必要がある
6. Envelope Encryption(etcd暗号化 + KMS連携)
Kubernetesのetcd データベースには、Secret、ConfigMap、API認証情報など機密データが保存されます。EKSではこれをKMSで暗号化します。
Envelope Encryption のしくみ
Secret データ → (etcd が保有する)データ暗号化キー → KMS → マスターキー
KMS キー作成
resource "aws_kms_key" "eks_etcd" {
description = "EKS etcd encryption"
deletion_window_in_days = 7
enable_key_rotation = true
tags = {
Name = "eks-etcd-key"
}
}
resource "aws_kms_alias" "eks_etcd" {
name = "alias/eks-etcd"
target_key_id = aws_kms_key.eks_etcd.key_id
}
EKSクラスターでの暗号化有効化
resource "aws_eks_cluster" "main" {
name = "production"
version = "1.29"
# Envelope Encryption
encryption_config {
enable = true
provider {
key_arn = aws_kms_key.eks_etcd.arn
}
resources = ["secrets"]
}
# ... その他の設定
}
AWS CLI での確認
# 暗号化状態の確認
aws eks describe-cluster \
--name production \
--query "cluster.encryptionConfig"
# 出力例
[
{
"resources": ["secrets"],
"provider": {
"keyArn": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
}
]
暗号化の対象外:
- etcd内のすべてのデータではなく、Secretリソースのみ
- ConfigMap、Pod、Deployment等は暗号化されない(機密度低)
7. EKS Audit Logs
Kubernetes APIへのすべてのリクエストを記録し、CloudWatch Logsに出力します。
Audit Logs 有効化
resource "aws_eks_cluster" "main" {
name = "production"
enabled_cluster_log_types = [
"api",
"audit",
"authenticator",
"controllerManager",
"scheduler"
]
logging_service_configuration {
cloudwatch_logs_log_group_arn = "${aws_cloudwatch_log_group.eks_audit.arn}:*"
}
}
resource "aws_cloudwatch_log_group" "eks_audit" {
name = "/aws/eks/production/audit"
retention_in_days = 90
kms_key_id = aws_kms_key.cloudwatch_logs.arn
}
Audit Log 分析例
{
"apiVersion": "audit.k8s.io/v1",
"kind": "Event",
"level": "RequestResponse",
"timestamp": "2026-04-27T10:30:45.123Z",
"user": {
"username": "alice",
"uid": "12345",
"groups": ["developers"]
},
"sourceIPs": ["10.0.1.5"],
"verb": "get",
"objectRef": {
"apiVersion": "v1",
"kind": "Secret",
"namespace": "default",
"name": "db-password"
},
"requestObject": null,
"responseStatus": {
"code": 200
}
}
CloudWatch Insights でのクエリ例
fields @timestamp, user.username, verb, objectRef.kind
| filter verb = "delete" and objectRef.kind = "Secret"
| stats count() by user.username
8. GuardDuty EKS Protection
EKS上の異常な動作を自動検出するセキュリティサービスです。
GuardDuty 有効化
aws guardduty create-detector \
--enable \
--finding-publishing-frequency FINDING_PUBLISHING_FREQUENCY_FIFTEEN_MINUTES \
--kubernetes-protection '{
"AuditLogs": {
"Enable": true
},
"RuntimeMonitoring": {
"Enable": true
}
}'
検出する異常
| 異常パターン | 説明 |
|---|---|
| Reconnaissance | ポートスキャン、APIサーバー列挙 |
| CryptoCurrency Mining | 暗号資産マイニングプロセス実行 |
| Lateral Movement | クラスター内の横展開試行 |
| Privilege Escalation | 権限昇格試行 |
| Data Exfiltration | データ流出疑いのある外部通信 |
9. ネットワークポリシー(Calico / VPC CNI)
Kubernetesのネットワークポリシーはポッド間通信を制御します。
NetworkPolicy 例
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
# すべてのIngressを拒否(デフォルトdeny)
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-db
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
VPC CNI セキュリティグループ統合
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
annotations:
# VPC セキュリティグループを適用
vpc.amazonaws.com/pod-security-group-enforcement: "standard"
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: app:latest
ECR セキュリティ
1. イメージスキャン
基本スキャン vs 拡張スキャン(Inspector v2)
| 項目 | 基本スキャン | 拡張スキャン(Inspector v2) |
|---|---|---|
| 検出対象 | OS脆弱性のみ | OS + アプリケーション脆弱性 |
| カバー言語 | OS(Alpine, Debian等) | Java, Python, Node.js, Ruby等 |
| 精度 | 低(誤検知多い) | 高(ハッシュベース) |
| コスト | 無料 | スキャン画像数課金 |
| リアルタイム | オンデマンド | プッシュ時自動スキャン |
基本スキャン設定
resource "aws_ecr_repository" "app" {
name = "myapp"
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
encryption_configuration {
encryption_type = "KMS"
kms_key = aws_kms_key.ecr.arn
}
}
Inspector v2 の有効化
# Inspector v2 を有効化
aws ecr put-image-scanning-configuration \
--repository-name myapp \
--image-scanning-configuration scanOnPush=true
# 拡張スキャン有効化(Inspector)
aws inspector enable \
--resource-types ECR_IMG_PUSH ECR_IMG_REPOSITORY
スキャン結果の確認
# イメージのスキャン結果を取得
aws ecr describe-image-scan-findings \
--repository-name myapp \
--image-id imageTag=v1.0.0 \
--query "imageScanFindings.findingSeverityCounts"
# 出力例
{
"CRITICAL": 2,
"HIGH": 5,
"MEDIUM": 12
}
EventBridge での自動応答
resource "aws_cloudwatch_event_rule" "ecr_scan_high" {
name = "ecr-high-severity-scan"
description = "ECR scan findings with HIGH/CRITICAL severity"
event_pattern = jsonencode({
source = ["aws.inspector2"]
detail-type = ["Inspector2 Finding - Severity Change"]
detail = {
finding = {
severity = ["HIGH", "CRITICAL"]
}
resource = {
type = ["AWS_ECR_IMAGE"]
}
}
})
}
resource "aws_cloudwatch_event_target" "sns" {
rule = aws_cloudwatch_event_rule.ecr_scan_high.name
target_id = "SendToSNS"
arn = aws_sns_topic.security_alerts.arn
}
2. ライフサイクルポリシー
イメージの古いバージョンを自動削除し、ストレージコストを削減します。
{
"rules": [
{
"rulePriority": 1,
"description": "Delete untagged images older than 30 days",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 30
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 2,
"description": "Keep only last 10 tagged images",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["v"],
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 3,
"description": "Delete old development images",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["dev-"],
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 7
},
"action": {
"type": "expire"
}
}
]
}
Terraform での設定
resource "aws_ecr_lifecycle_policy" "app" {
repository = aws_ecr_repository.app.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Remove untagged images"
selection = {
tagStatus = "untagged"
countType = "sinceImagePushed"
countUnit = "days"
countNumber = 30
}
action = {
type = "expire"
}
},
{
rulePriority = 2
description = "Keep last 5 images per tag prefix"
selection = {
tagStatus = "tagged"
tagPrefixList = ["v"]
countType = "imageCountMoreThan"
countNumber = 5
}
action = {
type = "expire"
}
}
]
})
}
3. レプリケーション(クロスリージョン、クロスアカウント)
クロスリージョンレプリケーション
{
"rules": [
{
"destinations": [
{
"region": "eu-west-1",
"registryId": "123456789012"
},
{
"region": "ap-northeast-1",
"registryId": "123456789012"
}
],
"repositoryFilters": [
{
"filter": "prefix-list",
"filterValues": ["prod/"]
}
]
}
]
}
クロスアカウントレプリケーション
{
"rules": [
{
"destinations": [
{
"region": "us-east-1",
"registryId": "999888777666"
}
],
"repositoryFilters": [
{
"filter": "prefix-list",
"filterValues": ["shared/"]
}
]
}
]
}
ターゲットアカウント側の IAM ポリシー:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": [
"ecr:CreateRepository",
"ecr:ReplicateImage",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
}
]
}
4. イミュータブルタグ
一度プッシュしたイメージタグは上書き不可にします。本番運用でタグの一貫性を保証します。
resource "aws_ecr_repository" "app" {
name = "myapp"
image_tag_mutability = "IMMUTABLE" # デフォルトはMUTABLE
image_scanning_configuration {
scan_on_push = true
}
}
違反時の動作
# イミュータブルが有効な場合、上書きはエラー
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:v1.0.0
# Error: image tag v1.0.0 already exists in repository myapp
# 代わりにバージョンを上げる
docker tag app:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:v1.0.1
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:v1.0.1
ベストプラクティス:
- 本番環境ではイミュータブルを有効化
latestタグは避け、セマンティックバージョニングを採用
5. 暗号化(AES-256 デフォルト / KMS)
ECRリポジトリのイメージデータは暗号化されます。
デフォルト暗号化(AES-256)
# リポジトリの暗号化設定を確認
aws ecr describe-repositories \
--repository-names myapp \
--query "repositories[0].encryptionConfiguration"
# 出力: デフォルト設定
{
"encryptionType": "AES256"
}
KMS 暗号化への変更
resource "aws_ecr_repository" "app" {
name = "myapp"
encryption_configuration {
encryption_type = "KMS"
kms_key = aws_kms_key.ecr.arn
}
}
resource "aws_kms_key" "ecr" {
description = "ECR repository encryption"
deletion_window_in_days = 7
enable_key_rotation = true
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "Enable IAM User Permissions"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::123456789012:root"
}
Action = "kms:*"
Resource = "*"
},
{
Sid = "Allow ECR to use the key"
Effect = "Allow"
Principal = {
Service = "ecr.amazonaws.com"
}
Action = [
"kms:Decrypt",
"kms:GenerateDataKey"
]
Resource = "*"
}
]
})
}
暗号化のコスト影響:
- AES-256: 無料
- KMS: KMS APIコール課金(大規模運用で顕著)
ベストプラクティス・設計パターン
パターン1: ECS Fargate + Secrets Manager + ECS Exec
{
"family": "secure-production-app",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::123456789012:role/ecsTaskRole",
"containerDefinitions": [
{
"name": "app",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/app:v1.0.0",
"readonlyRootFilesystem": true,
"mountPoints": [
{
"containerPath": "/tmp",
"sourceVolume": "tmp"
}
],
"linuxParameters": {
"capabilities": {
"drop": ["ALL"],
"add": ["NET_BIND_SERVICE"]
}
},
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db-password"
},
{
"name": "API_KEY",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/api-key"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
],
"volumes": [
{
"name": "tmp",
"emptyDir": {}
}
]
}
パターン2: EKS + IRSA + Pod Security Standards
# Namespace設定
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
---
# IAM Role for Service Account
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: production
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/app-irsa-role
---
# Deployment(セキュリティ設定完全版)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
serviceAccountName: app-sa
automountServiceAccountToken: true
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/app:v1.0.0
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
volumeMounts:
- name: tmp
mountPath: /tmp
- name: secrets
mountPath: /etc/secrets
readOnly: true
env:
- name: AWS_ROLE_ARN
value: arn:aws:iam::123456789012:role/app-irsa-role
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
volumes:
- name: tmp
emptyDir: {}
- name: secrets
secret:
secretName: app-secrets
---
# NetworkPolicy:デフォルトdeny, ALBからのみ許可
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: TCP
port: 8080
パターン3: ECR セキュリティスタック
# ECRリポジトリ(完全セキュリティ構成)
resource "aws_ecr_repository" "app" {
name = "production/app"
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
encryption_configuration {
encryption_type = "KMS"
kms_key = aws_kms_key.ecr.arn
}
tags = {
Environment = "production"
}
}
# ライフサイクルポリシー
resource "aws_ecr_lifecycle_policy" "app" {
repository = aws_ecr_repository.app.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Delete untagged images after 30 days"
selection = {
tagStatus = "untagged"
countType = "sinceImagePushed"
countUnit = "days"
countNumber = 30
}
action = {
type = "expire"
}
}
]
})
}
# リポジトリポリシー(プル許可)
resource "aws_ecr_repository_policy" "app" {
repository = aws_ecr_repository.app.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowECSTaskExecutionRole"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::123456789012:role/ecsTaskExecutionRole"
}
Action = [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
]
}
]
})
}
# Inspector v2 有効化
resource "aws_inspector2_enabler" "ecr" {
resource_types = ["ECR_IMG_REPOSITORY"]
}
# EventBridge: 脆弱性検出時のアラート
resource "aws_cloudwatch_event_rule" "ecr_vuln_critical" {
name = "ecr-critical-vulnerability"
event_pattern = jsonencode({
source = ["aws.inspector2"]
detail-type = ["Inspector2 Finding - Severity Change"]
detail = {
finding = {
severity = ["CRITICAL"]
}
resource = {
type = ["AWS_ECR_IMAGE"]
}
}
})
}
resource "aws_cloudwatch_event_target" "sns" {
rule = aws_cloudwatch_event_rule.ecr_vuln_critical.name
target_id = "AlertSNS"
arn = aws_sns_topic.security_alerts.arn
}
試験で狙われるポイント(SCS-C03対策)
ECS
- タスク実行ロール vs タスクロール: 機能分離の理解(頻出)
- readonlyRootFilesystem + emptyDir: イミュータブルコンテナ設計
- awsvpc + SecurityGroup: ネットワーク隔離の精密制御
- Secrets Manager 権限: KMS復号権限の必要性
- ECS Exec 監査: CloudWatch Logs, Session Manager ログ
EKS
- IRSA vs Pod Identity: 認証方式の比較、OIDC理解(頻出)
- Pod Security Standards Restricted: nonrootユーザー強制、readOnlyFS必須
- IAM + RBAC: 二重認証モデルの設計
- Envelope Encryption: etcdのKMS暗号化
- Audit Logs + GuardDuty: 検出・対応体制
ECR
- 基本スキャン vs Inspector v2: 言語別脆弱性検出の違い
- イミュータブルタグ: 本番環境での整合性保証
- ライフサイクルポリシー: 自動削除ルール設計
- レプリケーション: クロスリージョン・クロスアカウント権限
横断的
- 最小権限の原則: 権限設計のセオリー
- 暗号化: AES-256 vs KMS のコスト・セキュリティトレードオフ
- 監査証跡: CloudTrail, CloudWatch Logs, Audit Logs の統合
- イミュータブル設計: FS、タグ、設定の変更不可化
制約事項・できないこと
| 機能 | ECS | EKS |
|---|---|---|
| クラスターのホスト管理 | AWS管理(Fargate)またはEC2 | ユーザー管理(worker nodes) |
| Kubernetes RBAC | 不可(IAMのみ) | 必須 |
| Pod Security Standards | 不可 | 必須(PSS/admission) |
| IRSA / Pod Identity | 不可 | 必須 |
| NetworkPolicy | 不可 | 必須(Calico / VPC CNI) |
| Sealed Secrets | 未サポート | サポート |
まとめ
- ECS: シンプルで管理容易。Fargate + Secrets Manager + ECS Exec で小〜中規模向け
- EKS: Kubernetes標準準拠。複雑だが拡張性高い。エンタープライズ向け
- ECR: イメージレジストリ。スキャン・暗号化・レプリケーションで流通管理
本番環境では、イミュータブル設計(FS, タグ)、最小権限(IRSA/Pod Identity)、完全監査(CloudTrail + Audit Logs)の三点セットを必ず実装してください。