상세 컨텐츠

본문 제목

Cilium Policy Audit Mode로 네트워크 정책 만들기

> Tech

by Ryusstory 2025. 9. 6.

본문

서론 : 기대했던 기능과 실제 차이점

공식문서에 Creating Policies from Verdicts 이라고 되어있어서 제목만보고 예전 SELinux에서 사용하던 audit2allow를 상상했었다.
audit2allow는 거부된 로그를 통해 허용하는 룰을 바로 적용할 수 있도록 생성해주는 도구이다.
audit2allow처럼 정책이 자동으로 생성되진 않지만, 감사 로그를 기반으로 정책을 수동 작성할 수 있어서 충분히 유용하게 활용할 수 있다.

구성 환경 : 3tier 샘플 앱 구성

3tier sample app을 통해 구성했다. 아래 github과 같은 소스로 ./recreate.sh && ./build.sh && ./deploy.sh을 실행한 상태에서 테스트했다.

https://github.com/ryusstory/k8s-3tier-sample-clickcounter/tree/cni/cilium

 

GitHub - ryusstory/k8s-3tier-sample-clickcounter

Contribute to ryusstory/k8s-3tier-sample-clickcounter development by creating an account on GitHub.

github.com

3tier app은 테스트를 위한 샘플 앱이다

https://ryusstory.tistory.com/entry/k8s-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%9A%A9-3tier-sample-app

 

K8s 테스트용 3tier sample

개요cilium 을 공부하면서 테스트 클러스터는 vagrant 와 kind를 사용하면 대부분 테스트가 큰 무리 없이 가능했고, bgp 구성 관련해서도 쉽지는 않지만 containerlab을 사용하면 되어서 만족하고 있었다.

ryusstory.tistory.com

 


정책 감사(Audit) 기반 정책 생성

클러스터 전체에 policy-audit-mode를 적용하는 방법도 있지만 특정 엔드포인트에 대해서만 정책감사모드를 적용하는 방법을 사용 했다.
mysql db에 대한 연결로그를 확인 후 정책을 적용해 보기로 했다

절차 개요

먼저 절차를 간단하게 소개해보면

  1. 감사 모드를 켜고,
  2. 정책을 적용하길 원하는 엔드포인트로 deny 정책을 적용
  3. 정책에 의해 트래픽이 막히지 않고, 감사로그로만 남음
  4. 해당 감사로그를 보고 정책을 생성해서 적용
  5. 감사 모드 종료

감사 모드 활성화

아래처럼 mysql pod가 동작하고 있는 노드를 찾아서 해당 노드의 cilium-agent에 Policy Audit Mode를 Enabled 상태로 변경한다

CILIUM_NAMESPACE="kube-system"
PODNAME=$(kubectl get pods -l app=mysql -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

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

초기 전체 거부 정책 적용

정책 감사를 위해 cnp-mysql.yaml 파일로 정책을 만들어서 적용해준다.
마지막의 - {}는 전체 거부를 의미한다.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: mysql-access-policy
spec:
  description: Allow backend-http and backend-grpc to access MySQL
  endpointSelector:
    matchLabels:
      app: mysql
  ingress:
  - {}

감사로그 확인

정책 감사 모드가 켜져 있으므로, 정책을 적용했음에도 Click action을 해보면 정상적으로 동작하는 걸 알 수 있는데, HTTP click과 grpc click을 한번씩 클릭해주고 hubble observe로 확인해보면 정책에 걸린 트래픽을 볼 수 있다.

$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- hubble observe flows -t policy-verdict --last 4 

Sep  5 13:39:39.508: default/backend-http-5859bf58f-gr8nq:35152 (ID:13025) -> default/mysql-647d757f9d-xzqxv:3306 (ID:18805) policy-verdict:none INGRESS AUDITED (TCP Flags: SYN)
Sep  5 13:39:39.794: default/backend-grpc-6955f774c9-pcj6k:38440 (ID:13702) -> default/mysql-647d757f9d-xzqxv:3306 (ID:18805) policy-verdict:none INGRESS AUDITED (TCP Flags: SYN)
Sep  5 13:39:40.278: default/backend-grpc-6955f774c9-pcj6k:38444 (ID:13702) -> default/mysql-647d757f9d-xzqxv:3306 (ID:18805) policy-verdict:none INGRESS AUDITED (TCP Flags: SYN)
Sep  5 13:39:40.562: default/backend-grpc-6955f774c9-pcj6k:38448 (ID:13702) -> default/mysql-647d757f9d-xzqxv:3306 (ID:18805) policy-verdict:none INGRESS AUDITED (TCP Flags: SYN)

-o json 으로 출력하면 더 상세한 데이터를 볼 수 있다 (yaml은 안된다..)

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

{"flow":{"time":"2025-09-05T13:39:40.562530912Z","uuid":"7cd50673-201a-49b7-8957-b66984d6c2d1","verdict":"AUDIT","ethernet":{"source":"aa:13:e9:44:fc:02","destination":"ce:ce:a7:f1:65:98"},"IP":{"source":"10.0.1.225","destination":"10.0.2.3","ipVersion":"IPv4"},"l4":{"TCP":{"source_port":38448,"destination_port":3306,"flags":{"SYN":true}}},"source":{"identity":13702,"cluster_name":"default","namespace":"default","labels":["k8s:app=backend-grpc","k8s:id=3tier-app","k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default","k8s:io.cilium.k8s.policy.cluster=default","k8s:io.cilium.k8s.policy.serviceaccount=default","k8s:io.kubernetes.pod.namespace=default"],"pod_name":"backend-grpc-6955f774c9-pcj6k"},"destination":{"ID":3437,"identity":18805,"cluster_name":"default","namespace":"default","labels":["k8s:app=mysql","k8s:id=3tier-db","k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default","k8s:io.cilium.k8s.policy.cluster=default","k8s:io.cilium.k8s.policy.serviceaccount=default","k8s:io.kubernetes.pod.namespace=default"],"pod_name":"mysql-647d757f9d-xzqxv","workloads":[{"name":"mysql","kind":"Deployment"}]},"Type":"L3_L4","node_name":"kind-worker2","node_labels":["beta.kubernetes.io/arch=amd64","beta.kubernetes.io/os=linux","kubernetes.io/arch=amd64","kubernetes.io/hostname=kind-worker2","kubernetes.io/os=linux"],"event_type":{"type":5},"traffic_direction":"INGRESS","is_reply":false,"Summary":"TCP Flags: SYN"},"node_name":"kind-worker2","time":"2025-09-05T13:39:40.562530912Z"}

그리고 host에서 hubble observe 를 통해서도 AUDITED라는 메세지로 확인이 가능하다

외부(world)에서 3306 포트로 접근할 때 감사 로그가 어떻게 보이는지도 확인해보았다.
kubectl port-forward service/mysql 3306:3306 으로 포트를 열어준 뒤 curl telnet://127.0.0.1:3306으로 요청을 보내서 한번 더 확인을 해봤다.

hubble observe -f --to-label app=mysql 명령어를 통해서 hubble observe에서는 아래처럼 확인이 됐지만 kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- hubble observe flows -t policy-verdict --last 명령어로는 확인이 되지 않았지만 어차피 실제로는 사용되지 않을 경로이기 때문에 실제 사용 경로가 아니므로 로그가 보이지 않아도 문제는 없다.

$ hubble observe -f --to-label app=mysql

Sep  5 13:50:38.501: 127.0.0.1:42770 (world) <> default/mysql-647d757f9d-xzqxv (ID:18805) pre-xlate-rev TRACED (TCP)

로그 양이 많을 경우 sortuniq를 사용해서 결과를 필터링하면 편하게 볼 수 있다. 필터링 해보면 k8s:app=backend-grpck8s:app=backend-http의 두 app만 확인된다.

$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
  hubble observe flows -t policy-verdict -o json | jq ".flow.source.labels" -c | sort | uniq

["k8s:app=backend-grpc","k8s:id=3tier-app","k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default","k8s:io.cilium.k8s.policy.cluster=default","k8s:io.cilium.k8s.policy.serviceaccount=default","k8s:io.kubernetes.pod.namespace=default"]
["k8s:app=backend-http","k8s:id=3tier-app","k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default","k8s:io.cilium.k8s.policy.cluster=default","k8s:io.cilium.k8s.policy.serviceaccount=default","k8s:io.kubernetes.pod.namespace=default"]

정책 생성 및 적용

위 내용을 바탕으로 정책을 생성하고 앞에서 작성했던 cnp-mysql.yaml- {} 부분을 위에서 알아낸 label 로 변경하고 apply를 해준다.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: mysql-access-policy
spec:
  description: Allow backend-http and backend-grpc to access MySQL
  endpointSelector:
    matchLabels:
      app: mysql
  ingress:
  - fromEndpoints:
    - matchExpressions:
      - key: app
        operator: In
        values: ["backend-http", "backend-grpc"]
    toPorts:
    - ports:
      - port: "3306"
        protocol: TCP

정책 감사 모드를 끄고 나서 접속이 안되는 것도 확인하기 위해 정책 감사 모드를 끄기 전에 frontend pod에서도 mysql로 접속을 시도해보고 잘 열리는 걸 확인한다.
위 정책이 적용되면 마지막에 암묵적 거부(Implicit Deny)가 적용되기 때문에 backend-http와 backend-grpc를 제외하면 모두 막힐 예정이다.

$ kubectl exec deployment/frontend -- nc -vz mysql 3306
mysql (10.96.14.118:3306) open

hubble observe flow에 frontend까지 잘 들어왔는지도 확인해보면 잘 들어온 걸 알 수 있다

ryuss@R250822:/mnt/d/study/kind-git/tmp$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- hubble observe flows -t policy-verdict -o json | jq ".flow.source.labels" -c | sort | uniq | jq
[
  "k8s:app=backend-grpc",
  "k8s:id=3tier-app",
  "k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default",
  "k8s:io.cilium.k8s.policy.cluster=default",
  "k8s:io.cilium.k8s.policy.serviceaccount=default",
  "k8s:io.kubernetes.pod.namespace=default"
]
[
  "k8s:app=backend-http",
  "k8s:id=3tier-app",
  "k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default",
  "k8s:io.cilium.k8s.policy.cluster=default",
  "k8s:io.cilium.k8s.policy.serviceaccount=default",
  "k8s:io.kubernetes.pod.namespace=default"
]
[
  "k8s:app=frontend",
  "k8s:id=3tier-app",
  "k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default",
  "k8s:io.cilium.k8s.policy.cluster=default",
  "k8s:io.cilium.k8s.policy.serviceaccount=default",
  "k8s:io.kubernetes.pod.namespace=default"
]

감사 모드 해제

이제 처음에 정책 감사 모드를 활성화 했던 것처럼 다시 PolicyAuditMode를 비활성화 해준다

CILIUM_NAMESPACE="kube-system"
PODNAME=$(kubectl get pods -l app=mysql -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

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

정책 적용 결과 검증

웹에서 http clickgrpc click을 한번씩 눌러주고 로그를 확인하면 문제없이 통신 되는게 확인된다

ryuss@R250822:/mnt/d/study/kind-git/tmp$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    hubble observe flows -t policy-verdict --to-pod mysql

...
Sep  5 14:17:06.559: default/backend-grpc-6955f774c9-pcj6k:38898 (ID:43409) -> default/mysql-647d757f9d-xzqxv:3306 (ID:18805) policy-verdict:L3-L4 INGRESS ALLOWED (TCP Flags: SYN)
Sep  5 14:17:06.849: default/backend-http-5859bf58f-gr8nq:45722 (ID:13025) -> default/mysql-647d757f9d-xzqxv:3306 (ID:18805) policy-verdict:L3-L4 INGRESS ALLOWED (TCP Flags: SYN)

frontend pod에서 mysql:3306을 호출해보면 앞의 정책이 잘 적용되어 통신이 되지 않는다

$ kubectl exec deployment/frontend -- nc -vz mysql 3306
...반응없음

로그를 확인해보면 frontend에서 들어온 3306포트로의 연결은 막힌걸 볼 수 있다

$ kubectl -n "$CILIUM_NAMESPACE" exec "$CILIUM_POD" -c cilium-agent -- \
    hubble observe flows -t policy-verdict --to-pod mysql --last 1
Sep  5 14:17:52.005: default/frontend-74647b95d9-lnvss:35443 (ID:51304) <> default/mysql-647d757f9d-xzqxv:3306 (ID:18805) policy-verdict:none INGRESS DENIED (TCP Flags: SYN)


$ hubble observe -f --to-label app=mysql
Sep  5 14:23:37.553: default/frontend-74647b95d9-lnvss (ID:51304) <> default/mysql-647d757f9d-xzqxv:3306 (ID:18805) post-xlate-fwd TRANSLATED (TCP)
Sep  5 14:23:37.553: default/frontend-74647b95d9-lnvss:35297 (ID:51304) <> default/mysql-647d757f9d-xzqxv:3306 (ID:18805) policy-verdict:none INGRESS DENIED (TCP Flags: SYN)
Sep  5 14:23:37.553: default/frontend-74647b95d9-lnvss:35297 (ID:51304) <> default/mysql-647d757f9d-xzqxv:3306 (ID:18805) Policy denied DROPPED (TCP Flags: SYN)

 

'> Tech' 카테고리의 다른 글

MinIO 오브젝트 스토리지 에센셜 강의 자료 정리  (0) 2025.09.11
Cilium:Securing gRPC 테스트  (0) 2025.09.05
K8s 테스트용 3tier sample  (0) 2025.09.05

관련글 더보기