OPA는 K8S 뿐만 아니라 다양한 영역에서 운영환경에 대한 정책을 적용하고 통합하기 위해 사용되는 Open Source 기반의 플랫폼입니다. 현재 CNCF 의 Graduated Project 상태로 정책의 코드화된 관리를 목적으로 다양하게 사용되고 있습니다. AWS 에서는 EKS 뿐만 아니라 AWS Config 서비스에서도 OPA 기반의 Config 규칙을 사용하실 수 있습니다.
OPA 는 Rego 라는 별도의 Language 를 통해 정책을 생성/관리합니다. Rego Language는 OPA Playground 에서 작성한 OPA 정책을 확인할 수 있습니다.
OPA Gatekeeper 는 OPA 에서 생성된 정책을 K8S 환경에서 적용하기 위해 사용하는 K8S 를 위한 Adminssion Webhook 입니다. OPA Gatekeep 의 기본적인 Flow 는 아래와 같습니다.
ENV
REGION_CODE=$(aws configure get default.region --output text)
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
Shell
복사
OPA Gatekeeper Install
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.17.1/deploy/gatekeeper.yaml
Shell
복사
kubectl get pods -n gatekeeper-system
Shell
복사
# 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
Shell
복사
실습
Container Image 제한하기
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])
}
YAML
복사
# ConstraintTemplate
kubectl apply -f constraint-template-image.yaml
Shell
복사
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: enforceimagelist
metadata:
name: k8senforceallowlistedimages
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
images:
- 362708816803.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
YAML
복사
# Constraint
kubectl apply -f constraint-image.yaml
Shell
복사
kubectl get constrainttemplate
kubectl get constraint
Shell
복사
# constraintTemplate 상세 내용 확인
kubectl get constrainttemplate -o yaml enforceimagelist
# constraint 의 상세 내용을 확인
kubectl get constraint -o yaml k8senforceallowlistedimages
Shell
복사
OPA 정책에 위반된 Pod를 배포 (Error)
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
YAML
복사
kubectl apply -f pod-with-invalid-image.yaml
Shell
복사
# Error 발생
Error from server (Forbidden): error when creating "/home/ec2-user/environment/opa/pod-with-invalid-image.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [k8senforceallowlistedimages] pod "invalid-image" has invalid image "docker:latest". Please, contact Security Team. Follow the allowlisted images {"300861432382.dkr.ecr.ap-northeast-2.amazonaws.com/eks-security-shared", "amazon/aws-alb-ingress-controller", "amazon/aws-cli", "amazon/aws-efs-csi-driver", "amazon/aws-node-termination-handler", "amazon/cloudwatch-agent", "busybox", "docker.io/amazon/aws-alb-ingress-controller", "falco", "grafana/grafana", "nginx", "openpolicyagent/gatekeeper", "prom/alertmanager", "prom/prometheus"}
Shell
복사
OPA 정책에 위반 되지 않은 Pod를 배포 (Success)
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
YAML
복사
kubectl apply -f pod-with-valid-image.yaml
Shell
복사
•
Container Image 사용제한 정책 삭제
kubectl delete -f pod-with-valid-image.yaml
kubectl delete -f constraint-image.yaml
kubectl delete -f constraint-template-image.yaml
Shell
복사
Privilege Container 사용 제한하기
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[_]
}
YAML
복사
# ConstraintTemplate
kubectl apply -f constraint-template-privileged.yaml
Shell
복사
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: enforceprivilegecontainer
metadata:
name: privileged-container-security
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
YAML
복사
# Constraint
kubectl apply -f constraint-privileged.yaml
Shell
복사
# constraintTemplate 상세 내용 확인
kubectl get constrainttemplate -o yaml enforceprivilegecontainer
# constraint 의 상세 내용을 확인
kubectl get constraint -o yaml privileged-container-security
Shell
복사
허용되지 않은 Privileged를 사용한 Pod 배포 (Error)
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 # true or false
restartPolicy: Always
YAML
복사
kubectl apply -f privileged-container.yaml
Shell
복사
"privileged:" 값이 "false" 로 처리되어 있는 Pod를 배포 (Success)
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: false # true or false
restartPolicy: Always
YAML
복사
kubectl apply -f privileged-container.yaml
Shell
복사
•
Container Image 사용제한 정책 삭제
kubectl delete -f privileged-container.yaml
kubectl delete -f constraint-privileged.yaml
kubectl delete -f constraint-template-privileged.yaml
Shell
복사
Repository 사용 제한하기
nginx image 를 Pull 후 "eks-security-shared" Repository에 업로드할 수 있도록 Tag를 부여
aws ecr get-login-password --region $REGION_CODE | \
docker login --username AWS --password-stdin \
$AWS_ACCOUNT_ID.dkr.ecr.$REGION_CODE.amazonaws.com
Shell
복사
docker pull public.ecr.aws/docker/library/nginx
docker tag public.ecr.aws/docker/library/nginx $AWS_ACCOUNT_ID.dkr.ecr.$REGION_CODE.amazonaws.com/eks-security-shared
Shell
복사
docker push $AWS_ACCOUNT_ID.dkr.ecr.$REGION_CODE.amazonaws.com/eks-security-shared
Shell
복사
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])
}
YAML
복사
# ConstraintTemplate
kubectl apply -f constraint-template-repository.yaml
Shell
복사
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: AllowedRepos
metadata:
name: allowed-repo
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- "default"
parameters:
repos:
- "362708816803.dkr.ecr.ap-northeast-2.amazonaws.com/"
YAML
복사
# constraint
kubectl apply -f constraint-repository.yaml
Shell
복사
kubectl get constrainttemplate
kubectl get constraint
Shell
복사
등록되지 않은 Repository 를 이용한 Pod 배포 (Error)
apiVersion: v1
kind: Pod
metadata:
name: nginx-disallowed
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
cpu: "100m"
memory: "30Mi"
YAML
복사
kubectl apply -f disallowed-repository.yaml
Shell
복사
등록된 ECR Repository 를 사용하도록 하는 Pod를 배포 (Success)
apiVersion: v1
kind: Pod
metadata:
name: nginx-allowed
spec:
containers:
- name: shared-nginx
image: 362708816803.dkr.ecr.ap-northeast-2.amazonaws.com/eks-security-shared
command:
- sleep
- "3600"
resources:
limits:
cpu: "100m"
memory: "30Mi"
YAML
복사
kubectl apply -f allowed-repository.yaml
Shell
복사
•
Repository 사용제한 정책 삭제
kubectl delete -f allowed-repository.yaml
kubectl delete -f constraint-repository.yaml
kubectl delete -f constraint-template-repository.yaml
Shell
복사
특정 namespace(dev)의 deployment에 nodeSelector 강제 할당
apiVersion: mutations.gatekeeper.sh/v1
kind: Assign
metadata:
name: mutator-dev-ns
namespace: gatekeeper-system
spec:
applyTo:
- groups: ["apps"]
kinds: ["Deployment"]
versions: ["v1"]
match:
namespaces:
- "dev"
location: "spec.template.spec.nodeSelector"
parameters:
assign:
value:
eks.amazonaws.com/nodegroup: skills-eks-nodegroup
YAML
복사
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: sample
template:
metadata:
labels:
app: sample
spec:
containers:
- name: sample
image: nginx
resources:
requests:
memory: "128Mi"
cpu: "128m"
limits:
memory: "128Mi"
cpu: "128m"
ports:
- containerPort: 8080
YAML
복사
kube-system은 모든 정책 제외
apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
name: kube-system-ns-exclude-config
namespace: gatekeeper-system
spec:
match:
- excludedNamespaces: ["kube-system"]
processes: ["*"]
YAML
복사
dev ns의 latest tag image 생성 금지
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdisallowlatesttag
spec:
crd:
spec:
names:
kind: K8sDisallowLatestTag
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdisallowlatesttag
violation[{"msg": msg}] {
input.review.kind.kind == "Deployment"
container := input.review.object.spec.template.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("image '%v' uses the ':latest' tag, which is not allowed", [container.image])
}
YAML
복사
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDisallowLatestTag
metadata:
name: disallow-latest-tag-in-dev
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
namespaces:
- dev
YAML
복사
dev namespace의 모든 pod에 label 자동 삽입
apiVersion: mutations.gatekeeper.sh/v1alpha1
kind: AssignMetadata
metadata:
name: add-gk-label-to-dev-pods
namespace: gatekeeper-system
spec:
match:
scope: Namespaced
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["dev"]
location: "metadata.labels.controlled-by" # <--- controlled-by: gatekeeper
parameters:
assign:
value: "gatekeeper"
YAML
복사