1. Gatekeeper 개요
쿠버네티스 환경에서 정책제어를 할 수 있도록 특화된 솔루션으로 OPA(Open Policy Agent) 엔진을 사용하여 클러스터에 대한 승인, 제어를 할 수 있습니다.
OPA란?
플랫폼 관리자가 시스템 권한 관리를 체계적으로 할 수 있도록 도와주는 오픈소스 솔루션으로 쿠버네티스 뿐만 아니라 OPA 엔진은 이해하는 모든 플랫폼에서 동작합니다. 요청이 들어오면 서비스는 JSON을 이용하여 OPA에 허용 여부 질의를 하고, 질의를 받은 OPA는 저장된 Policy를 불러와서 요청에 대한 평가를 하고 다시 JSON 형식으로 서비스에 반환합니다.
OPA는 Policy를 기반으로하여 사용자 접근을 관리하기 때문에 Policy를 어떻게 작성하는 지가 중요하며, Policy는 Rego라는 자체 질의언어를 이용하여 작성하기 때문에 해당 문법에 대한 이해가 필요합니다.
Gatekeeper란?
Gatekeeper는 내부적으로 OPA 엔진을 사용하는 쿠버네티스 승인/제어를 위해 제작된 솔루션입니다.
쿠버네티스에서는 정책적인 결정을 API 서버와 분리하여 독립적으로 할 수 있게 Admission Controller Webhook(이하 웹훅)을 제공합니다. 웹훅은 쿠버네티스 클러스터가 변경될 때 무조건 실행되며, Gatekeeper는 웹훅을 확인하여 OPA 정책 엔진에서 정의한 대로 실행합니다.
Gatekeeper 구성요소
Validating Admission Control: 웹훅을 트리거로 OPA와 api-server의 다리 역할을 하여 정책을 적용
Policies and Constraints: Rego 언어로 작성되며 정의한 요구사항을 위반하는 리소스들을 확인
Audit: 배포된 자원을 정기적으로 감사하며 Constraints에 반하는 것이 있는지 확인
Data Replication: 리소스를 복제 한 후 감사가 진행되며 복제할 수 있도록 권한 부여가 필요
2. Gatekeeper 사용
상태 확인
(1) Gatekeeper 파드가 정상적으로 Running 중인지 확인합니다.
kubectl -n playcekube get all | grep Gatekeeper pod/Gatekeeper-audit-696f978cd9-fj6nz 1/1 Running 1 (6h55m ago) 6h56m pod/Gatekeeper-controller-manager-6f98779979-h9sxv 1/1 Running 0 6h56m pod/Gatekeeper-controller-manager-6f98779979-r7nzh 1/1 Running 0 6h56m pod/Gatekeeper-controller-manager-6f98779979-sc6fx 1/1 Running 0 6h56m service/Gatekeeper-webhook-service ClusterIP 10.233.21.178 <none> 443/TCP 6h56m deployment.apps/Gatekeeper-audit 1/1 1 1 6h56m deployment.apps/Gatekeeper-controller-manager 3/3 3 3 6h56m replicaset.apps/Gatekeeper-audit-696f978cd9 1 1 1 6h56m replicaset.apps/Gatekeeper-controller-manager-6f98779979 3 3 3 6h56m
Gatekeeper-library 다운로드
(1) Gatekeeper 정책 샘플 라이브러리를 다운로드합니다.
# git clone https://github.com/open-policy-agent/Gatekeeper-library.git
# ls -l total 76 -rw-r--r-- 1 root root 11357 Jan 16 16:53 LICENSE -rw-r--r-- 1 root root 3377 Jan 16 16:53 Makefile -rw-r--r-- 1 root root 62 Jan 16 16:53 NOTICE -rw-r--r-- 1 root root 4333 Jan 16 16:53 README.md drwxr-xr-x 3 root root 4096 Jan 16 16:53 artifacthub drwxr-xr-x 4 root root 4096 Jan 16 16:53 build drwxr-xr-x 3 root root 4096 Jan 16 16:53 docs -rw-r--r-- 1 root root 83 Jan 16 16:53 go.work -rw-r--r-- 1 root root 336 Jan 16 16:53 go.work.sum drwxr-xr-x 4 root root 4096 Jan 16 16:53 library drwxr-xr-x 3 root root 4096 Jan 16 16:53 mutation drwxr-xr-x 5 root root 4096 Jan 16 16:53 scripts drwxr-xr-x 5 root root 4096 Jan 16 16:53 src drwxr-xr-x 3 root root 4096 Jan 16 16:53 test -rwxr-xr-x 1 root root 338 Jan 16 16:53 test.sh drwxr-xr-x 5 root root 4096 Jan 16 16:53 website
샘플 정책 적용
(1) Gatekeeper 정책 샘플 중에 아내 requiredlabels를 적용해보겠습니다.
root@playcekube-test-deploy:/tmp/Gatekeeper-library/library/general# ls -l total 100 drwxr-xr-x 3 root root 4096 Jan 16 16:53 allowedrepos drwxr-xr-x 3 root root 4096 Jan 16 16:53 automount-serviceaccount-token drwxr-xr-x 3 root root 4096 Jan 16 16:53 block-endpoint-edit-default-role drwxr-xr-x 3 root root 4096 Jan 16 16:53 block-loadbalancer-services drwxr-xr-x 3 root root 4096 Jan 16 16:53 block-nodeport-services drwxr-xr-x 3 root root 4096 Jan 16 16:53 block-wildcard-ingress drwxr-xr-x 3 root root 4096 Jan 16 16:53 containerlimits drwxr-xr-x 3 root root 4096 Jan 16 16:53 containerrequests drwxr-xr-x 3 root root 4096 Jan 16 16:53 containerresourceratios drwxr-xr-x 3 root root 4096 Jan 16 16:53 containerresources drwxr-xr-x 3 root root 4096 Jan 16 16:53 disallowanonymous drwxr-xr-x 3 root root 4096 Jan 16 16:53 disallowedtags drwxr-xr-x 3 root root 4096 Jan 16 16:53 externalip drwxr-xr-x 3 root root 4096 Jan 16 16:53 httpsonly drwxr-xr-x 3 root root 4096 Jan 16 16:53 imagedigests -rw-r--r-- 1 root root 579 Jan 16 16:53 kustomization.yaml drwxr-xr-x 3 root root 4096 Jan 16 16:53 noupdateserviceaccount drwxr-xr-x 3 root root 4096 Jan 16 16:53 poddisruptionbudget drwxr-xr-x 3 root root 4096 Jan 16 16:53 replicalimits drwxr-xr-x 3 root root 4096 Jan 16 16:53 requiredannotations drwxr-xr-x 3 root root 4096 Jan 16 16:53 requiredlabels # 적용할 정책 drwxr-xr-x 3 root root 4096 Jan 16 16:53 requiredprobes drwxr-xr-x 3 root root 4096 Jan 16 16:53 storageclass drwxr-xr-x 3 root root 4096 Jan 16 16:53 uniqueingresshost drwxr-xr-x 3 root root 4096 Jan 16 16:53 uniqueserviceselector
(2) template 적용
requiredlabels 폴더에 template.yaml 하단부에 rego 언어로 작성된 내용을 확인 할 수 있습니다.
해당 yaml 을 적용합니다.
kubectl apply -f ~/requiredlabels/template.yaml
root@playcekube-test-deploy:/tmp/Gatekeeper-library/library/general/requiredlabels# cat template.yaml apiVersion: templates.Gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8srequiredlabels annotations: metadata.Gatekeeper.sh/title: "Required Labels" metadata.Gatekeeper.sh/version: 1.0.0 description: >- Requires resources to contain specified labels, with values matching provided regular expressions. spec: crd: spec: names: kind: K8sRequiredLabels validation: openAPIV3Schema: type: object properties: message: type: string labels: type: array description: >- A list of labels and values the object must specify. items: type: object properties: key: type: string description: >- The required label. allowedRegex: type: string description: >- If specified, a regular expression the annotation's value must match. The value must contain at least one match for the regular expression. targets: - target: admission.k8s.Gatekeeper.sh rego: | package k8srequiredlabels get_message(parameters, _default) = msg { not parameters.message msg := _default } get_message(parameters, _default) = msg { msg := parameters.message } violation[{"msg": msg, "details": {"missing_labels": missing}}] { provided := {label | input.review.object.metadata.labels[label]} required := {label | label := input.parameters.labels[_].key} missing := required - provided count(missing) > 0 def_msg := sprintf("you must provide labels: %v", [missing]) msg := get_message(input.parameters, def_msg) } violation[{"msg": msg}] { value := input.review.object.metadata.labels[key] expected := input.parameters.labels[_] expected.key == key # do not match if allowedRegex is not defined, or is an empty string expected.allowedRegex != "" not re_match(expected.allowedRegex, value) def_msg := sprintf("Label <%v: %v> does not satisfy allowed regex: %v", [key, value, expected.allowedRegex]) msg := get_message(input.parameters, def_msg) }
(3) constraint 적용
네임스페이스에 ‘owner’ label이 적용되어 있어야 한다는 제약을 확인합니다.
# cat ~/requiredlabels/samples/all-must-have-owner/constraint.yaml apiVersion: constraints.Gatekeeper.sh/v1beta1 kind: K8sRequiredLabels metadata: name: all-must-have-owner spec: match: kinds: - apiGroups: [""] kinds: ["Namespace"] parameters: message: "All namespaces must have an `owner` label that points to your company username" labels: - key: owner allowedRegex: "^[a-zA-Z]+.agilebank.demo$"
제약 조건을 적용을 위해 constraint.yaml 을 적용합니다.
kubectl apply -f ~/requiredlabels/samples/all-must-have-owner/constraint.yaml
(4) 네임스페이스를 생성하여 제약이 정상적으로 동작하는지 확인합니다.
명령어로 네임스페이스 생성 시 Gatekeeper에서 denied 되었다는 메시지를 확인 할 수 있습니다.
# kubectl create ns test-ns Error from server (Forbidden): admission webhook "validation.Gatekeeper.sh" denied the request: [all-must-have-owner] All namespaces must have an `owner` label that points to your company username
lable 설정 없는 yaml 파일로 네임스페이스 생성 시 denied 된 메시지를 확인 할 수 있습니다.
# cat example_disallowed.yaml apiVersion: v1 kind: Namespace metadata: name: disallowed-namespace
# kubectl apply -f example_disallowed.yaml Error from server (Forbidden): error when creating "example_disallowed.yaml": admission webhook "validation.Gatekeeper.sh" denied the request: [all-must-have-owner] All namespaces must have an `owner` label that points to your company username
lable 설정 추가하여 네임스페이스 생성을 확인합니다.
# cat example_allowed.yaml apiVersion: v1 kind: Namespace metadata: name: allowed-namespace labels: owner: user.agilebank.demo
# kubectl apply -f example_allowed.yaml namespace/allowed-namespace created # kubectl get ns NAME STATUS AGE allowed-namespace Active 16s ...
3. Gatekeeper 기술자료
https://open-policy-agent.github.io/gatekeeper/website/docs/
https://github.com/open-policy-agent/gatekeeper-library