본문으로 건너뛰기

Kubernetes Admission Webhook

개요​

Casbin K8s-Gatekeeper는 Casbin을 인가(authorization)에 사용하는 Kubernetes 어드미션 웹훅(admission webhook)입니다. 모델과 정책을 선언적으로 정의하여 모든 Kubernetes 리소스에 대한 작업을 허용하거나 거부할 수 있으며, 웹훅에 커스텀 코드가 필요하지 않습니다. Casbin 커뮤니티에서 유지보수합니다: github.com/apache/casbin-k8s-gatekeeper.

기본 예제​

예제: 설정(config)만 사용하여 특정 태그가 포함된 이미지를 사용하는 배포(deployment)를 거부하기:

모델(Model):

[request_definition]
r = obj

[policy_definition]
p = obj,eft

[policy_effect]
e = !some(where (p.eft == deny))

[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
contain(split(accessWithWildcard(${OBJECT}.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)

정책(Policy):

p, "1.14.1",deny

이는 표준 Casbin ACL 언어를 사용하므로, 입문 장들을 읽었다면 쉽게 이해할 수 있을 것입니다.

Casbin K8s-Gatekeeper는 다음과 같은 몇 가지 이점을 제공합니다:

  • 사용이 간편—방대한 코드 대신 ACL 구성을 작성하면 됩니다
  • 플러그인 재시작 없이 라이브(live) 구성 업데이트 지원
  • 유연함—kubectl gatekeeper를 사용하여 임의의 규칙을 모든 Kubernetes 리소스에 적용 가능
  • Kubernetes 어드미션 웹훅 구현 간소화—웹훅 내부 구조를 이해하거나 웹훅 코드를 작성할 필요가 없습니다. 단순히 제약 조건을 정의하고 Casbin ACL을 작성하면 됩니다.
  • 커뮤니티 주도—질문이나 이슈가 있으면 연락 주세요

1.1 Casbin K8s-Gatekeeper 작동 방식​

K8s-Gatekeeper는 Casbin을 사용하여 사용자 지정 접근 제어 규칙을 강제하고, 원치 않는 Kubernetes 리소스 작업을 방지하는 Kubernetes용 어드미션 웹훅입니다.

Casbin은 다양한 인가 모델을 지원하는 효율적인 오픈 소스 접근 제어 라이브러리입니다. 자세한 내용은 개요를 참조하세요.

Kubernetes의 어드미션 웹훅은 어드미션 요청을 수신하고 처리하는 HTTP 콜백(callback)입니다. K8s-Gatekeeper는 어드미션 요청을 승인하거나 거부하는 ValidatingAdmissionWebhook입니다. 어드미션 요청은 Kubernetes 리소스에 대한 작업(예: 배포 생성 또는 삭제)을 설명하는 HTTP 요청입니다. 자세한 내용은 Kubernetes 문서를 참조하세요.

1.2 예제 워크플로​

누군가(kubectl 또는 Kubernetes 클라이언트를 통해) nginx 파드를 포함하는 배포를 생성하면, Kubernetes는 다음과 같은(YAML 형식의) 어드미션 요청을 생성합니다:

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.1
ports:
- containerPort: 80

이 요청은 K8s-Gatekeeper를 포함한 미들웨어 레이어를 통과합니다. K8s-Gatekeeper는 Kubernetes etcd에 저장된 모든 Casbin 인포서(enforcer)(사용자가 kubectl 또는 제공된 Go 클라이언트를 통해 생성 및 관리)를 감지합니다. 각 인포서는 Casbin 모델과 정책을 포함합니다. 어드미션 요청은 각 인포서에 의해 순차적으로 평가되며, 승인되려면 모든 인포서를 통과해야 합니다.

(Casbin 인포서, 모델 또는 정책에 익숙하지 않다면 시작하기를 참조하세요).

예를 들어, 관리자가 'nginx:1.3.1'은 허용하면서 'nginx:1.14.1' 이미지를 차단하려면, 다음 모델과 정책으로 인포서를 생성할 수 있습니다(생성 및 구성 세부 사항은 이후 섹션에서 설명됩니다):

모델(Model):

[request_definition]
r = obj

[policy_definition]
p = obj,eft

[policy_effect]
e = !some(where (p.eft == deny))

[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") == p.obj

정책(Policy):

p, "nginx:1.13.1",allow
p, "nginx:1.14.1",deny

이 모델과 정책으로 인포서를 생성하면 어드미션 요청이 거부되어, Kubernetes가 배포를 생성하는 것을 방지합니다.

2. K8s-gatekeeper 설치​

세 가지 설치 방법이 제공됩니다: External webhook, Internal webhook, 그리고 Helm.

노트

이러한 설치 방법은 평가 목적 전용입니다. 프로덕션 배포 시에는 Chapter 5. Advanced settings를 검토하고 설치 전에 필요한 보안 수정 사항을 적용하세요.

2.1 Internal Webhook​

2.1.1 Step 1: 이미지 빌드​

Internal webhook 배포의 경우, K8s-gatekeeper는 Kubernetes 서비스로 실행됩니다. 이미지를 빌드하세요:

docker build --target webhook -t k8s-gatekeeper .

이렇게 하면 'k8s-gatekeeper:latest'라는 이름의 로컬 이미지가 생성됩니다.

노트

minikube 사용자는 'docker build' 전에 eval $(minikube -p minikube docker-env)를 실행하세요.

2.1.2 Step 2: 서비스 및 리소스 배포​

다음 명령어를 실행하세요:

kubectl apply -f config/rbac.yaml
kubectl apply -f config/webhook_deployment.yaml
kubectl apply -f config/webhook_internal.yaml

kubectl get pods로 배포 상태를 확인하세요.

2.1.3 Step 3: CRD 리소스 설치​

커스텀 리소스 정의(CRD)를 설치하세요:

kubectl apply -f config/auth.casbin.org_casbinmodels.yaml
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml

2.2 External Webhook​

External webhook 배포의 경우, K8s-gatekeeper는 Kubernetes 외부에서 실행됩니다. Kubernetes는 어드미션 웹훅에 HTTPS를 요구합니다. 테스트용 인증서와 개인 키(프로덕션 보안 용도가 아님)를 제공합니다. 커스텀 인증서의 경우 Chapter 5. Advanced settings를 참조하세요.

제공된 인증서는 'webhook.domain.local'에 대해 발급되었습니다. hosts 파일(예: /etc/hosts)을 업데이트하여 'webhook.domain.local'이 K8s-gatekeeper가 실행되는 IP 주소를 가리키도록 하세요.

실행:

go mod tidy
go mod vendor
go run cmd/webhook/main.go
kubectl apply -f config/auth.casbin.org_casbinmodels.yaml
kubectl apply -f config/auth.casbin.org_casbinpolicies.yaml
kubectl apply -f config/webhook_external.yaml

2.3 Helm을 통한 설치​

2.3.1 단계 1: 이미지 빌드​

Chapter 2.1.1 장을 참조하세요.

2.3.2 Helm 설치​

실행: helm install k8sgatekeeper ./k8sgatekeeper

3. K8s-gatekeeper 사용​

3.1 Casbin 모델 및 정책 생성​

kubectl 또는 제공된 Go 클라이언트를 사용하여 모델과 정책을 생성합니다.

3.1.1 kubectl을 통한 생성/업데이트​

K8s-gatekeeper에서는 Casbin 모델이 'CasbinModel' CRD 리소스로 저장됩니다. 정의는 config/auth.casbin.org_casbinmodels.yaml에 있습니다.

example/allowed_repo/model.yaml의 예제를 참조하세요. 중요한 필드:

  • metadata.name: 모델 이름. K8s-gatekeeper가 올바르게 페어링하려면 연결된 CasbinPolicy 객체 이름과 일치해야 합니다.
  • spec.enable: 이 모델과 관련 정책을 비활성화하려면 "false"로 설정하세요.
  • spec.modelText: Casbin 모델 정의를 포함하는 문자열.

Casbin 정책은 'CasbinPolicy' CRD 리소스로 저장되며, config/auth.casbin.org_casbinpolicies.yaml에 정의되어 있습니다.

example/allowed_repo/policy.yaml의 예제를 참조하세요. 중요한 필드:

  • metadata.name: 정책 이름. 연결된 CasbinModel 객체 이름과 일치해야 합니다.
  • spec.policyItem: Casbin 정책 정의를 포함하는 문자열.

CasbinModel 및 CasbinPolicy 파일을 적용하세요:

kubectl apply -f <filename>

K8s-gatekeeper는 5초 이내에 새로운 CasbinModel/CasbinPolicy 쌍을 감지합니다.

3.1.2 Go 클라이언트를 통한 생성/업데이트​

클러스터 노드에 대한 직접적인 셸 접근이 실용적이지 않은 시나리오(예: 자동화된 클라우드 플랫폼 구축 시)를 위해, CasbinModel 및 CasbinPolicy 리소스를 관리할 수 있는 Go 클라이언트를 제공합니다.

Go 클라이언트 라이브러리는 pkg/client에 있습니다.

client.go에서 클라이언트를 생성하기 위해 이 함수를 사용하세요:

func NewK8sGateKeeperClient(externalClient bool) (*K8sGateKeeperClient, error)

externalClient 매개변수는 K8s-gatekeeper가 Kubernetes 클러스터 내부에서 실행되는지 외부에서 실행되는지를 나타냅니다.

model.go에서는 CasbinModel의 생성, 삭제 및 수정을 위한 함수가 제공됩니다. 사용 예제는 model_test.go를 참조하세요.

policy.go에서는 CasbinPolicy의 생성, 삭제 및 수정을 위한 함수가 제공됩니다. 사용 예제는 policy_test.go를 참조하세요.

3.1.2 K8s-gatekeeper 테스트​

example/allowed_repo에서 모델과 정책을 생성한 후 다음으로 테스트하세요:

kubectl apply -f example/allowed_repo/testcase/reject_1.yaml

Kubernetes는 webhook을 거부 이유로 들어 이 요청을 거부해야 합니다. 그러나 example/allowed_repo/testcase/approve_2.yaml을 적용하면 성공해야 합니다.

4. K8s-gatekeeper용 모델 및 정책 작성​

진행하기 전에 Casbin 모델 및 정책 구문을 이해했는지 확인하세요. 그렇지 않다면 먼저 Get Started 섹션을 읽으세요. 이 장에서는 Casbin 모델 및 정책에 대한 친숙함을 가정합니다.

4.1 요청 정의​

K8s-gatekeeper가 요청을 평가할 때 입력은 항상 AdmissionReview Go 객체입니다. enforcer는 다음과 같이 사용됩니다:

ok, err := enforcer.Enforce(admission)

여기서 admission은 Kubernetes 공식 Go API "k8s.io/api/admission/v1"의 AdmissionReview 객체입니다. 구조체 정의는 다음에서 확인할 수 있습니다: https://github.com/kubernetes/api/blob/master/admission/v1/types.go. 추가 문서: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#webhook-request-and-response.

K8s-gatekeeper 모델의 경우 request_definition은 항상 이 형식을 따라야 합니다:

[request_definition]
r = obj

'obj'라는 이름은 [matchers] 섹션에서 일관되게 사용되는 한 변경할 수 있습니다.

4.2 모델 매처​

Casbin의 ABAC 기능을 사용하여 규칙을 작성합니다. 그러나 Casbin의 표현식 평가기는 맵/배열 인덱싱이나 배열 확장을 기본적으로 지원하지 않습니다. K8s-gatekeeper는 이를 해결하기 위해 확장 함수를 제공합니다. 추가 기능이 필요한 경우 issue를 열거나 pull request를 제출해 주세요.

Casbin 함수에 대한 배경 정보는 Function을 참조하세요.

확장 함수:

4.2.1 확장 함수​

4.2.1.1 access​

access 함수는 맵 및 배열 인덱싱을 가능하게 합니다. example/allowed_repo/model.yaml을 참조하세요:

[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image") == p.obj

여기서 access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Image")는 r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Image와 동일하며, r.obj.Request.Object.Object.Spec.Template.Spec.Containers는 슬라이스입니다.

access는 단일 값을 반환하는 매개변수가 없는 함수도 호출할 수 있습니다. example/container_resource_limit/model.yaml을 참조하세요:

[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
parseFloat(access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","cpu","Value")) >= parseFloat(p.cpu) && \
parseFloat(access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","memory","Value")) >= parseFloat(p.memory)

여기서 access(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , 0, "Resources","Limits","cpu","Value")는 r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"].Value()와 같으며, r.obj.Request.Object.Object.Spec.Template.Spec.Containers[0].Resources.Limits는 맵이고 Value()는 단일 값을 반환하는 매개변수 없는 함수입니다.

4.2.1.2 accessWithWildcard​

for 루프 없이 모든 배열 요소에 대해 조건을 확인하려면(예: "모든 요소가 'aaa'로 시작해야 함"), 맵/슬라이스 확장과 함께 accessWithWildcard를 사용하세요.

a.b.c가 배열 [aaa,bbb,ccc,ddd,eee]인 경우, accessWithWildcard(a,"b","c","*")는 슬라이스 [aaa,bbb,ccc,ddd,eee]를 반환합니다. 와일드카드 *는 슬라이스를 확장합니다.

다중 와일드카드가 지원됩니다. 예를 들어, accessWithWildcard(a,"b","c","*","*")는 [a.b.c[0][0], a.b.c[0][1], ..., a.b.c[1][0], a.b.c[1][1], ...]를 반환합니다.

4.2.1.3 가변 길이 인자 함수​

Casbin의 표현식 평가기는 배열을 가변 길이 인자로 자동으로 확장합니다. 이를 배열/슬라이스/맵 확장에 활용하여 몇 가지 함수가 배열/슬라이스를 허용합니다:

  • contain(): 여러 매개변수를 허용하며 마지막 매개변수를 제외한 어떤 매개변수가 마지막 매개변수와 동일한지 여부를 반환합니다.
  • split(a,b,c...,sep,index): [splits(a,sep)[index], splits(b,sep)[index], splits(c,sep)[index], ...]를 반환합니다.
  • len(): 가변 길이 인자의 개수를 반환합니다.
  • matchRegex(a,b,c...,regex): 모든 매개변수(a, b, c, ...)가 정규식과 일치하는지 여부를 반환합니다.

example/disallowed_tag/model.yaml의 예제:

[matchers]
m = r.obj.Request.Namespace == "default" && r.obj.Request.Resource.Resource =="deployments" && \
contain(split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)

accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image")가 ["a:b", "c:d", "e:f", "g:h"]를 반환하면, split 연산은 각 요소를 가변 길이 인자 지원으로 처리하여 각 요소에서 인덱스 1을 선택합니다. 따라서 split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1)는 ["b","d","f","h"]를 반환합니다. 마지막으로, contain(split(accessWithWildcard(r.obj.Request.Object.Object.Spec.Template.Spec.Containers , "*", "Image"),":",1) , p.obj)는 p.obj가 ["b","d","f","h"]에 존재하는지 확인합니다.

4.2.1.2 타입 변환 함수​

  • ParseFloat(): 정수를 부동 소수점으로 변환합니다(비교에는 float 타입이 필요하므로 필수).
  • ToString(): 객체를 문자열로 변환합니다. 객체는 기본 문자열 타입을 가져야 합니다(예: type XXX string).
  • IsNil(): 매개변수가 nil인지 여부를 반환합니다.

5. 고급 설정​

5.1 인증서 구성​

Kubernetes는 웹훅이 HTTPS를 사용하도록 요구합니다. 두 가지 옵션이 가능합니다:

  • 자가 서명된 인증서(이 저장소의 예제에서 사용)
  • 공개적으로 신뢰되는 인증서

5.1.1 자가 서명된 인증서​

자가 서명된 인증서는 공개적으로 인식되지 않는 인증 기관(CA)을 사용합니다. Kubernetes가 이 CA를 신뢰하도록 구성해야 합니다.

저장소 예제는 개인 키와 인증서가 config/certificate/ca.key 및 config/certificate/ca.crt에 저장된 사용자 정의 CA를 사용합니다. 웹훅 인증서 config/certificate/server.crt는 도메인 "webhook.domain.local"(외부 웹훅) 및 "casbin-webhook-svc.default.svc"(내부 웹훅)용으로 이 CA에 의해 발급되었습니다.

CA 정보는 웹훅 구성 파일을 통해 Kubernetes에 전달됩니다. config/webhook_external.yaml과 config/webhook_internal.yaml 모두 base64로 인코딩된 CA 인증서를 포함하는 "CABundle" 필드를 가지고 있습니다.

인증서/도메인을 변경하려면(예: 웹훅을 다른 네임스페이스로 이동하거나 도메인 변경):

  1. 새로운 CA 생성:

    • CA 개인 키 생성:

      openssl genrsa -des3 -out ca.key 2048
    • 비밀번호 보호 제거:

      openssl rsa -in ca.key -out ca.key
  2. 웹훅 서버 개인 키 생성:

    openssl genrsa -des3 -out server.key 2048
    openssl rsa -in server.key -out server.key
  3. CA로 웹훅 인증서에 서명:

    • 시스템의 OpenSSL 구성 파일을 복사합니다(openssl version -a로 위치를 확인하며, 일반적으로 openssl.cnf입니다).

    • 구성 파일을 수정합니다:

      • [req] 섹션에서 다음을 추가합니다: req_extensions = v3_req

      • [v3_req] 섹션에서 다음을 추가합니다: subjectAltName = @alt_names

      • 다음 내용을 덧붙입니다:

        [alt_names]
        DNS.2=<Your desired domain>

        참고: 'casbin-webhook-svc.default.svc'를 실제 서비스 이름으로 변경하세요(수정된 경우).

    • 인증서 요청을 생성합니다:

      openssl req -new -nodes -keyout server.key -out server.csr -config openssl.cnf
    • CA로 인증서에 서명합니다:

      openssl x509 -req -days 3650 -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -extensions v3_req -extensions SAN -extfile openssl.cnf
  4. config/webhook_external.yaml과 config/webhook_internal.yaml의 'CABundle' 필드를 base64로 인코딩된 새 CA 인증서로 업데이트합니다.

  5. Helm 배포의 경우, Helm 차트에 동일한 변경 사항을 적용합니다.

5.1.2 공개적으로 신뢰할 수 있는 인증서​

공개적으로 신뢰할 수 있는 인증서를 사용하는 경우 위의 절차를 생략합니다. config/webhook_external.yaml 및 config/webhook_internal.yaml에서 "CABundle" 필드를 제거하고 도메인을 등록한 도메인으로 설정합니다.

Comments are powered by giscus and GitHub Discussions. They load from giscus.app and GitHub only after you click the button below.