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

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
클러스터 전체에 policy-audit-mode를 적용하는 방법도 있지만 특정 엔드포인트에 대해서만 정책감사모드를 적용하는 방법을 사용 했다.
mysql db에 대한 연결로그를 확인 후 정책을 적용해 보기로 했다
먼저 절차를 간단하게 소개해보면
아래처럼 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)
로그 양이 많을 경우 sort와 uniq를 사용해서 결과를 필터링하면 편하게 볼 수 있다. 필터링 해보면 k8s:app=backend-grpc 와 k8s: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 click과 grpc 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)

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