iamjjanga blog

Cilium Study Security Network Policy

Intro

Cilium Security

Policy Enforcement — Cilium 1.18.1 documentation

ID 기반 보안 정책 (https://docs.cilium.io/en/stable/security/network/identity/)

Cilium Envoy Proxy

Cilium Envoy Proxy
https://docs.cilium.io/en/stable/security/network/proxy/envoy/

Cilium은 클러스터의 네트워크 정책에 지정된 대로 HTTP 및 기타 L7 정책을 적용하기 위해 이 최소 배포를 호스트 프록시로 사용

Identity-Based

https://docs.cilium.io/en/stable/security/network/identity/

기존의 IP 주소 기반 접근 제어와 달리, 클러스터 내 모든 엔드포인트(예: Pod)에 고유한 Security Identity를 부여하고 이 Identity를 중심으로 네트워크 정책을 집행하는 방식

다음 특징을 가진다.

Cilium이 Identity를 확인하는 순서

  1. Pod 혹은 Container가 생성되면, Cilium은 컨테이너 런타임에서 수신한 Event를 기반으로 네트워크 Pod 또는 Container를 나타내는 Endpoint(Cilium Endpoint)를 생성
  2. 생성된 Endpoint ID를 확인
  3. Pod 또는 Container의 Label이 변경될때마다 재확인하고 필요에 따라 수정
 1# Cilium Endpoint 확인
 2kubectl get ciliumendpoints.cilium.io -n kube-system
 3NAME                              SECURITY IDENTITY   ENDPOINT STATE   IPV4           IPV6
 4coredns-674b8bbfcf-7tm5s          31321               ready            172.20.0.132   
 5coredns-674b8bbfcf-np4mw          31321               ready            172.20.0.204   
 6hubble-relay-fdd49b976-lgr8q      19024               ready            172.20.0.170   
 7hubble-ui-655f947f96-rrkh8        10462               ready            172.20.0.159   
 8metrics-server-5dd7b49d79-44vww   14275               ready            172.20.0.229  
 9
10# Cilium ID 확인
11kubectl get ciliumidentities.cilium.io
12NAME    NAMESPACE            AGE
1310462   kube-system          25m
1412743   cilium-monitoring    25m
1514275   kube-system          25m
1619024   kube-system          25m
1731321   kube-system          25m
1832083   local-path-storage   25m
1950153   cilium-monitoring    25m

Q. 기동 중인 Pod에 Label 추가 후 Cilium ID(보안 Label)에 어떻게 반영되는지?

 1# 현재 CoreDNS의 ID 확인
 2kubectl exec -it -n kube-system ds/cilium -- cilium identity get 31321
 331321   k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=kube-system
 4        k8s:io.cilium.k8s.policy.cluster=default
 5        k8s:io.cilium.k8s.policy.serviceaccount=coredns
 6        k8s:io.kubernetes.pod.namespace=kube-system
 7        k8s:k8s-app=kube-dns
 8
 9# Label 변경(추가)
10kubectl label pods -n kube-system -l k8s-app=kube-dns study=8w
11
12# ID 재확인
13kubectl exec -it -n kube-system ds/cilium -- cilium identity get 31321
1431321   k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=kube-system
15        k8s:io.cilium.k8s.policy.cluster=default
16        k8s:io.cilium.k8s.policy.serviceaccount=coredns
17        k8s:io.kubernetes.pod.namespace=kube-system
18        k8s:k8s-app=kube-dns
19
20# 신규 ID 생성됨
21kubectl exec -it -n kube-system ds/cilium -- cilium identity list
22...
2319722   k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=kube-system
24        k8s:io.cilium.k8s.policy.cluster=default
25        k8s:io.cilium.k8s.policy.serviceaccount=coredns
26        k8s:io.kubernetes.pod.namespace=kube-system
27        k8s:k8s-app=kube-dns
28        k8s:study=8w

Special Identities

Cilium에서 관리하는 모든 엔드포인트에는 ID가 할당되는데, 관리하지 않는 네트워크 엔드포인트와의 통신을 허용하기 위해 이러한 엔드포인트를 나타내는 특수(Special) ID가 존재

 1kubectl exec -it -n kube-system ds/cilium -- cilium identity list
 2ID      LABELS
 31       reserved:host
 4        reserved:kube-apiserver
 52       reserved:world
 63       reserved:unmanaged
 74       reserved:health
 85       reserved:init
 96       reserved:remote-node
107       reserved:kube-apiserver
11        reserved:remote-node
128       reserved:ingress
139       reserved:world-ipv4
1410      reserved:world-ipv6
158836    k8s:app.kubernetes.io/instance=metrics-server
16        k8s:app.kubernetes.io/name=metrics-server
17        k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=kube-system
18        k8s:io.cilium.k8s.policy.cluster=default
19        k8s:io.cilium.k8s.policy.serviceaccount=metrics-server*

Identity Management in the cluster

Network Policy

alt text
https://isovalent.com/blog/post/intro-to-cilium-network-policies/

Kubernetes Network Policy 대비 Cilium Network Policy가 어떤 이점을 가지고 있는지 표 형식으로 정리

항목Kubernetes Network PolicyCilium Network Policy
정책 형식표준 NetworkPolicy 리소스NetworkPolicy 및 Cilium CustomResource (CiliumNetworkPolicy)
지원 레이어L3, L4 (IP, Port)L3, L4, L7 (HTTP, Kafka 등 애플리케이션 프로토콜 인식)
정책 적용 범위네임스페이스 Scope네임스페이스 또는 클러스터 전체(Global) 정책
정책 객체 확장성제한적(커스텀 확장 불가, 표준에 한정)확장성 높음: DNS 기반, 동적 인식 등 다양한 조건 지원
엔진/기술일반 CNI 플러그인(플러그인마다 구현 세부 상이)eBPF 기반으로 리눅스 커널 직접 제어
정책 조건PodSelector, NamespaceSelector, IPBlock위 조건 모두 + EndPointSelector, DNS, L7규칙 등 다양
성능/가시성표준 리눅스 커널 iptables 기반, 가시성 낮음eBPF 기반 고성능, 트래픽 가시성 Hubble 지원
클러스터/멀티클러스터클러스터 내 정책만 지원멀티클러스터 정책 지원(ClusterMesh)
정책 충돌네임스페이스 내 중첩 가능, 우선순위 개념 없음네임스페이스/클러스터 스코프 우선순위 및 중첩 체계 지원
관리 및 생태계 통합kubectl 등 표준 툴kubectl, REST API, Cilium CLI, Hubble 등 통합 관리

Cilium Network Policy는 다음 큰 특징을 가지고 있다.

Policy Enforcement Modes

다음 3가지 정책 적용 모드가 있다.

Endpoint default Policy

기본적으로 모든 송신 및 수신 트래픽은 모든 엔드포인트에 허용됩니다. 네트워크 정책에 의해 엔드포인트가 선택되면 명시적으로 허용된 트래픽만 허용되는 기본 거부 상태로 전환

상태는 방향별로 적용된다.

  flowchart TD
    subgraph 기본["기본 상태"]
        Start["모든 트래픽 허용"]
    end
    
    subgraph 방향별_동작["방향별 동작"]
        direction TB
        Ingress["수신 정책 적용"]
        Egress["송신 정책 적용"]
        
        Ingress --> IDeny["수신: 기본 거부 모드"]
        Egress --> EDeny["송신: 기본 거부 모드"]
    end
    
    subgraph 결과["최종 상태"]
        Final["정책에 명시된 트래픽만 허용"]
    end
    
    Start --> Ingress
    Start --> Egress
    IDeny --> Final
    EDeny --> Final
    
    classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px,color:#000000
    classDef action fill:#e1f5fe,stroke:#0288d1,stroke-width:2px,color:#000000
    classDef result fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000000
    
    class Start default
    class Ingress,Egress action
    class IDeny,EDeny,Final result

Rule Basics

Network Policy Editor

alt text
https://editor.networkpolicy.io/

[Example] Layer 3 예시

https://docs.cilium.io/en/stable/security/policy/language/#layer-3-examples

[Example] Layer 4 예시

https://docs.cilium.io/en/stable/security/policy/language/#layer-4-examples

[Layer 4] 예시 1 - app: myService 엔드포인트 송신 80(TCP) 포트 제한

 1apiVersion: "cilium.io/v2"
 2kind: CiliumNetworkPolicy
 3metadata:
 4  name: "l4-rule"
 5spec:
 6  endpointSelector:
 7    matchLabels:
 8      app: myService
 9  egress:
10    - toPorts:
11      - ports:
12        - port: "80"
13          protocol: TCP

[Layer 4] 예시 2 - app: myService 엔드포인트에 송신 80 ~ 444(TCP) 포트 제한

 1apiVersion: "cilium.io/v2"
 2kind: CiliumNetworkPolicy
 3metadata:
 4  name: "l4-port-range-rule"
 5spec:
 6  endpointSelector:
 7    matchLabels:
 8      app: myService
 9  egress:
10    - toPorts:
11      - ports:
12        - port: "80"
13          endPort: 444
14          protocol: TCP

[Layer 4] 예시 3 - app: myService 엔드포인트에서 송신 CIDR ‘192.0.2.0/24’ 80(TCP) 포트 제한

 1apiVersion: "cilium.io/v2"
 2kind: CiliumNetworkPolicy
 3metadata:
 4  name: "cidr-l4-rule"
 5spec:
 6  endpointSelector:
 7    matchLabels:
 8      role: myService
 9  egress:
10  - toCIDR:
11    - 192.0.2.0/24
12    toPorts:
13    - ports:
14      - port: "80"
15        protocol: TCP

TLS SNI 제한

 1apiVersion: "cilium.io/v2"
 2kind: CiliumNetworkPolicy
 3metadata:
 4  name: "l4-sni-rule"
 5spec:
 6  endpointSelector:
 7    matchLabels:
 8      app: myService
 9  **egress**:
10  - toPorts:
11    - ports:
12      - port: "443"
13        protocol: TCP
14      **serverNames:** # SNI
15      **- one.one.one.one**

[Example] Layer 7 예시

https://docs.cilium.io/en/stable/security/policy/language/#layer-7-examples

HTTP

[Layer 7] HTTP 예시 1 - /public Path 허용

 1apiVersion: "cilium.io/v2"
 2kind: CiliumNetworkPolicy
 3metadata:
 4  name: "rule1"
 5spec:
 6  description: "Allow HTTP GET /public from env=prod to app=service"
 7  endpointSelector:
 8    matchLabels:
 9      app: service
10  ingress:
11  - fromEndpoints:
12    - matchLabels:
13        env: prod
14    toPorts:
15    - ports:
16      - port: "80"
17        protocol: TCP
18      **rules:
19        http:
20        - method: "GET"
21          path: "/public"**

[Layer 7] HTTP 예시 2 - Header(**X-My-Header: true)**가 설정된 경우 모든 GET /path1PUT /path2 허용

 1apiVersion: "cilium.io/v2"
 2kind: CiliumNetworkPolicy
 3metadata:
 4  name: "l7-rule"
 5spec:
 6  endpointSelector:
 7    matchLabels:
 8      app: myService
 9  ingress:
10  - toPorts:
11    - ports:
12      - port: '80'
13        protocol: TCP
14      **rules:
15        http:
16        - method: GET
17          path: "/path1$"
18        - method: PUT
19          path: "/path2$"
20          headers:
21          - 'X-My-Header: true'**

DNS Policy and IP Discovery

Obtaining DNS Data for use by toFQDNs

alt text

 1# 다음 예제는 DNS 요청을 차단하지 않고 가로채기 방식으로 DNS 데이터를 수집합니다. 
 2# sub.cilium.io 및 모든 하위 도메인에 대한 L3 연결을 cilium.io허용 sub.cilium.io 합니다 
 3apiVersion: cilium.io/v2
 4kind: CiliumNetworkPolicy
 5metadata:
 6  name: "tofqdn-dns-visibility"
 7spec:
 8  endpointSelector:
 9    matchLabels:
10      any:org: alliance
11  egress:
12  - toEndpoints:
13    - matchLabels:
14       "k8s:io.kubernetes.pod.namespace": kube-system
15       "k8s:k8s-app": kube-dns
16    toPorts:
17      - ports:
18         - port: "53"
19           protocol: ANY
20        rules:
21          dns:
22            - matchPattern: "*"
23  - toFQDNs:
24      - matchName: "cilium.io"
25      - matchName: "sub.cilium.io"
26      - matchPattern: "*.sub.cilium.io"

Lab

[Lab1] Locking Down External Access with DNS-Based Policies

https://docs.cilium.io/en/stable/security/dns/

[DNS Egress Policy] mediabot Pod가 api.github.com 에만 액세스하도록 허용

 1cat << EOF | kubectl apply -f -
 2apiVersion: "cilium.io/v2"
 3kind: CiliumNetworkPolicy
 4metadata:
 5  name: "fqdn"
 6spec:
 7  endpointSelector:
 8    matchLabels:
 9      org: empire
10      class: mediabot
11  egress:
12  - toFQDNs:
13    - matchName: "api.github.com"
14  - toEndpoints:
15    - matchLabels:
16        "k8s:io.kubernetes.pod.namespace": kube-system
17        "k8s:k8s-app": kube-dns
18    toPorts:
19    - ports:
20      - port: "53"
21        protocol: ANY
22      rules:
23        dns:
24        - matchPattern: "*"
25EOF
26
27# 확인
28kubectl get cnp
29NAME   AGE   VALID
30fqdn   8s    True

CNP 설정이후 Cilium 설정에서는 다음 값을 확인할 수 있다.

 1# Cilium Policy Selector 확인
 2kubectl exec -it -n kube-system ds/cilium -- cilium policy selectors
 3SELECTOR                                                                                                                                                                      LABELS         USERS   IDENTITIES
 4&LabelSelector{MatchLabels:map[string]string{any.class: mediabot,any.org: empire,k8s.io.kubernetes.pod.namespace: default,},MatchExpressions:[]LabelSelectorRequirement{},}   default/fqdn   1       6390   
 5
 6# Cilium Config 확인
 7cilium config view | grep -i dns
 8dnsproxy-enable-transparent-mode                  true
 9dnsproxy-socket-linger-timeout                    10
10hubble-metrics                                    dns drop tcp flow port-distribution icmp httpV2:exemplars=true;labelsContext=source_ip,source_namespace,source_workload,destination_ip,destination_namespace,destination_workload,traffic_direction
11tofqdns-dns-reject-response-code                  refused
12tofqdns-enable-dns-compression                    true
13tofqdns-endpoint-max-ip-per-hostname              1000
14tofqdns-idle-connection-grace-period              0s
15tofqdns-max-deferred-connection-deletes           10000
16tofqdns-preallocate-identities                    true
17tofqdns-proxy-response-max-delay                  100ms

이전 테스트 처럼 api.github.com과 support.github.com에 curl 요청을 보내보면, api.github.com 요청만 정상적으로 forwarding된걸 확인할 수 있다.

1# forwarded
2kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
3HTTP/2 200 
4
5# dropped
6kubectl exec mediabot -- curl -I -s --max-time 5 https://support.github.com | head -1
7command terminated with exit code 28

alt text

alt text

그리고 추가적으로 Cilium Agent 내에서 go로 구현된 Lightweight Proxy가 DNS 쿼리 / 응답에 대한 가로채기를 진행하고 이를 hubble observe 를 통해서 흐름 로그를 확인할 수 있다.

1cilium hubble port-forward&
2hubble observe --pod mediabot
3
4# 
5Sep  7 14:19:15.615: default/mediabot:44740 (ID:6390) <- kube-system/coredns-674b8bbfcf-np4mw:53 (ID:19722) dns-response proxy FORWARDED (DNS Answer  TTL: 4294967295 (Proxy api.github.com. AAAA))
6...
7Sep  7 14:19:15.618: default/mediabot:44740 (ID:6390) <- kube-system/coredns-674b8bbfcf-np4mw:53 (ID:19722) dns-response proxy FORWARDED (DNS Answer "20.200.245.245" TTL: 30 (Proxy api.github.com. A))
8...
9Sep  7 14:19:15.621: default/mediabot:44066 (ID:6390) -> api.github.com:443 (ID:16777217) policy-verdict:L3-Only EGRESS ALLOWED (TCP Flags: SYN)

Cilium에서 cilium fqdn cache list 라는 명령어로 FQDN(Full Qulified Domain Name)에 대한 Caching 정보를 확인할 수 있는데, 위 요청에서는 다음과 같이 확인되었다.

 1# 현재 mediabot이 실행중인 노드는 k8s-w1이다.
 2k get pods -o wide
 3NAME       READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
 4mediabot   1/1     Running   0          78m   172.20.1.148   k8s-w1   <none>           <none>
 5
 6# k8s-w1에서는 FQDN Cache List가 조회돔
 7c1 fqdn cache list
 8Endpoint   Source       FQDN                  TTL   ExpirationTime             IPs               
 92809       connection   support.github.com.   0     2025-09-07T15:10:12.302Z   185.199.108.133   
102809       connection   support.github.com.   0     2025-09-07T15:10:12.302Z   185.199.109.133   
112809       connection   support.github.com.   0     2025-09-07T15:10:12.302Z   185.199.110.133   
122809       connection   support.github.com.   0     2025-09-07T15:10:12.302Z   185.199.111.133   
132809       connection   api.github.com.       0     2025-09-07T15:10:12.302Z   20.200.245.245    
14
15# k8s-ctr, k8s-w2에서는 조회되지 않음
16c0 fqdn cache list
17c2 fqdn cache list