I got IT

EKS Security - OPA 로 정책 적용하기 본문

AWS/EKS

EKS Security - OPA 로 정책 적용하기

joshhoxy 2025. 3. 16. 02:38

※ 해당 글은 CloudNet@ gasida 님의 EKS 스터디 내용을 참고하여 작성하였습니다.
또한 다음 EKS Hands on lab 을 참고하였습니다. 🔗

OPA 란?

Open Policy Agent(OPA)는 클라우드 네이티브 환경에서 정책 관리와 보안 운영 자동화를 지원하는 오픈 소스 범용 정책 엔진입니다. CNCF 의 프로젝트로, 다양한 플랫폼과 서비스에서 일관된 정책 집행을 가능하게 합니다. 즉 쿠버네티스 환경에서의 거버넌스 관리 구성을 위해 사용되는 유용한 도구라고 볼 수 있습니다.

OPA 도입 배경

기존의 접근 제어 및 정책 관리 솔루션들은 모던 애플리케이션 환경을 고려하지 않고 설계된 경우가 많아, 과거에 비해 훨씬 더 동적이고 빈번한 호출을 처리하는 현대적인 시스템을 효과적으로 관리하기 어렵습니다.

즉, 전통적인 방식으로는 오늘날의 복잡한 클라우드 네이티브 환경에서 일관된 정책 적용과 보안 통제를 유지하기 어려운 상황이 되었습니다.

OPA는 이러한 문제를 해결하기 위해 탄생한 프로젝트입니다. 단순히 조직의 KMS에 정책을 정리해두고 수동으로 관리하는 것이 아니라, 하나의 중앙 정책 엔진을 활용하여 다양한 시스템과 일관되게 통합하고 적용할 수 있도록 지원합니다.

이를 통해 각 애플리케이션이나 인프라가 개별적으로 인증과 정책을 관리할 필요 없이, OPA를 중심으로 통합된 정책 관리 체계를 구축할 수 있습니다. 즉, 여러 플랫폼과 서비스에서 동일한 정책을 적용하고 검증할 수 있도록 함으로써, 운영의 복잡성을 줄이고 보안성을 향상시킵니다.

OPA 의 특징

  • 정책 코드화: 사람이 이해할 수 있는 형식으로 정책을 정의하고 코드로 관리할 수 있습니다.
  • 중앙 집중식 정책 관리: Kubernetes 클러스터 내에서 정책을 중앙에서 설정하고 적용할 수 있습니다.
  • 자동 정책 검사: 새로운 리소스(Pod, Service, Ingress 등)가 생성될 때 정책을 자동으로 검사하여 허용 여부를 결정할 수 있습니다.
  • OPA 는 Rego 라는 별도의 Language 를 통해 정책을 생성/관리합니다. 따라서 이에대한 러닝커브가 있을 수 있습니다. Rego Language는 OPA Playground 에서 작성한 OPA 정책을 확인할 수 있습니다.

OPA 작동 방식과 구성요소

  1. Kubernetes API Server
    • Kubernetes 클러스터에서 모든 요청을 처리하는 핵심 컴포넌트입니다.
    • 사용자가 Pod, Service, Ingress 등을 생성하면 API Server가 이를 관리합니다.
  2. Policy CRD (Custom Resource Definition)
    • Policy Template CRD: 정책을 정의하는 템플릿입니다.
    • Policy Instance CRD: 개별 정책을 설정하고 적용하는 역할을 합니다.
  3. OPA (Open Policy Agent)
    • Kubernetes 내에서 정책을 실행하고 평가하는 엔진입니다.
    • Kubernetes의 리소스 변경을 감시하고 정책을 적용할 수 있도록 돕습니다.
  4. Gatekeeper
    • OPA와 Kubernetes API Server 사이에서 정책 평가 요청을 중개하는 역할을 합니다.
    • Admission Controller와 연동하여 정책을 위반하는 리소스가 배포되지 않도록 차단할 수 있습니다.
  5. Admission Controller
    • Kubernetes API Server에서 새로운 리소스 요청을 받으면 Admission Controller가 이를 검사합니다.
    • Gatekeeper를 통해 OPA 정책 검사를 수행한 후, 정책을 위반하는 경우 요청을 거부합니다.
즉 사용자는 Policy Template CRD 및 Policy Instance CRD를 정의하여 Kubernetes 클러스터에 정책을 설정합니다. API Server는 정책을 감지하고 OPA에 복제(Replicate)하여 적용합니다.
이후 사용자가 새로운 Pod, Service, Ingress 등을 생성하려는 리소스 요청이 발생하면 API Server는 요청을 Admission Controller
로 전달합니다.
이 때, OPA와 Kubernetes API Server의 중개역할을 수행하는 Gatekeeper가 OPA에게 정책 검토를 요청하고, 정책을 위반하는 경우 요청을 차단합니다.
또한 기존 Kubernetes 리소스가 정책을 준수하는지 정기적으로 감시(Audit) 합니다.

 

OPA 실습

그렇다면 OPA가 쿠버네티스 환경에서 어떻게 작동이 되는지 직접 실습을 통해 확인해보겠습니다.

⚠️ OPA 실습을 위해서는 cluster-admin의 역할을 가지고 있어야 합니다.

OPA Gatekeeper 설치

OPA 설치의 자세한 과정은 다음 공식문서를 참고합니다. 🔗

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.18.2/deploy/gatekeeper.yaml

해당 yaml을 어플라이 하면 꽤 많은 리소스가 생성이 됩니다. 어떤 것이 있는지 간단히 살펴봅니다.

 

아래의 명령을 실행하여 "audit-controller" 와 "controller-manager" 의 Log 를 확인합니다.

# audit-controller log 확인
kubectl logs -l control-plane=audit-controller -n gatekeeper-system

# gatekeeper-system log 확인
kubectl logs -l control-plane=controller-manager -n gatekeeper-system

아직 까지 config 및 정책을 설정한 것이 없기 때문에 별다른 로그가 발생하지 않습니다.

1️⃣ OPA 정책 실습 - 특정 컨테이너 이미지 제한하기

이제 실제 OPA의 정책을 생성해서 다음과 같은 거버넌스를 적용해봅니다. 특정 컨테이너의 사용을 제한하는 사례는 실무에서도 많이 적용됩니다.

  1. 등록된 Container Image 만을 허용
  2. Priviledge 권한이 부여된 Container 실행 차단
  3. 등록된 Repository 에서만 Container Image 사용을 허용

K8S 에 OPA Gatekeeper 의 정책을 적용하기 위해서는 "ConstraintTemplate" 과 "Constraint" 를 함께 구성해야 합니다.

  • ConstraintTemplate 는 정책의 전체적인 내용(무엇을 검사하고 어떤 경고 메시지를 보낼지 등)을 정의
  • Constraint 는 ConstraintTemplate 에 정의된 정책의 대상을 지정하는 용도입니다.

ConstraintTemplate 생성

mkdir -p environment/opa
cat  << EOF > environment/opa/constraint-template-image.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: enforceimagelist
spec:
  crd:
    spec:
      names:
        kind: enforceimagelist
      validation:
        openAPIV3Schema:
          properties:
            images:
              type: array
              items: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package enforceimagelist

        allowlisted_images = {images |
            images = input.parameters.images[_]
        }

        images_allowlisted(str, patterns) {
            image_matches(str, patterns[_])
        }

        image_matches(str, pattern) {
            contains(str, pattern)
        }

        violation[{"msg": msg}] {
          input.review.object
          image := input.review.object.spec.containers[_].image
          name := input.review.object.metadata.name
          not images_allowlisted(image, allowlisted_images)
          msg := sprintf("pod %q has invalid image %q. Please, contact Security Team. Follow the allowlisted images %v", [name, image, allowlisted_images])
        }
EOF

해당 템플릿은 허용되지 않은 컨테이너를 생성하는 것을 거부하는 정책을 선언합니다. 이때 허용된 이미지는 아래 Constraint로 설정하는 것입니다. 일종의 whitelist 방식의 접근제어라고 볼 수 있습니다.

또한 OPA는 rego라는 자체 랭귀지를 사용한다고 하였는데 이에 대한 syntax는 공식문서를 확인하고 playground에서 테스트 해볼 수 있습니다.

또한 이러한 정책문을 쉽게 작성할 수 있도록 ‘패키지’ 라는 일종의 모듈을 제공합니다. 이는 본문의 맨 아래 설명을 첨언하도록 하겠습니다.

Constraint 생성

cat  << EOF > environment/opa/constraint-image.yaml 
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: enforceimagelist
metadata:
  name: k8senforceallowlistedimages
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    images:
      - $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/eks-security-shared
      - amazon/aws-node-termination-handler
      - amazon/aws-alb-ingress-controller
      - amazon/aws-efs-csi-driver
      - amazon/cloudwatch-agent
      - docker.io/amazon/aws-alb-ingress-controller
      - grafana/grafana
      - prom/alertmanager
      - prom/prometheus
      - openpolicyagent/gatekeeper
      - amazon/aws-cli
      - busybox
      - nginx
      - falco
EOF

 

이제 실제 리소스를 생성하여 해당 정책을 적용하여 줍니다.

cd environment/opa
kubectl apply -f constraint-template-image.yaml 
kubectl apply -f constraint-image.yaml

 

아래 명령을 통해서 "constraintTemplate"에 적용된 정책을 확인할 수 있습니다.

# constraintTemplate 상세 내용 확인
kubectl get constrainttemplate -o yaml enforceimagelist

해당 내용을 살펴보면 허용된 이미지 외에는 violation에 적용되어 msg 에 명시된 메세지가 출력될 것입니다.

 

아래 명령을 통해서 "Constratint"의 상세 항목을 살펴볼 수 있습니다.

kubectl get constraint -o yaml k8senforceallowlistedimages

위와 같이 허용된 컨테이너 이미지 목록이 나옵니다. 더 아래로 내려가 보면 해당 정책에 위반된 컨테이너들에 대해서도 리스트가 출력됩니다.

현재 저 같은 경우에는 eks를 임의로 구성하였기 때문에 여러 컨테이너가 검출된 것을 확인할 수 있었습니다.

따라서 위에서 violation시 출력할 msg가 출력되는 것을 확인할 수 있습니다.

※ enforcementAction

위에서 처럼 정책 위반 리소스를 어떻게 처리할 지 결정하는 옵션을 enforcementAction 로 설정하는 데 두가지 옵션이 있습니다.

  • enforcementAction: deny : 규칙 위반 시 해당 리소스의 생성 또는 변경을 차단함 (강제 적용)
  • enforcementAction: dryrun : 규칙 위반 여부를 확인하지만 차단하지 않고 로그(Audit)만 남김 (테스트 모드)

⚠️ 이 때 해당 옵션을 따로 명시하지 않으면 기본값은 deny 입니다. 따라서 운영환경에서는 배포가 발생하지 않는 문제가 발생할 수도 있기 때문에 이러한 점을 고려해야 합니다.

다행히 기존 리소스에는 적용되진 않기 때문에 기존 파드가 삭제되거나 하지는 않습니다. enforcementAction: deny는 새로운 리소스가 생성되거나 변경될 때만 적용됩니다.

Container Image 사용제한 확인

이제 새로운 파드를 생성하여 정책이 잘 적용되는 지 확인하도록 합니다.

Constraint에 등록되지 않은 이미지의 컨테이너를 생성하기

cat  << EOF > pod-with-invalid-image.yaml
apiVersion: v1
kind: Pod
metadata:
  name: invalid-image
  labels:
    app: invalid-image
  namespace: default
spec:
  containers:
  - image: docker:latest
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: invalid-docker
  restartPolicy: Always
EOF

kubectl apply -f ~/environment/opa/pod-with-invalid-image.yaml

 

파드 생성을 요청하자 다음과 같이 거부되는 것을 볼 수 있습니다.

 

이번에는 Constraint 에 등록된, 즉 허용된 이미지의 컨테이너를 생성하도록 해봅니다.

cat  << EOF > pod-with-valid-image.yaml
apiVersion: v1
kind: Pod
metadata:
  name: valid-image
  labels:
    app: valid-image
  namespace: default
spec:
  containers:
  - image: busybox
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: valid-busybox
  restartPolicy: Always
EOF

kubectl apply -f ~/environment/opa/pod-with-valid-image.yaml

 

Constraint에 등록된 busybox 이미지는 정상적으로 생성이 되는 것을 확인할 수 있습니다.

Container Image 사용제한 정책 삭제

해당 정책과 생성된 파드를 삭제하여줍니다.

kubectl delete -f pod-with-valid-image.yaml
kubectl delete -f constraint-image.yaml
kubectl delete -f constraint-template-image.yaml 

➕ 특정 컨테이너 이미지 외에는 사용이 가능하게 끔 설정하기 (block)

위에서는 constraint 에 적용된 이미지만 사용가능하도록 했다면 이번에는 특정 이미지만 사용을 차단하고 이외에는 허용하는 정책을 적용해보겠습니다.

ConstraintTemplate 생성 (특정 이미지 차단)

cat << EOF > ~/environment/opa/constraint-template-block-image.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: blockimagelist
spec:
  crd:
    spec:
      names:
        kind: blockimagelist
      validation:
        openAPIV3Schema:
          properties:
            images:
              type: array
              items: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package blockimagelist

        blocked_images = {images |
            images = input.parameters.images[_]
        }

        images_blocked(str, patterns) {
            image_matches(str, patterns[_])
        }

        image_matches(str, pattern) {
            contains(str, pattern)
        }

        violation[{"msg": msg}] {
          input.review.object
          image := input.review.object.spec.containers[_].image
          name := input.review.object.metadata.name
          images_blocked(image, blocked_images)
          msg := sprintf("Pod %q uses blocked image %q. Please contact Security Team. Blocked images: %v", [name, image, blocked_images])
        }
EOF

 

Constraint 생성 (차단할 이미지 목록 설정)

cat << EOF > ~/environment/opa/constraint-block-image.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: blockimagelist
metadata:
  name: restricted-images
spec:
  enforcementAction: deny
  parameters:
    images:
      - "nginx:latest"
      - "ubuntu:20.04"
EOF

nginx와 ubuntu:20.04 이미지는 사용하지 못하도록 설정합니다. 이 외에는 사용할 수 있습니다.

 

이를 배포하여 테스트 해봅니다.

k apply -f constraint-template-block-image.yaml
k apply -f constraint-block-image.yaml

 

다음 명령어를 통해 정책에 위반되는 리소스가 있는지 살펴봅니다.

kubectl get constraint -o yaml restricted-images

 

제한된 이미지를 배포해보기

차단된 이미지를 배포 시도 해봅니다.

cat << EOF > ~/environment/opa/pod-blocked.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-blocked
spec:
  containers:
  - name: test-container
    image: nginx:latest
EOF

kubectl apply -f pod-blocked.yaml

 

다음과 같이 nginx 이미지를 사용한 컨테이너는 차단되는 것을 확인할 수 있습니다.

 

그렇다면 이외의 이미지는 생성이 가능한지도 확인해 봅니다.

허용된 이미지 배포해보기

cat << EOF > ~/environment/opa/pod-allowed.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-allowed
spec:
  containers:
  - name: test-container
    image: alpine:latest
EOF

kubectl apply -f pod-allowed.yaml

차단된 이미지 외에는 잘 배포가 되는 것이 확인됩니다.

2️⃣ OPA 정책 실습 - Privilege Container 사용 제한

Privileged 컨테이너란 호스트 시스템의 모든 리소스에 거의 완전한 접근 권한을 가지는 컨테이너를 의미합니다.
일반적으로 컨테이너는 리눅스 네임스페이스(Linux Namespaces)와 cgroup(Control Groups) 등을 이용하여 격리되지만, Privileged 컨테이너는 이러한 격리를 해제하여, 컨테이너가 거의 호스트 머신과 동일한 권한을 가질 수 있도록 설정됩니다.

따라서 Privileged 컨테이너가 탈취되면 호스트 머신의 권한을 갖게 되고 해당 서버를 탈취할 수 있게 되므로 사용하지 못하도록 하는 것이 일반적으로 권고됩니다.

Privilege Container 사용 제한 정책 생성

ConstraintTemplate 생성 (enforceprivilegecontainer)

cat  << EOF > ~/environment/opa/constraint-template-privileged.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: enforceprivilegecontainer
spec:
  crd:
    spec:
      names:
        kind: enforceprivilegecontainer
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package enforceprivilegecontainer

        violation[{"msg": msg, "details": {}}] {
            c := input_containers[_]
            c.securityContext.privileged
            msg := sprintf("Privileged container is not allowed: %v, securityContext: %v", [c.name, c.securityContext])
        }

        input_containers[c] {
            c := input.review.object.spec.containers[_]
        }

        input_containers[c] {
            c := input.review.object.spec.initContainers[_]
        }
EOF

해당 ConstraintTemplate은 Privileged 컨테이너를 차단하는 규칙을 정의합니다.
input_containers 함수

  • Pod 내부의 containers 및 initContainers를 검사하는 두 개의 규칙이 정의됨.
  • 즉, 일반 컨테이너와 Init 컨테이너 모두를 검사 대상으로 포함.

violation 조건

  • securityContext.privileged == true이면 정책 위반(violation)으로 판단.
  • 정책 위반 시, 다음과 같은 메시지를 반환: Privileged container is not allowed: my-container, securityContext: {privileged: true}

Constraint 생성

cat  << EOF > ~/environment/opa/constraint-privileged.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: enforceprivilegecontainer
metadata:
  name: privileged-container-security
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
EOF
  • match.kinds에서 Pod 리소스만 검사하도록 설정.
  • apiGroups: [""]는 기본 쿠버네티스 API 그룹을 의미함.
  • 즉, 쿠버네티스에서 모든 Pod 배포 요청이 이 정책의 영향을 받음.

아래의 명령을 실행하여 ConstraintTemplate와 constraint를 적용합니다.

kubectl apply -f ~/environment/opa/constraint-template-privileged.yaml
kubectl apply -f ~/environment/opa/constraint-privileged.yaml

 

다음 명령어를 실행하여 constraint 대상을 확인합니다.

kubectl get constraint -o yaml privileged-container-security

spec 을 보면 해당 constraint는 모든 쿠버네티스 리소스가 대상임을 의미합니다.

이때 컨트롤플레인의 kube-system 네임스페이스 파드의 컨테이너들은 기본적으로 privileged 컨테이너 이기 때문에 위반된 것으로 표시된 걸 볼 수 있습니다.

 

Privilege Container 생성해보기

"privileged: true" 를 통해 Privilege 권한을 허용한 Container 를 이용하는 Pod 을 배포합니다.

cat  << EOF > ~/environment/opa/privileged-container.yaml
apiVersion: v1
kind: Pod
metadata:
  name: privileged-container
  labels:
    role: privileged-container
  namespace: default
spec:
  containers:
  - image: busybox
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: privileged-container
    securityContext:
      privileged: true
  restartPolicy: Always
EOF

kubectl apply -f ~/environment/opa/privileged-container.yaml

이와 같이 허용되지 않은 Privileged를 사용한 Pod 의 배포는 Error 와 함께 차단되는 것을 확인할 수 있습니다.

 

그렇다면 일반적으로 privilege 설정을 false로 하는 파드를 배포하여 정책에 부합하는지 확인해봅니다. 일반적으로 사용하는 Nginx 파드를 배포해봅니다.

kubectl run non-privileged --image nginx

다음과 같이 파드가 성공적으로 생성된 것을 볼 수 있습니다.

 

3️⃣ OPA 정책 실습 - Repository 사용 제한하기

Repository 생성

실습에 사용할 리포지토리를 생성합니다.

aws ecr create-repository --repository-name eks-security-dev
aws ecr create-repository --repository-name eks-security-prd

 

nginx 이미지를 aws 퍼블릭 리포지토리에서 Pull 해옵니다.

docker pull public.ecr.aws/docker/library/nginx

 

해당 이미지를 위에서 생성한 리포지토리에 Push 해줍니다.

# 프라이빗 리포지토리에 push하기 이미지에 태그 달기
docker tag public.ecr.aws/docker/library/nginx $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/eks-security-dev
docker tag public.ecr.aws/docker/library/nginx $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/eks-security-prd
# ECR 로그인
aws ecr get-login-password --region $AWS_REGION | \
docker login --username AWS --password-stdin \
$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com

# eks-security-dev 리포에 푸시
docker push $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/eks-security-dev
docker push $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/eks-security-prd

 

프라이빗 리포지토리 push 하는 방법에 대한 자세한 설명은 공식문서를 참고합니다.

이미지가 업로드된 것을 확인할 수 있습니다.

 

Repository 사용 제한 정책 생성

환경별로 퍼블릭 리포지토리를 사용하지 못하도록 한다거나 특정 리포지토리만 사용하게끔 해야할 경우가 많이 있습니다. 이러할 때에도 OPA 정책을 통해서 관리가 가능합니다.

ConstraintTemplate 생성 (AllowedRepos)

아래의 명령을 실행하여 Repository 를 제한하는 ConstraintTemplate 을 생성합니다.

cat  << EOF > ~/environment/opa/constraint-template-repository.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: allowedrepos
  annotations:
    description: >-
      Requires container images to begin with a string from the specified list.
spec:
  crd:
    spec:
      names:
        kind: AllowedRepos
      validation:
        openAPIV3Schema:
          type: object
          properties:
            repos:
              description: The list of prefixes a container image is allowed to have.
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package allowedrepos

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          satisfied := [good | repo = input.parameters.repos[_] ; good = startswith(container.image, repo)]
          not any(satisfied)
          msg := sprintf("container <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.initContainers[_]
          satisfied := [good | repo = input.parameters.repos[_] ; good = startswith(container.image, repo)]
          not any(satisfied)
          msg := sprintf("initContainer <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.ephemeralContainers[_]
          satisfied := [good | repo = input.parameters.repos[_] ; good = startswith(container.image, repo)]
          not any(satisfied)
          msg := sprintf("ephemeralContainer <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
        }
EOF

Constraint 생성

이제 어떤 repository가 위 constraintTemplate의 적용을 받을지 지정합니다.

cat  << EOF > ~/environment/opa/constraint-repository.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: AllowedRepos
metadata:
  name: allowed-repo
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - "default"
  parameters:
    repos:
      - "$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/eks-security-dev"
EOF

이제 해당 정책을 배포하기 위해 다음 코드를 실행합니다.

kubectl apply -f ~/environment/opa/constraint-template-repository.yaml
kubectl apply -f ~/environment/opa/constraint-repository.yaml

 

다음 명령을 실행하여 허용된 repository 목록을 확인합니다.

kubectl get constraint -o yaml allowed-repo

Repository 사용 제한 확인

실제로 파드를 배포하여 리포지토리 제한 정책이 적용 되었는지 확인해봅니다.

허용되지 않은 Repository 사용 - Public

퍼블릭 리포지토리로부터 nginx 이미지를 pull 하여 파드를 생성해봅니다.

cat  << EOF > ~/environment/opa/disallowed-repository-public.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-from-public
spec:
  containers:
    - name: nginx
      image: nginx
      resources:
        limits:
          cpu: "100m"
          memory: "30Mi"
EOF

kubectl apply -f disallowed-repository-public.yaml

 

퍼블릭 repository를 사용하는 것을 차단하는 것을 확인할 수 있습니다.

 

허용되지 않은 Repository 사용 - ECR(eks-security-prd)

이번엔 사설 리포지토리를 사용하지만 허용된 repository가 아닌 prd 리포지토리를 사용하는 케이스를 테스트 해봅니다.

cat  << EOF > ~/environment/opa/disallowed-repository-prd.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-allowed
spec:
  containers:
    - name: shared-nginx
      image: $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/eks-security-prd
      command:
        - sleep
        - "3600"
      resources:
        limits:
          cpu: "100m"
          memory: "30Mi"
EOF

 

해당 리포지토리도 차단되는 것을 확인할 수 있습니다.

 

허용된 Repository 사용 - ECR(eks-security-dev)

다음 코드를 실행하여 eks-security-dev 리포지토리를 사용하여 파드를 배포합니다.

cat  << EOF > ~/environment/opa/allowed-repository.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-allowed
spec:
  containers:
    - name: shared-nginx
      image: $ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/eks-security-dev
      command:
        - sleep
        - "3600"
      resources:
        limits:
          cpu: "100m"
          memory: "30Mi"
EOF

kubectl apply -f allowed-repository.yaml

 

eks-security-dev 리포지토리는 유일하게 허용된 저장소이기때문에 해당 저장소는 사용이 가능하여 파드배포가 성공적으로 실행되었습니다.

 

4️⃣ 특정 네임스페이스에서만 리소스 생성 허용

이번에는 특정 네임스페이스(restricted)에서만 리소스를 생성할 수 있도록 제한해보도록 하겠습니다. 이때 다른 네임스페이스에서 리소스를 생성하면 차단되도록 합니다.

ConstraintTemplate 생성 (constraint-template-namespace.yaml)

cat  << EOF > ~/environment/opa/constraint-template-namespace.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: restrictnamespace
spec:
  crd:
    spec:
      names:
        kind: restrictnamespace
      validation:
        openAPIV3Schema:
          properties:
            allowedNamespaces:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package kubernetes.admission

        violation[{"msg": msg}] {
            input.review.object.metadata.namespace != "restricted"
            msg := sprintf("Namespace %v에서는 리소스를 생성할 수 없습니다. 허용된 네임스페이스: restricted", [input.review.object.metadata.namespace])
        }
EOF

Constraint 생성 (constraint-namespace.yaml)

cat  << EOF > ~/environment/opa/constraint-namespace.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: restrictnamespace
metadata:
  name: restrict-to-namespace
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod", "Deployment", "Service"]
  parameters:
    allowedNamespaces:
      - "restricted"
EOF

다음 명령어를 실행하여 ConstraintTemplate과 Constraint를 배포합니다.

kubectl apply -f constraint-template-namespace.yaml
kubectl apply -f constraint-namespace.yaml

 

현재 kubeconfig로 설정된 current-context의 네임스페이스를 확인해 봅니다.

kubectl config view --minify --output 'jsonpath={..namespace}'

저는 default로 설정되어 있습니다.

 

위에서 restricted 라는 네임스페이스에서만 리소스 생성을 가능하게끔 정책을 설정하였으므로 default 네임스페이스에서 리소스 생성(Pod, Deployment, Service)을 요청하면 다음과 같이 실패합니다

 

이제 restricted 라는 네임스페이스를 생성하고 해당 네임스페이스에서 파드를 생성해봅니다.

kubectl create ns restricted
kubectl run nginx --image nginx --namespace restricted

restricted 네임스페이스에서는 파드가 생성가능한 것을 확인할 수 있습니다.

 

5️⃣ 특정 네임스페이스에서는 특정 라벨이 필수

eks-security-project 라는 네임스페이스에서 리소스를 생성할 때 team 라벨이 반드시 포함되어야 하도록 합니다. team 라벨이 없는 리소스는 생성할 수 없습니다.

ConstraintTemplate 생성 (constraint-template-required-label.yaml)

cat  << EOF > ~/environment/opa/constraint-template-required-label.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: requiredlabel
spec:
  crd:
    spec:
      names:
        kind: requiredlabel
      validation:
        openAPIV3Schema:
          properties:
            label:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package kubernetes.admission

        violation[{"msg": msg}] {
            input.review.object.metadata.namespace == "eks-security-project"
            not input.review.object.metadata.labels["team"]
            msg := sprintf("Namespace 'eks-security-project'에서는 'team' 라벨이 필수입니다. (리소스: %v)", [input.review.object.metadata.name])
        }
EOF

Constraint 생성 (constraint-required-label.yaml)

cat << EOF > ~/environment/opa/constraint-required-label.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: requiredlabel
metadata:
  name: require-team-label
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod", "Deployment", "Service"]
  parameters:
    label: "team"
EOF
kubectl apply -f constraint-template-required-label.yaml
kubectl apply -f constraint-required-label.yaml

constraintTemplate과 constraint 를 확인합니다.

kubectl get constrainttemplate
kubectl get constraint

정책 설정 확인하기

정책 설정 확인을 위해 eks-security-project 네임스페이스를 생성합니다.

kubectl create ns eks-security-project

 

해당 네임스페이스에서 팀 라벨 없이 파드를 생성해봅니다.

kubectl run nginx --image nginx -n eks-security-project

팀라벨을 필수로 지정하라는 메세지와 함께 해당 요청은 실패합니다.

 

팀라벨을 지정하여 파드를 생성해봅니다.

k run nginx --image nginx -n eks-security-project --labels=team=aews

라벨을 지정하자 파드 생성에 성공한 것을 확인할 수 있습니다.

 

6️⃣ 응용하기: 특정 라벨이 없으면 생성 금지 및 특정 라벨을 지정하면 리소스 제한

특정 라벨(예: "Purpose": "test")이 지정된 Pod에 대해서만 CPU 및 메모리 리소스 제한을 적용합니다. 해당 라벨이 없는 Pod는 리소스 제한 정책을 적용하지 않음.

제한 기준은 다음과 같습니다.

  • CPU: 최대 2 코어
  • 메모리: 최대 4Gi

또한 위에서 사용한 정책을 그대로 유지하여 team 라벨을 반드시 지정하도록 합니다. 이러한 사용사례는 실제로 실무에서도 많이 쓰이곤 합니다.

특정 라벨을 지정하면 리소스 제한 적용

ConstraintTemplate 생성 (constraint-template-restricted-resources.yaml)

cat << EOF > ~/environment/opa/constraint-template-restricted-resources.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: restrictresourcelimits
spec:
  crd:
    spec:
      names:
        kind: restrictresourcelimits
      validation:
        openAPIV3Schema:
          properties:
            labelKey:
              type: string
            labelValue:
              type: string
            maxCPU:
              type: number
            maxMemory:
              type: number
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package kubernetes.admission

        violation[{"msg": msg}] {
            # Pod에 특정 라벨이 포함되어 있는지 확인
            input.review.object.metadata.labels[input.parameters.labelKey] == input.parameters.labelValue

            # Pod의 컨테이너를 확인
            container := input.review.object.spec.containers[_]

            # CPU 제한 초과 검사
            cpu_limit := to_number(trim(container.resources.limits.cpu, "m")) / 1000
            cpu_limit > input.parameters.maxCPU
            msg := sprintf("Pod %v의 CPU 리소스 제한 초과: %v (최대 허용: %v)", 
                           [input.review.object.metadata.name, cpu_limit, input.parameters.maxCPU])
        }

        violation[{"msg": msg}] {
            # Pod에 특정 라벨이 포함되어 있는지 확인
            input.review.object.metadata.labels[input.parameters.labelKey] == input.parameters.labelValue

            # Pod의 컨테이너를 확인
            container := input.review.object.spec.containers[_]

            # 메모리 제한 초과 검사
            memory_limit := to_number(trim(container.resources.limits.memory, "Gi"))
            memory_limit > input.parameters.maxMemory
            msg := sprintf("Pod %v의 Memory 리소스 제한 초과: %vGi (최대 허용: %vGi)", 
                           [input.review.object.metadata.name, memory_limit, input.parameters.maxMemory])
        }
EOF

특정 라벨을 검사하고 CPU와 Memory를 위반하는 지 체크합니다.

Constraint 생성 (constraint-restricted-resources.yaml)

cat << EOF > ~/environment/opa/constraint-restricted-resources.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: restrictresourcelimits
metadata:
  name: enforce-restricted-label-resources
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    labelKey: "Purpose"
    labelValue: "test"
    maxCPU: 2
    maxMemory: 4
EOF

템플릿에서 적용될 파라미터를 설정합니다.

다음 명령어를 실행하여 ConstraintTemplate과 Constraint를 배포합니다.

kubectl apply -f constraint-template-restricted-resources.yaml
kubectl apply -f constraint-restricted-resources.yaml

 

constraint 정책 확인하기

kubectl get constrainttemplate
kubectl get constraint

정책을 확인하면 다음과 같이 두가지 정책이 적용되어 있는 것을 볼수 있습니다.

즉, eks-security-project 에서는 반드시 team 라벨을 지정하여야 하고, Purpose=test 라벨을 지정하였을 경우 파드 생성 리소스에 제한을 받게됩니다.

정책 설정 확인하기

파드를 배포하기전에 현재 context를 eks-security-project 네임스페이스를 사용하도록 설정합니다.

kubectl config set-context --current --namespace eks-security-project
kubectl config view | grep namespace

 

my-nginx-service 파드 작성

cat << EOF > ~/environment/opa/my-nginx-service.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-nginx-service
  labels:
      team: "aews"
    Purpose: "service" 
spec:
  containers:
  - name: test-container
    image: nginx
    resources:
      limits:
        cpu: "3"    
        memory: "5Gi"
EOF

my-nginx-test 파드 작성

cat << EOF > ~/environment/opa/my-nginx-test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-nginx-test
  labels:
      team: "aews"
    Purpose: "test"
spec:
  containers:
  - name: test-container
    image: nginx
    resources:
      limits:
        cpu: "3"    
        memory: "5Gi"
EOF

 

i) 파드 배포. 팀 라벨 설정 x

 

ii) 파드 배포. 팀 라벨 설정. Purpose 라벨 service

✅ team 라벨을 설정했기 때문에 requiredlabel 정책을 패스

✅ Purpose 라벨이 test가 아니기 때문에 restrictresourcelimits 정책을 패스

 

iii) 파드 배포. 팀 라벨 설정. Purpose 라벨 test

✅ team 라벨을 설정했기 때문에 requiredlabel 정책을 패스

❌ Purpose 라벨이 test인데 최대 request 인 2cpu 와 4Mem을 초과하였기 때문에 restrictresourcelimits 정책을 통과하지 못함

 

7️⃣ 리소스 삭제하기

생성한 리소스를 모두 삭제합니다.

cd ~/environment/opa
kubectl delete -f ./

Kyverno VS OPA

사실 쿠버네티스 정책을 관리하는 데에는 OPA보다는 kyverno를 사용하는게 더 대중적입니다. 사용방법이 간단하고 Yaml 기반으로 정책을 설정하기 때문에 좀더 친화적입니다.

하지만 점점 다양한 CICD 도구가 생겨나고 같이 데브옵스 환경에서 같이 사용되고 있기 때문에 다양한 도구를 통합해서 하나의 거버넌스 플랫폼 처럼 사용하는데 있어서 OPA의 사용도 늘어나고 있습니다.

앞으로 더 어떤 기능이 지원되고 사용사례가 생겨나느냐에 따라 업계 defacto가 될지는 지켜봐야 할 것 같습니다.

  Kyverno OPA(Gatekeeper)
정책 엔진 Kyverno (전용 엔진) OPA (Open Policy Agent)
정책 정의 언어 YAML 기반 (Kubernetes 리소스와 유사) Rego (OPA 전용 정책 언어)
설치 방식 Kubernetes 컨트롤러 (Helm으로 배포) Admission Controller (Webhook 기반)
적용 방식 Mutating & Validating Validating Only (Gatekeeper)
정책 실행 유형 Admission Control, Mutation, Generation, Background Scan Admission Control, Background Scan
정책 적용 예제 Kubernetes 네이티브 YAML OPA의 Rego 언어 사용
RBAC & 인증 연동 네이티브 RBAC 기반 IAM, JWT 등과 연동 가능
학습 곡선 쉬움 (YAML 기반) 어려움 (Rego 학습 필요)

 

kyverno 와 OPA는 다음과 같이 지원되는 기능에 있어서도 조금 차이가 있습니다.

기능 Kyverno OPA(Gatekeeper)
정책 검증 (Validating) ✅ 지원 ✅ 지원
정책 수정 (Mutating) ✅ 지원 ❌ 기본 지원 안됨 (OPA는 Mutating 기능 없음)
정책 생성 (Generation) ✅ 지원 ❌ 지원 안됨
RBAC 적용 ✅ 지원 ✅ 지원
API 인증/인가 ❌ 지원 안됨 ✅ 지원 가능 (JWT, OAuth 등)
Terraform, API Gateway 정책 적용 ❌ 지원 안됨 ✅ 지원 가능
커스텀 정책 작성 쉬움 (YAML) 어려움 (Rego 언어 필요)
CI/CD 및 Compliance 검사 ✅ 지원 ✅ 지원
정책 감사 (Audit) ✅ 지원 ✅ 지원

 

장단점

Kyverno는 Kubernetes 네이티브 정책 적용에 최적화된 솔루션입니다. YAML 기반 정책을 사용하기 때문에 쉽게 적용할 수 있으며, 빠르게 운영할 수 있습니다. OPA(Gatekeeper)는 Kubernetes뿐만 아니라 Terraform, API Gateway, Cloud IAM 등 다양한 환경에서 정책을 적용할 수 있는 강력한 솔루션입니다.

Mutation 기능이 필요하시면 Kyverno를, 보다 정교한 보안 및 인증 정책이 필요하시면 OPA를 사용하시는 것이 좋습니다. 조직의 요구사항에 따라 Kyverno와 OPA를 병행하여 활용하는 것도 가능합니다.

※ 부록

OPA Package

OPA Package 란?

package는 Rego 정책 코드에서 네임스페이스(이름공간) 역할을 하는 개념입니다.

  • OPA(Open Policy Agent)는 정책을 다양한 모듈로 나누어 관리할 수 있도록 설계되어 있습니다.
  • 이를 위해, 각 정책 파일은 특정 package에 속하게 되며, 같은 package 내에서는 변수나 함수 등을 공유할 수 있습니다.
  • package를 사용하면 정책을 계층적으로 정리할 수 있어, 코드 관리와 모듈화가 용이합니다.

지원되는 package 목록 확인 방법

OPA의 공식 문서를 통해, 지원되는 package 목록을 확인할 수 있습니다.OPA에서 사용되는 대표적인 패키지 목록은 다음과 같습니다.

패키지 설명
kubernetes.admission 쿠버네티스 Admission Controller 정책
kubernetes.security 쿠버네티스 컨테이너 보안 정책
kubernetes.network 네트워크 정책 검토
http.authz HTTP API 인증/인가 정책
system.log 시스템 로그 모니터링
data OPA에서 로드된 데이터 참조
rego OPA 내장 기능

 

이렇듯 kubernetes.admission, kubernetes.security, kubernetes.network 등의 패키지를 사용해 관련된 리소스의 정책을 적용하는데 사용됩니다. 이 때 OPA는 Gatekeeper를 통해 k8s admission controller와 OPA 엔진을 연동하는 것입니다.

 

'AWS > EKS' 카테고리의 다른 글

K8S 환경에서 CI/CD 구축하기 - 1  (0) 2025.03.30
EKS Auto Mode  (0) 2025.03.23
EKS Security - ECR Enhanced Scanning  (0) 2025.03.16
EKS Autoscaling - 3 (Karpenter)  (0) 2025.03.09
EKS Autoscaling - 2 (Node level)  (0) 2025.03.09