4. Gatekeeper
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
4. Gatekeeper 릴리즈 노트
https://github.com/open-policy-agent/gatekeeper/releases