Kubernetes 이야기

Kyverno ( Kubernetes Policy) 본문

Kubernetes/보안

Kyverno ( Kubernetes Policy)

kmaster 2022. 2. 20. 20:22
반응형

Kyverno

Kubernetes에서는 PodSecurityPolicy라는 파드 명세의 보안 관련 측면을 제어할 수 있도록 하는 내장 admission controller이다. 하지만 Kubernetes v1.21부터 더이상 사용되지 않으며, v1.25에서 제거되는 리소스이다.

(관련내용 : https://kubernetes.io/docs/concepts/policy/pod-security-policy/)

 

PodSecurityPolicy와 유사한 기능을 하는 여러 외부 승인 컨트롤러가 있습니다. 이 중 대표적으로 OPA/Gatekeeper 가 있는데 이번에는 OPA와 더불어 많이 사용되고 있는 Kyverno에 대해 알아보도록 하자.

( Kubernetes 1.23 에서는 PodSecurityPolicy대신 Pod Security Adminssion 라는 기능이 새로 추가되었다. 다음에는 PSA 기능에 대해 알아보도록 하겠다.)

 

Kyverno github를 보면 아래와 같이 소개하고 있다.

 

Kyverno는 Kubernetes용으로 설계된 정책 엔진입니다.
 
승인 제어 및 백그라운드 스캔을 사용하여 구성을 검증, 변경 및 생성할 수 있습니다.
 
Kyverno 정책은 Kubernetes 리소스이며 새로운 언어를 배울 필요가 없습니다.
 
Kyverno는 kubectl, kustomize, Git과 같이 이미 사용하고 있는 도구와 잘 작동하도록 설계되었습니다.

 

소개에서 보듯이 OPA와의 대표적인 차이는 새로운 언어(rego language) 를 배울 필요없이 Kubernetes resource 를 만들어서 배포하면 되기 때문에 배우기 매우 쉽다는 장점이 있다.

 

참고로 OPA에서 클러스터에 생성/배포되는 개체에 특정 레이블을 있어야 하는 조건은 아래와 같이 작성한다.

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
 name: k8srequiredlabels
spec:
 crd:
   spec:
     names:
       kind: K8sRequiredLabels
     validation:
       # Schema for the `parameters` field
       openAPIV3Schema:
         properties:
           labels:
             type: array
             items: string
 targets:
   - target: admission.k8s.gatekeeper.sh
     rego: |
       package k8srequiredlabels
       violation[{"msg": msg, "details": {"missing_labels": missing}}] {
         provided := {label | input.review.object.metadata.labels[label]}
         required := {label | label := input.parameters.labels[_]}
         missing := required - provided
         count(missing) > 0
         msg := sprintf("\n\nDENIED. \nReason: Our org policy mandates the following labels: \nYou must provide these labels: %v", [missing])
       }

 

ConstraintTemplate Object가 배포되면 이제 Constraints를 생성하여 정책을 시행할 수 있다. 다음은 "zone", "stage", "status" 레이블이 필요한 제약 조건이다.

 

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
 name: label-check
spec:
 match:
   kinds:
     - apiGroups: [""]
       kinds: ["Namespace", "Pod"]
   excludedNamespaces:
   - kube-system
   - kube-public
   - kube-node-lease
   - gatekeeper-system
 parameters:
   labels:
     - "zone"
     - "stage"
     - "status"

OPA는 의심할 여지 없이 Kyverno보다 더 유연하지만 Kubernetes 클러스터를 관리하는 많은 팀에게 복잡성은 유지 관리 및 관리 문제의 너무 많다.

 

Kyverno의 특장점

 

장점

  • yaml 로 정책을 작성하기 때문에 단순하고 배우기 쉽다.
  • 100여가지의 다양한 정책이 이미 만들어져 제공되기 때문에 적용하기 쉽다.

단점

  • OPA와 같이 프로그래밍 방식이 아니라서 복잡한 정책이 불가능할 수 있다.
  • 아직 오래된 프로젝트가 아니라서 OPA보다는 레퍼런스가 많지 않다.

 

동작원리

출처 : https://kyverno.io/docs/introduction/

 

Kyverno 정책은 리소스 종류, 이름 및 레이블 선택기를 사용하여 리소스를 일치시킬 수 있다. 유효성 검사 정책은 패턴 일치 및 조건부 ( if-then-else ) 처리를 지원하는 오버레이 스타일 구문도 사용할 수 있다.

정책 시행은 Kubernetes event를 사용하여 캡처된다. Kyverno는 또한 기존 리소스에 대한 정책 위반을 보고한다.

 

우선 Kyverno 를 설치해보자.

 

설치

 

아래와 같이 최신 버전을 설치해보자

# kubectl create -f https://raw.githubusercontent.com/kyverno/kyverno/main/config/install.yaml
namespace/kyverno created
customresourcedefinition.apiextensions.k8s.io/clusterpolicies.kyverno.io created
customresourcedefinition.apiextensions.k8s.io/clusterpolicyreports.wgpolicyk8s.io created
customresourcedefinition.apiextensions.k8s.io/clusterreportchangerequests.kyverno.io created
customresourcedefinition.apiextensions.k8s.io/generaterequests.kyverno.io created
customresourcedefinition.apiextensions.k8s.io/policies.kyverno.io created
customresourcedefinition.apiextensions.k8s.io/policyreports.wgpolicyk8s.io created
customresourcedefinition.apiextensions.k8s.io/reportchangerequests.kyverno.io created
serviceaccount/kyverno-service-account created
role.rbac.authorization.k8s.io/kyverno:leaderelection created
clusterrole.rbac.authorization.k8s.io/kyverno:admin-generaterequest created
clusterrole.rbac.authorization.k8s.io/kyverno:admin-policies created
clusterrole.rbac.authorization.k8s.io/kyverno:admin-policyreport created
clusterrole.rbac.authorization.k8s.io/kyverno:admin-reportchangerequest created
clusterrole.rbac.authorization.k8s.io/kyverno:events created
clusterrole.rbac.authorization.k8s.io/kyverno:generate created
clusterrole.rbac.authorization.k8s.io/kyverno:policies created
clusterrole.rbac.authorization.k8s.io/kyverno:userinfo created
clusterrole.rbac.authorization.k8s.io/kyverno:view created
clusterrole.rbac.authorization.k8s.io/kyverno:webhook created
rolebinding.rbac.authorization.k8s.io/kyverno:leaderelection created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:events created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:generate created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:policies created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:userinfo created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:view created
clusterrolebinding.rbac.authorization.k8s.io/kyverno:webhook created
configmap/kyverno created
configmap/kyverno-metrics created
service/kyverno-svc created
service/kyverno-svc-metrics created
deployment.apps/kyverno created

 

설치 상태를 확인해 보자.

# kubectl get all -n kyverno
NAME                           READY   STATUS    RESTARTS   AGE
pod/kyverno-59fb4c9bd9-b2xbn   1/1     Running   0          52s

NAME                          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/kyverno-svc           ClusterIP   10.108.184.221   <none>        443/TCP    52s
service/kyverno-svc-metrics   ClusterIP   10.106.154.242   <none>        8000/TCP   52s

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kyverno   1/1     1            1           52s

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/kyverno-59fb4c9bd9   1         1         1       52s

 

정책 및 규칙

Kyverno 정책과 규칙이 어떻게 작동하는지 알아보자.

https://kyverno.io/docs/kyverno-policies/

 

정책은 클러스터 레벨의 정책과 네임스페이스에 해돵되는 리소스로 정의할 수 있다.

Kyverno에서는 약 100여가지의 정책을 가지고 있다 ( 참고 : https://kyverno.io/policies/ )

 

Example 1

Kuberneres에서는 'exec'명령어를 통해 실행중인 Pod의 컨테이너에서 Shell access권한을 얻거나 다른 명령을 실행하는 데 사용할 수 있다. 이는 문제 해결 목적으로 유용할 수 있지만 공격 벡터를 나타낼 수 있으므로 권장하지 않는다. 다음 예제는 test 라는 네임스페이스의 POD들은 exec 명령을 차단하게 하는 예제이다.

 

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: deny-exec-by-namespace-name
  annotations:
    policies.kyverno.io/title: Block Pod Exec by Namespace Name
    policies.kyverno.io/category: Sample
    policies.kyverno.io/minversion: 1.4.2
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/description: >-
      The `exec` command may be used to gain shell access, or run other commands, in a Pod's container. While this can
      be useful for troubleshooting purposes, it could represent an attack vector and is discouraged.
      This policy blocks Pod exec commands to Pods in a Namespace called `pci`.      
spec:
  validationFailureAction: enforce # 감사 모드에서는 리소스 요청이 허용되고 정책 위반이 기록됩니다. 달리 지정하지 않으면 감사 모드가 기본 설정입니다.
  background: false # true이면 기존 resource 를 감사(audit) 한다.
  rules:
  - name: deny-exec-ns-pci
    match:
      resources:
        kinds:
        - PodExecOptions
    preconditions:
      all:
      - key: "{{ request.operation }}"
        operator: Equals
        value: CONNECT
    validate:
      message: Pods in this namespace may not be exec'd into.
      deny:
        conditions:
          any:
          - key: "{{ request.namespace }}"
            operator: Equals
            value: test

 

test 네임스페이스에 pod 들을 먼저 조회하면 아래와 같다.

# kubectl get po -n test
NAME                                READY   STATUS    RESTARTS   AGE
hello-v2-6f68bcd76b-6jlrt           1/1     Running   0          3h6m
hello-v2-6f68bcd76b-6m8pq           1/1     Running   0          3h6m
hello-v2-6f68bcd76b-x5ln8           1/1     Running   0          3h11m
locust-master-9db68fffc-vj4m7       1/1     Running   0          47h
locust-worker-64498dfbc8-wcnbn      1/1     Running   0          47h
rollingupdate-web-889f46689-4dpvf   1/1     Running   0          24h
rollingupdate-web-889f46689-scttz   1/1     Running   0          24h
rollingupdate-web-889f46689-tclwp   1/1     Running   0          24h

 

ClusterPolicy 정책을 반영하기 전에 hello-v2-6f68bcd76b-6jlrt pod로 exec수행을 해보자.

kubectl exec -it -n test hello-v2-6f68bcd76b-6jlrt -- /bin/echo '123'
123

 

이제 ClusterPolicy를 적용한 후 exec수행을 해보자.

# kubectl exec -it -n test hello-v2-6f68bcd76b-6jlrt -- /bin/echo '123'
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:

resource PodExecOptions/test/ was blocked due to the following policies

deny-exec-by-namespace-name:
  deny-exec-ns-test: Pods in this namespace may not be exec'd into.

 

Example 2

애플리케이션은 가용성과 확장성을 위해 동일한 Pod의 여러 복제본을 포함할 수 있지만 Kubernetes는 기본적으로 가용성에 대한 솔루션을 제공하지 않는다. 이 예제는 '앱' 레이블이 아직 없는 경우 이를 포함하는 배포에서 podAntiAffinity를 구성하는 예제이다. 즉 Deployment의 replica가 3개이고 Node가 3개이면 Node당 1개씩 Pod가 스케쥴링 되도록 한다.

 

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: insert-pod-antiaffinity
  namespace: test
  annotations:
    policies.kyverno.io/title: Add Pod Anti-Affinity
    policies.kyverno.io/category: Sample
    policies.kyverno.io/subject: Deployment, Pod
    policies.kyverno.io/description: >-
      Applications may involve multiple replicas of the same Pod for availability as well as scale
      purposes, yet Kubernetes does not by default provide a solution for availability. This policy
      sets a Pod anti-affinity configuration on Deployments which contain an `app` label if it is
      not already present.      
spec:
  rules:
    - name: insert-pod-antiaffinity
      match:
        resources:
          kinds:
            - Deployment
      preconditions:
        # This precondition selects Pods with the label `
        all:
        - key: "{{request.object.spec.template.metadata.labels.app}}"
          operator: NotEquals
          value: ""
      # Mutates the Deployment resource to add fields.
      mutate:
        patchStrategicMerge:
          spec:
            template:
              spec:
                # Add the `affinity`if not already specified.
                +(affinity):
                  +(podAntiAffinity):
                    +(preferredDuringSchedulingIgnoredDuringExecution):
                      - weight: 1
                        podAffinityTerm:
                          topologyKey: "kubernetes.io/hostname"
                          labelSelector:
                            matchExpressions:
                            - key: app
                              operator: In
                              values:
                              - "{{request.object.spec.template.metadata.labels.app}}"

 

배포한 deployment를 edit로 보면

kubectl edit deployment -n test hello-v2
  creationTimestamp: "2022-02-20T10:36:40Z"
  generation: 1
  name: hello-v2
  namespace: test
  resourceVersion: "6811919"
  selfLink: /apis/apps/v1/namespaces/test/deployments/hello-v2
  uid: b52ebcf8-fd29-4018-abf1-47fd875147c3
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: hello-v2
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: hello-v2
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - hello-v2
              topologyKey: kubernetes.io/hostname
            weight: 1
      containers:
...

podAntiAffinity가 자동으로 삽입된 것을 볼 수 있다.

 

실제 실행된 Pod의 상태를 보자.

# kubectl get po -n test -o wide
NAME                             READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
hello-v2-fc7bdc486-r7r75         1/1     Running   0          97s   172.32.183.186   master   <none>           <none>
hello-v2-fc7bdc486-s7lmg         1/1     Running   0          97s   172.32.24.50     node2    <none>           <none>
hello-v2-fc7bdc486-xrc7m         1/1     Running   0          97s   172.32.139.158   node1    <none>           <none>

위와 같이 Pod가 모든 노드에 배포되는 것을 볼 수 있다.

 

이 밖에도 Kyverno 여러가지 정책이 있다. 

 

예)

  • Private Registry 사용 시 매번 secret를 namespace마다 만들지 않고 특정 이미지 주소인 경우 기본 secret를 자동 복사하는 기능
  • Pod 간 통신을 제어하는 ​​데 사용되는 NetworkPolicy를 강제화 하도록 설정하는 정책
  • 기본 Pod securityContext 내에서 'runAsNonRoot', runAsUser', 'runAsGroup' 및 'fsGroup' 필드가 아직 설정되지 않은 경우 설정하도록 Pod를 변경하는 정책
  • ResourceQutoa나 LimitRange의 설정을 할 수 있도록 하는 정책
  • 비정상적인 이미지 크기 ( 예를들어 10GB ) 를 제한하는 정책
  • 특정 레이블이 포함된 모드 서비스 리소스에 대한 업데이트 및 삭제를 제한하는 정책
  • automountServiceAccountToken 비활성화 정책  (default service account )
  • CRI 소켓 마운트 금지
  • empty ingress 허용 금지
  • Helm v2 Tiller 사용 금지
  • NodePort 허용 금지
  • set-user-ID 또는 set-group-ID 파일모드를 통한 권한 상승 금지
  • Privileged container 금지
  • SELinux 허용 안함
  • 사이드카 컨테이너 주입
  • hostPath 볼륨을 특정 디렉토리로 제한
  • Log4j 취약점 보완을 위한 환경변수 자동 추가
  • 노드 taint 보호
  • 루트 파일 시스템 읽기 전용으로 변경함으로써 악성 바이너리가 호스트 시스템에 기록되는 것을 방지
  • 서비스 계정 토큰 자동 마운트 제한
  • 노드 레이블 생성 제한
  • 노드 선택 제한
  • 특정 레이블이 지정되지 않은 Secret만 Pod에서 사용할 수 있도록 제한
  • sysctls 시스템 제어 제한
  • 이미지 서명 확인 ( Cosign 프로젝트를 사용한 이미지 서명 확인 )
반응형
Comments