평결로부터 정책 만들기

평결로부터 정책 만들기 (Creating Policies from Verdicts)

Policy Audit Mode를 사용해 평결(verdict)을 관찰하고, 그로부터 네트워크 정책을 만드는 과정을 설명하는 문서예요. 감사 모드 활성화, 트래픽 관찰, 정책 생성, 감사 모드 비활성화의 전체 흐름을 다뤄요.

출처: Creating Policies from Verdicts

본문

Policy Audit Mode는 모든 트래픽을 허용하면서 네트워크 정책에 의해 버려졌을 연결을 모두 기록하도록 Cilium을 구성해요. Policy Audit Mode는 --policy-audit-mode=true로 전체 데몬에 대해 구성하거나 개별 Cilium Endpoint에 대해 구성할 수 있어요. Policy Audit Mode가 활성화되면 어떤 네트워크 정책도 시행되지 않으므로 이 설정은 프로덕션 배포에 권장되지 않아요. Policy Audit Mode는 네트워크 레이어 3과 4에서 구현된 네트워크 정책의 감사를 지원해요. 이 가이드는 Policy Audit Mode를 사용해 정책을 만드는 과정을 안내해요.

Cilium 설치 (Setup Cilium)

아직 Cilium을 설치하지 않았다면, Cilium Quick Installation 가이드에 따라 Kubernetes 클러스터를 빠르게 부트스트랩하고 Cilium을 설치하는 방법을 따라 해 보세요. 고민된다면 minikube 방식을 고르면 5분 안에 준비를 마칠 수 있어요.

데모 애플리케이션 배포 (Deploy the Demo Application)

Cilium이 배포되고 kube-dns가 올바르게 동작하면 데모 애플리케이션을 배포할 수 있어요. Star Wars에서 영감을 받은 이 예제에는 deathstar, tiefighter, xwing 세 개의 마이크로서비스 애플리케이션이 있어요. deathstar는 80 포트에서 HTTP 웹서비스를 실행하며, 죽음의 별로 향하는 요청을 두 개의 파드 레플리카에 부하 분산하는 Kubernetes Service로 노출돼요. deathstar 서비스는 은하 제국의 우주선에 착륙 포트를 요청할 수 있도록 착륙(landing) 서비스를 제공해요. tiefighter 파드는 일반적인 제국 우주선의 착륙 요청 클라이언트 서비스를 나타내고, xwing은 반란 연합 우주선의 유사한 서비스를 나타내요. 이들은 deathstar 착륙 서비스에 대한 접근 제어를 위한 서로 다른 보안 정책을 테스트할 수 있도록 존재해요.

http-sw-app.yaml 파일에는 세 서비스 각각에 대한 Kubernetes Deployment가 들어 있어요. 각 배포는 Kubernetes 라벨(org=empire, class=deathstar), (org=empire, class=tiefighter), (org=alliance, class=xwing)로 식별돼요. 또한 org=empire, class=deathstar 라벨을 가진 모든 파드로 트래픽을 부하 분산하는 deathstar-service도 포함돼요.

$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/minikube/http-sw-app.yaml
service/deathstar created
deployment.apps/deathstar created
pod/tiefighter created
pod/xwing created

Kubernetes는 파드와 서비스를 백그라운드에서 배포해요. kubectl get pods,svc를 실행하면 작업 진행 상황을 알 수 있어요. 각 파드는 Running 상태에 도달할 때까지 여러 상태를 거치며, 그 시점에 파드가 준비된 거예요.

$ kubectl get pods,svc
NAME                             READY   STATUS    RESTARTS   AGE
pod/deathstar-6fb5694d48-5hmds   1/1     Running   0          107s
pod/deathstar-6fb5694d48-fhf65   1/1     Running   0          107s
pod/tiefighter                   1/1     Running   0          107s
pod/xwing                        1/1     Running   0          107s

NAME                 TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/deathstar    ClusterIP   10.96.110.8   <none>        80/TCP    107s
service/kubernetes   ClusterIP   10.96.0.1     <none>        443/TCP   3m53s

각 파드는 로컬 cilium 에이전트에서 Cilium의 Endpoint로 나타나요. Cilium 파드 안의 cilium-dbg 도구를 호출해 목록을 확인할 수 있어요(단일 노드 설치에서는 kubectl -n kube-system exec ds/cilium -- cilium-dbg endpoint list가 모두 나열하지만, 다중 노드 설치에서는 같은 노드에서 실행 중인 것만 나열돼요).

$ kubectl -n kube-system get pods -l k8s-app=cilium
NAME           READY   STATUS    RESTARTS   AGE
cilium-5ngzd   1/1     Running   0          3m19s

$ kubectl -n kube-system exec cilium-5ngzd -- cilium-dbg endpoint list
ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                       IPv6   IPv4         STATUS
           ENFORCEMENT        ENFORCEMENT
232        Disabled           Disabled          16530      k8s:class=deathstar                                      10.0.0.147   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=empire
726        Disabled           Disabled          1          reserved:host                                                         ready
883        Disabled           Disabled          4          reserved:health                                          10.0.0.244   ready
1634       Disabled           Disabled          51373      k8s:io.cilium.k8s.policy.cluster=default                 10.0.0.118   ready
                                                           k8s:io.cilium.k8s.policy.serviceaccount=coredns
                                                           k8s:io.kubernetes.pod.namespace=kube-system
                                                           k8s:k8s-app=kube-dns
1673       Disabled           Disabled          31028      k8s:class=tiefighter                                     10.0.0.112   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=empire
2811       Disabled           Disabled          51373      k8s:io.cilium.k8s.policy.cluster=default                 10.0.0.47    ready
                                                           k8s:io.cilium.k8s.policy.serviceaccount=coredns
                                                           k8s:io.kubernetes.pod.namespace=kube-system
                                                           k8s:k8s-app=kube-dns
2843       Disabled           Disabled          16530      k8s:class=deathstar                                      10.0.0.89    ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=empire
3184       Disabled           Disabled          22654      k8s:class=xwing                                          10.0.0.30    ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default
                                                           k8s:io.kubernetes.pod.namespace=default
                                                           k8s:org=alliance

아직 어떤 파드도 선택하는 네트워크 정책이 가져오지지 않았기 때문에, 이 모든 파드에서 ingress와 egress 정책 시행이 모두 비활성화 상태예요.

deathstar Deployment 축소 (Scale down the deathstar Deployment)

이 가이드에서는 다음 단계를 단순화하기 위해 deathstar Deployment를 축소해요.

$ kubectl scale --replicas=1 deployment deathstar
deployment.apps/deathstar scaled

Policy Audit Mode 활성화 (전체 데몬) (Enable Policy Audit Mode (Entire Daemon))

이 Daemonset이 관리하는 모든 엔드포인트에 대한 정책 감사 메시지를 관찰하려면 Cilium ConfigMap을 수정하고 모든 데몬을 재시작하세요.

$ kubectl patch -n $CILIUM_NAMESPACE configmap cilium-config --type merge --patch '{"data":{"policy-audit-mode":"true"}}'
configmap/cilium-config patched
$ kubectl -n $CILIUM_NAMESPACE rollout restart ds/cilium
daemonset.apps/cilium restarted
$ kubectl -n $CILIUM_NAMESPACE rollout status ds/cilium
Waiting for daemon set "cilium" rollout to finish: 0 of 1 updated pods are available...
daemon set "cilium" successfully rolled out

helm install로 Cilium을 설치했다면 helm upgrade를 사용해 Policy Audit Mode를 활성화할 수 있어요.

helm upgrade cilium cilium/cilium --version 1.20.2 \
   --namespace $CILIUM_NAMESPACE \
   --reuse-values \
   --set policyAuditMode=true
helm upgrade cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
   --namespace $CILIUM_NAMESPACE \
   --reuse-values \
   --set policyAuditMode=true

Policy Audit Mode 활성화 (특정 엔드포인트) (Enable Policy Audit Mode (Specific Endpoint))

Cilium은 특정 엔드포인트에 대해 Policy Audit Mode를 활성화할 수 있어요. 전체 데몬에 대해 활성화하는 것이 너무 광범위할 때 유용할 수 있어요. 엔드포인트별로 활성화하면 같은 데몬이 관리하는 다른 엔드포인트가 영향을 받지 않도록 보장해요.

이 접근 방식은 일시적으로 사용하기 위한 거예요. Cilium 파드를 재시작하면 Policy Audit Mode가 데몬 구성에 맞춰 재설정돼요.

Policy Audit Mode는 엔드포인트의 Kubernetes 노드에서 cilium-dbg 도구를 통해 엔드포인트 구성을 수정해 특정 엔드포인트에 대해 활성화돼요. 단계는 다음과 같아요.

  • Policy Audit Mode를 활성화할 엔드포인트 ID를 결정하세요.
  • 엔드포인트에 해당하는 같은 Kubernetes 노드에서 실행 중인 Cilium 파드를 식별하세요.
  • 위 Cilium 파드를 사용해 PolicyAuditMode=Enabled를 설정해 엔드포인트 구성을 수정하세요.

다음 셸 명령이 이 단계를 수행해요.

$ PODNAME=$(kubectl get pods -l app.kubernetes.io/name=deathstar -o jsonpath='{.items[*].metadata.name}')
$ NODENAME=$(kubectl get pod -o jsonpath="{.items[?(@.metadata.name=='$PODNAME')].spec.nodeName}")
$ ENDPOINT=$(kubectl get cep -o jsonpath="{.items[?(@.metadata.name=='$PODNAME')].status.id}")
$ CILIUM_POD=$(kubectl -n "$CILIUM_NAMESPACE" get pod --all-namespaces --field-selector spec.nodeName="$NODENAME" -lk8s-app=cilium -o jsonpath='{.items[*].metadata.name}')
$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    cilium-dbg endpoint config "$ENDPOINT" PolicyAuditMode=Enabled
 Endpoint 232 configuration updated successfully

이 엔드포인트에 Policy Audit Mode가 활성화됐는지 확인할 수 있어요.

$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    cilium-dbg endpoint get "$ENDPOINT" -o jsonpath='{[*].spec.options.PolicyAuditMode}'
Enabled

정책 평결 관찰 (Observe policy verdicts)

이 예제에서 우리는 deathstar에 대한 보안 정책을 적용하는 임무를 맡았어요. 먼저 Cilium 파드에서 Hubble CLI를 사용해 정책 평결 알림을 모니터링해야 해요. deathstar로 향하는 인바운드 트래픽을 모니터링해 그 트래픽을 식별하고, 네트워크 정책을 확장해 그 트래픽을 허용할지 결정할 거예요.

default-deny 정책을 적용하세요.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "empire-default-deny"
spec:
  description: "Default-deny ingress policy for the empire"
  endpointSelector:
    matchLabels:
      org: empire
  ingress:
  - {}

CiliumNetworkPolicy는 endpointSelector를 사용해 파드 라벨을 일치시켜 정책이 적용되는 출처와 목적지를 식별해요. 위 정책은 org=empire 라벨의 파드에 보내지는 트래픽을 거부해요. 위에서 활성화한 Policy Audit Mode(전체 데몬 또는 deathstar 엔드포인트만)로 인해 트래픽은 실제로 거부되지 않고 대신 정책 평결 알림을 발생시켜요.

이 정책을 적용하려면 다음을 실행하세요.

$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/minikube/sw_deny_policy.yaml
ciliumnetworkpolicy.cilium.io/empire-default-deny created

위 정책으로 org=empire 라벨의 파드에 대한 ingress에 default-deny 태세를 활성화하고, 그 파드에 대한 정책 평결 알림을 활성화해요. egress에도 같은 원리가 적용돼요.

이제 tiefighter에서 deathstar로 트래픽을 보내볼게요.

$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed

Cilium 파드에서 정책 평결을 확인할 수 있어요.

$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    hubble observe flows -t policy-verdict --last 1
Feb  7 12:53:39.168: default/tiefighter:54134 (ID:31028) -> default/deathstar-6fb5694d48-5hmds:80 (ID:16530) policy-verdict:none AUDITED (TCP Flags: SYN)

위 예제에서 deathstar-6fb5694d48-5hmds 파드가 정책과 일치하지 않는(policy-verdict:none AUDITED) tiefighter 파드로부터 트래픽을 받은 것을 볼 수 있어요.

네트워크 정책 만들기 (Create the Network Policy)

흐름에 대한 더 자세한 정보는 다음으로 얻을 수 있어요.

$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    hubble observe flows -t policy-verdict -o json --last 1

위 정보를 바탕으로 출처와 목적지 파드의 라벨, 트래픽 방향, 목적지 포트를 이제 알게 됐어요. 이 경우 출처(즉, tiefighter 파드)가 제국 항공기임을(org=empire 라벨) 분명히 볼 수 있어요. 따라서 이 트래픽이 deathstar에 도착할 것으로 예상한다고 판단되면, 트래픽과 일치하는 정책을 구성할 수 있어요.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "L3-L4 policy to restrict deathstar access to empire ships only"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP

이 L3/L4 정책을 적용하려면 다음을 실행하세요.

$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/minikube/sw_l3_l4_policy.yaml
ciliumnetworkpolicy.cilium.io/rule1 created

이제 착륙 요청을 다시 실행하면,

$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed

이전에 정책에 의해 버려지도록 감사된 트래픽이 허용되는 것으로 보고되는 것을 관찰할 수 있어요.

$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    hubble observe flows -t policy-verdict --last 1
...
Feb  7 13:06:45.130: default/tiefighter:59824 (ID:31028) -> default/deathstar-6fb5694d48-5hmds:80 (ID:16530) policy-verdict:L3-L4 ALLOWED (TCP Flags: SYN)

이제 정책 평결이 트래픽이 허용될 것이라고 말해요: policy-verdict:L3-L4 ALLOWED. 성공이에요!

Policy Audit Mode 비활성화 (전체 데몬) (Disable Policy Audit Mode (Entire Daemon))

이 단계는 네트워크 정책이 모든 예상 트래픽을 허용하는지 확인하기 위해 클러스터의 각 연결에 대해 반복되어야 해요. 정책을 배포한 후 마지막 단계는 Policy Audit Mode를 다시 비활성화하는 거예요.

$ kubectl patch -n $CILIUM_NAMESPACE configmap cilium-config --type merge --patch '{"data":{"policy-audit-mode":"false"}}'
configmap/cilium-config patched
$ kubectl -n $CILIUM_NAMESPACE rollout restart ds/cilium
daemonset.apps/cilium restarted
$ kubectl -n $CILIUM_NAMESPACE rollout status ds/cilium
Waiting for daemon set "cilium" rollout to finish: 0 of 1 updated pods are available...
daemon set "cilium" successfully rolled out
helm upgrade cilium cilium/cilium --version 1.20.2 \
   --namespace $CILIUM_NAMESPACE \
   --reuse-values \
   --set policyAuditMode=false
helm upgrade cilium oci://quay.io/cilium/charts/cilium 1.20.2 \
   --namespace $CILIUM_NAMESPACE \
   --reuse-values \
   --set policyAuditMode=false

Policy Audit Mode 비활성화 (특정 엔드포인트) (Disable Policy Audit Mode (Specific Endpoint))

이 단계는 Policy Audit Mode 활성화와 거의 동일해요.

$ PODNAME=$(kubectl get pods -l app.kubernetes.io/name=deathstar -o jsonpath='{.items[*].metadata.name}')
$ NODENAME=$(kubectl get pod -o jsonpath="{.items[?(@.metadata.name=='$PODNAME')].spec.nodeName}")
$ ENDPOINT=$(kubectl get cep -o jsonpath="{.items[?(@.metadata.name=='$PODNAME')].status.id}")
$ CILIUM_POD=$(kubectl -n "$CILIUM_NAMESPACE" get pod --all-namespaces --field-selector spec.nodeName="$NODENAME" -lk8s-app=cilium -o jsonpath='{.items[*].metadata.name}')
$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    cilium-dbg endpoint config "$ENDPOINT" PolicyAuditMode=Disabled
 Endpoint 232 configuration updated successfully

또는 Cilium 파드를 재시작하면 엔드포인트 Policy Audit Mode가 데몬셋 구성에 맞춰 설정돼요.

Policy Audit Mode가 비활성화됐는지 확인 (Verify Policy Audit Mode is Disabled)

$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    cilium-dbg endpoint get "$ENDPOINT" -o jsonpath='{[*].spec.options.PolicyAuditMode}'
Disabled

이제 착륙 요청을 다시 실행하면 org=empire 라벨의 tiefighter 파드만 성공해야 해요.

$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed

트래픽이 정책에 의해 허용됐는지 관찰할 수 있어요.

$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    hubble observe flows -t policy-verdict --from-pod tiefighter --last 1
Feb  7 13:34:26.112: default/tiefighter:37314 (ID:31028) -> default/deathstar-6fb5694d48-5hmds:80 (ID:16530) policy-verdict:L3-L4 ALLOWED (TCP Flags: SYN)

예상대로 동작해요. 이제 xwing 파드의 같은 요청은 실패해야 해요.

$ kubectl exec xwing -- curl --connect-timeout 3 -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
command terminated with exit code 28

이 curl 요청은 3초 후 타임아웃돼야 해요. 정책 평결을 다음과 같이 관찰할 수 있어요.

$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    hubble observe flows -t policy-verdict --from-pod xwing --last 1
Feb  7 13:43:46.791: default/xwing:54842 (ID:22654) <> default/deathstar-6fb5694d48-5hmds:80 (ID:16530) policy-verdict:none DENIED (TCP Flags: SYN)

튜토리얼을 즐기셨기를 바라요. 더 설정을 가지고 놀아 보시고, Identity-Aware and HTTP-Aware Policy Enforcement 가이드를 따라가 보시고, 질문이 있으면 Cilium Slack으로 연락해 주세요!

정리 (Clean-up)

$ kubectl delete -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/minikube/http-sw-app.yaml
$ kubectl delete cnp empire-default-deny rule1

더 알아보기 (Learn more)