上級 40分 Lesson 21

コンテナセキュリティ — ECS・EKS・ECR

ECSタスクロール、EKS IRSA/Pod Identity、ECRスキャン、コンテナランタイムセキュリティを徹底解説

AWS ECS EKS ECR SCS-C03 コンテナ Security

概要

コンテナセキュリティは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起動タイプのセキュリティ差異

項目FargateEC2
ホスト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の前提条件

  1. タスク実行ロール: ssmmessages:* 権限が必要
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssmmessages:CreateControlChannel",
        "ssmmessages:CreateDataChannel",
        "ssmmessages:OpenControlChannel",
        "ssmmessages:OpenDataChannel"
      ],
      "Resource": "*"
    }
  ]
}
  1. 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_SERVICE1024以下のポートバインド削除
SYS_ADMIN管理権限削除推奨
SETFCAPファイルcapability設定削除推奨
NET_RAWRAWソケット生成削除推奨

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

比較項目IRSAPod Identity
OIDC Provider手動構築必要AWS管理
信頼ポリシー複雑(条件指定)シンプル
Token発行OIDC ProviderEKS 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

  1. タスク実行ロール vs タスクロール: 機能分離の理解(頻出)
  2. readonlyRootFilesystem + emptyDir: イミュータブルコンテナ設計
  3. awsvpc + SecurityGroup: ネットワーク隔離の精密制御
  4. Secrets Manager 権限: KMS復号権限の必要性
  5. ECS Exec 監査: CloudWatch Logs, Session Manager ログ

EKS

  1. IRSA vs Pod Identity: 認証方式の比較、OIDC理解(頻出)
  2. Pod Security Standards Restricted: nonrootユーザー強制、readOnlyFS必須
  3. IAM + RBAC: 二重認証モデルの設計
  4. Envelope Encryption: etcdのKMS暗号化
  5. Audit Logs + GuardDuty: 検出・対応体制

ECR

  1. 基本スキャン vs Inspector v2: 言語別脆弱性検出の違い
  2. イミュータブルタグ: 本番環境での整合性保証
  3. ライフサイクルポリシー: 自動削除ルール設計
  4. レプリケーション: クロスリージョン・クロスアカウント権限

横断的

  • 最小権限の原則: 権限設計のセオリー
  • 暗号化: AES-256 vs KMS のコスト・セキュリティトレードオフ
  • 監査証跡: CloudTrail, CloudWatch Logs, Audit Logs の統合
  • イミュータブル設計: FS、タグ、設定の変更不可化

制約事項・できないこと

機能ECSEKS
クラスターのホスト管理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)の三点セットを必ず実装してください。