DevOps/Study

Cilium Study [1기] 8주차 K8S Security & Tetragon

juyeon22 2025. 9. 6. 23:47

Cilium Study [1기] 8주차 Cilium Security & Tetragon

안녕하세요 이번 게시물에서는 Cilium Security & Tetragon 라는 주제에 대한 정보를 전달 드리고자 합니다.

1. 실습 환경 구성

실습 환경은 가장 기본적인 구성(Master Mode 1 EA, Worker Node 2 EA)으로 진행합니다.

curl.exe -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/cilium-study/8w/Vagrantfile

vagrant up

vargrant

2. Cilium Security 이론

Cilium Security

Cilium에서 제공하는 보안정책은 L3, L4, L7계층의 보안 정책을 적용할 수 있습니다.

L3 계층의 보안 정책은 주로 IP기반 정책이기 떄문에 kubernetes 와는 적합하지 않아 Identity 기반으로 보안 정책을 적용합니다.

Figure 2.1 Cilium L3 정책 예시

정책 적용 시 응답 패킷은 자동으로 허용되는 특징이 있습니다.

기본적으로 Cilium에서는 정책이 적용되지 않는 한 Any Open이 Default입니다.

L7 보안 정책을 수립하는 경우에는 Envoy Proxy를 이용하여 정책을 적용하게 됩니다.

IPSec, WireGuard같은 트래픽의 암호화 기능도 지원을 합니다.

IPSec은 BPF 호스트 라우팅에서는 작동하지 않고 터널당 단일 CPU 코어로 제한된 특징이 있으며, WireGuard 사용 시 VXLAN이나 Geneve와 같이 사용하는 경우 두번 캡슐화 된다는 특징이 있습니다.

Figure 2.2 Node간 통신 시 암호화 과정

Identity

Identity란 Cilum CNI에서 Pod를 생성하게되면 생성 시 연결된 Label 기반으로 생성되는 고유한 값을 말합니다.

EndPoint들이 동일한 Security Relevant Labels 사용 시 동일한 ID를 공유합니다.

# Identity 확인
kubectl get ciliumendpoints.cilium.io -n kube-system
# NAME                              SECURITY IDENTITY   ENDPOINT STATE   IPV4           IPV6
# coredns-674b8bbfcf-5ztwh          21925               ready            172.20.0.16
# coredns-674b8bbfcf-nfr9n          21925               ready            172.20.0.176
# hubble-relay-fdd49b976-x8mgm      22042               ready            172.20.0.70
# hubble-ui-655f947f96-jkvnt        14764               ready            172.20.0.251
# metrics-server-5dd7b49d79-crbs5   21898               ready            172.20.0.3

Cilumidentites의 정보를 세부적으로 확인하면 다음과 같이 정보를 확인 할 수 있습니다.

kubectl get ciliumidentities.cilium.io 21925 -o yaml | yq
# {
#   "apiVersion": "cilium.io/v2",
#   "kind": "CiliumIdentity",
#   "metadata": {
#     "creationTimestamp": "2025-09-06T07:05:17Z",
#     "generation": 1,
#     "labels": {
#       "io.kubernetes.pod.namespace": "kube-system"
#     },
#     "name": "21925",
#     "resourceVersion": "828",
#     "uid": "600d078d-786b-4f41-9a8a-df11295678c0"
#   },
#   "security-labels": {
#     "k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name": "kube-system",
#     "k8s:io.cilium.k8s.policy.cluster": "default",
#     "k8s:io.cilium.k8s.policy.serviceaccount": "coredns",
#     "k8s:io.kubernetes.pod.namespace": "kube-system",
#     "k8s:k8s-app": "kube-dns"
#   }
# }

Identity는 Pod 나 Container가 시작되면, Container Runtime에서 수신한 이벤트를 기반으로 EndPoint를 생성합니다.

그리고 Label이 변경될 때마다 ID가 재확인이 되고 필요에 따라 자동으로 수정됩니다.

해당 내용을 확인해보기 위해 CoreDNS에 Label을 추가하여 확인해 보도록 하겠습니다.

kubectl label pods -n kube-system -l k8s-app=kube-dns study=8w

kubectl get ciliumendpoints.cilium.io -n kube-system
# NAME                              SECURITY IDENTITY   ENDPOINT STATE         IPV4           IPV6
# coredns-674b8bbfcf-5ztwh          21925               waiting-for-identity   172.20.0.16
# coredns-674b8bbfcf-nfr9n          21925               waiting-for-identity   172.20.0.176
# hubble-relay-fdd49b976-x8mgm      22042               ready                  172.20.0.70
# hubble-ui-655f947f96-jkvnt        14764               ready                  172.20.0.251
# metrics-server-5dd7b49d79-crbs5   21898               ready                  172.20.0.3



kubectl get ciliumendpoints.cilium.io -n kube-system
# NAME                              SECURITY IDENTITY   ENDPOINT STATE   IPV4           IPV6
# coredns-674b8bbfcf-5ztwh          4308                ready            172.20.0.16
# coredns-674b8bbfcf-nfr9n          4308                ready            172.20.0.176

Cilium은 pod update 이벤트를 watch하므로, labels 변경 시 endpoint가 waiting-for-identity 상태로 전환되어 새로운 identity를 할당받고 Security Labels와 관련된 네트워크 정책도 자동으로 재적용됩니다.

대규모 클러스터에서는 Label을 변경하게 되면 identity 할당이 빈번해져 성능 저하가 발생하기때문에 identity-relevan labels를 제한하는 것을 권장합니다.

Cilium에서 관리하는 모든 엔드포인트에는 ID가 할당되지만 Cilium에서 관리하지 않는 Network EndPoint와 통신을 허용하기위한 특수 ID가 존재합니다.

특수 ID는 reserved 라는 문자열 접두사가 붙습니다.

kubectl exec -it -n kube-system ds/cilium -- cilium identity list
# ID      LABELS
# 1       reserved:host
#         reserved:kube-apiserver
# 2       reserved:world
# 3       reserved:unmanaged
# 4       reserved:health
# 5       reserved:init
# 6       reserved:remote-node
# ...

Identtiy 관리는 Cilium Agent에서 관리를 했지만 최근에는 Cilum Operator에서 관리하는 방식을 제공하고 있습니다(Beta)

Operator에서 관리를 하게 되면 여러 Agent가 동시에 Identity를 생성시 중앙에서 통제를 할 수 있어 CID중복이 감소할 수 있어 네트워크 정책의 신뢰성과 확장성을 향상 시킬 수 있습니다.

Network Policy

Cilium에서 제공하는 Network Policy는 kubernetes에서 기본적으로 제공하는 Network Policy와 다르게 3~7계층에서 모두 송 수신에 대한 정책을 지원합니다.

Figure 2.3 Cilium vs Kubernetes

클러스터 전체 정책은 CiliumClusterwideNetworkPolicy 으로 적용을 하고 Namespace별로 적용 시에는 CiliumNetworkPolicy 정책을 사용해서 적용합니다.

Network Policy 적용 시 Ingress, Egress, EndPoint, Label과 같은 요소들로 정책을 적용 합니다.

L3 정책을 실제로 적용 해 보겠습니다.

# 테스트를 위한 리소스 생성
apiVersion: v1
kind: Namespace
metadata:
  name: cnp-test
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: client
  namespace: cnp-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: client
  template:
    metadata:
      labels:
        app: client
    spec:
      containers:
      - name: netshoot
        image: nicolaka/netshoot:latest
        command: ["sleep", "infinity"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
  namespace: cnp-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
  template:
    metadata:
      labels:
        app: httpbin
    spec:
      containers:
      - name: httpbin
        image: kennethreitz/httpbin
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  namespace: cnp-test
spec:
  selector:
    app: httpbin
  ports:
  - name: http
    port: 80
    targetPort: 80

테스트 결과를 확실하게 확인하기 위해서 NameSpace 전체에 기본 차단을 적용 하고 L3 정책에서 적용 가능한 범위만 트래픽을 허용하는 방식으로 진행하겠습니다.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: allow-dns-to-kube-dns
  namespace: cnp-test
spec:
  endpointSelector:
    matchLabels:
      app: client
  egress:
  - toEndpoints:
    - matchLabels:
        "k8s:io.kubernetes.pod.namespace": kube-system
        "k8s:k8s-app": kube-dns
    toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
        - matchPattern: "*"

EndPoint 기반으로 정책을 적용하는 사례입니다.
app=client에서 appl=httpbin으로 TCP/80만 허용하는 정책입니다.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: ep-allow-client-to-httpbin
  namespace: cnp-test
spec:
  endpointSelector:
    matchLabels:
      app: client
  egress:
  - toEndpoints:
    - matchLabels:
        app: httpbin
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
kubectl -n cnp-test exec deploy/client -it -- curl -sS http://httpbin.cnp-test:80/get
# {
#   "args": {},
#   "headers": {
#     "Accept": "*/*",
#     "Host": "httpbin.cnp-test",
#     "User-Agent": "curl/8.14.1"
#   },
#   "origin": "172.20.1.176",
#   "url": "http://httpbin.cnp-test/get"
# }
kubectl -n cnp-test exec deploy/client -it -- curl -sS -m 3 http://httpbin.cnp-test:81 || echo "blocked as expected"
# curl: (28) Connection timed out after 3001 milliseconds
# command terminated with exit code 28
# blocked as expected

Service 기반으로 정책을 적용하는 사례입니다.
내부 Service 호출 시에는 toService를 사용하며, 이름이나 Label Collector를 이용해서 Service를 참조합니다.

client → httpbin Service 경유 트래픽은 허용되며, 다른 Service/Pod IP로 직접 접근은 차단합니다.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: svc-egress-to-httpbin
  namespace: cnp-test
spec:
  endpointSelector:
    matchLabels:
      app: client
  egress:
  - toServices:
    - k8sService:
        serviceName: httpbin
        namespace: cnp-test
kubectl -n cnp-test exec deploy/client -it -- curl -sS http://httpbin.cnp-test:80/get
# {
#   "args": {},
#   "headers": {
#     "Accept": "*/*",
#     "Host": "httpbin.cnp-test",
#     "User-Agent": "curl/8.14.1"
#   },
#   "origin": "172.20.1.176",
#   "url": "http://httpbin.cnp-test/get"
# }

3. Clium Security 실습

Locking Down External Access with DNS-Based Policies

이번 실습을 통해서 다음과 같은 정보를 알아보고자 합니다.

  • DNS 기반 정책을 사용하여 클러스터 외부 서비스에 대한 이탈 액세스 제어
  • 패턴(또는 와일드카드)을 사용하여 DNS 도메인 하위 집합을 허용 목록에 추가
  • 외부 서비스 접근 제한을 위한 DNS, 포트 및 L7 규칙 결합

Empire의 mediabot pod가 Empire의 git 저장소 관리를 위해 GitHub에 접근해야 하는 간단한 시나리오 입니다.

cat << EOF > dns-sw-app.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mediabot
  labels:
    org: empire
    class: mediabot
    app: mediabot
spec:
  containers:
  - name: mediabot
    image: quay.io/cilium/json-mock:v1.3.8@sha256:5aad04835eda9025fe4561ad31be77fd55309af8158ca8663a72f6abb78c2603
EOF

kubectl apply -f dns-sw-app.yaml

kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
# HTTP/2 200

Figure 3.1 Hubble UI을 이용한 시나리오 확인

mediabot Pod가 api.github.com에만 액세스하도록 허용해보겠습니다.

cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "fqdn"
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
  - toFQDNs:
    - matchName: "api.github.com"
  - toEndpoints:
    - matchLabels:
        "k8s:io.kubernetes.pod.namespace": kube-system
        "k8s:k8s-app": kube-dns
    toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
        - matchPattern: "*"
EOF


cilium config view | grep -i dns
# dnsproxy-enable-transparent-mode                  true
# dnsproxy-socket-linger-timeout                    10
# hubble-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

kubectl exec mediabot -- curl -I -s --max-time 5 https://support.github.com | head -1
# command terminated with exit code 28

Network Policy가 적용된 경우, 목적지의 FQDN이 표현되는 것을 Hubble UI를 통해 확인할 수 있습니다.

Figure 3.2 Hubble UI을 이용한 Network Policy 확인

hubble로 확인 시 lightweight proxy가 DNS 쿼리 및 응답을 가로 채는 것을 알 수 있습니다.

cilium hubble port-forward&
hubble observe --pod mediabot

# Sep  6 10:35:11.979: default/mediabot (ID:3024) <> kube-system/kube-dns:53 (world) pre-xlate-fwd TRACED (UDP)
# Sep  6 10:35:11.979: default/mediabot (ID:3024) <> kube-system/coredns-674b8bbfcf-nfr9n:53 (ID:4308) post-xlate-fwd TRANSLATED (UDP)
# Sep  6 10:35:11.979: default/mediabot:57255 (ID:3024) -> kube-system/coredns-674b8bbfcf-nfr9n:53 (ID:4308) policy-verdict:L3-L4 EGRESS ALLOWED (UDP)
# Sep  6 10:35:11.979: default/mediabot:57255 (ID:3024) -> kube-system/coredns-674b8bbfcf-nfr9n:53 (ID:4308) to-proxy FORWARDED (UDP)
# Sep  6 10:35:11.987: default/mediabot:57255 (ID:3024) <> 192.168.10.102 (host) pre-xlate-rev TRACED (UDP)
# Sep  6 10:35:11.989: default/mediabot:57255 (ID:3024) -> kube-system/coredns-674b8bbfcf-nfr9n:53 (ID:4308) to-endpoint FORWARDED (UDP)
# Sep  6 10:35:11.989: default/mediabot:57255 (ID:3024) <> 192.168.10.102 (host) pre-xlate-rev TRACED (UDP)

이번에는 모든 GitHub 하위 도메인(예: 패턴)에 액세스하는 정책 실습을 진행해보겠습니다.

## cilium 파드 이름
export CILIUMPOD0=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-ctr -o jsonpath='{.items[0].metadata.name}')
export CILIUMPOD1=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w1  -o jsonpath='{.items[0].metadata.name}')
export CILIUMPOD2=$(kubectl get -l k8s-app=cilium pods -n kube-system --field-selector spec.nodeName=k8s-w2  -o jsonpath='{.items[0].metadata.name}')
echo $CILIUMPOD0 $CILIUMPOD1 $CILIUMPOD2

## 단축키(alias) 지정
alias c0="kubectl exec -it $CILIUMPOD0 -n kube-system -c cilium-agent -- cilium"
alias c1="kubectl exec -it $CILIUMPOD1 -n kube-system -c cilium-agent -- cilium"
alias c2="kubectl exec -it $CILIUMPOD2 -n kube-system -c cilium-agent -- cilium"

kubectl delete cnp fqdn
c0 fqdn cache clean -f
c1 fqdn cache clean -f
c2 fqdn cache clean -f

kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-dns/dns-pattern.yaml

# *.github.com 통신 확인
kubectl exec mediabot -- curl -I -s https://support.github.com | head -1
# HTTP/2 302
kubectl exec mediabot -- curl -I -s --max-time 5 https://github.com | head -1
# command terminated with exit code 28
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "fqdn"
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
  - toFQDNs:
    - matchName: "*.github.com"
  - toEndpoints:
    - matchLabels:
        "k8s:io.kubernetes.pod.namespace": kube-system
        "k8s:k8s-app": kube-dns
    toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
        - matchPattern: "*"

Figure 3.3 Hubble UI을 이용한 Network Policy 확인(2)

마지막으로 DNS + Port 조합으로도 정책을 적용할 수 있습니다.

kubectl delete cnp fqdn
c0 fqdn cache clean -f
c1 fqdn cache clean -f
c2 fqdn cache clean -f

kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-dns/dns-port.yaml

# HTTP/HTTPS 통신 확인
kubectl exec mediabot -- curl -I -s https://support.github.com | head -1
# HTTP/2 302
kubectl exec mediabot -- curl -I -s --max-time 5 http://support.github.com | head -1
# command terminated with exit code 28
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "fqdn"
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
  - toFQDNs:
    - matchPattern: "*.github.com"
    toPorts:
    - ports:
      - port: "443"
        protocol: TCP
  - toEndpoints:
    - matchLabels:
        "k8s:io.kubernetes.pod.namespace": kube-system
        "k8s:k8s-app": kube-dns
    toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
        - matchPattern: "*" 

WireGuard 설정 및 실습 : 터널 모드는 두 번 캡슐화 확인

해당 실습을 진행하기 전 실습 Application을 먼저 배포하겠습니다.

실습 시 기본적으로 Linux Kernel 5.6 이상 버전이 필요합니다.

cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webpod
  template:
    metadata:
      labels:
        app: webpod
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - sample-app
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: webpod
        image: traefik/whoami
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webpod
  labels:
    app: webpod
spec:
  selector:
    app: webpod
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
EOF


# k8s-ctr 노드에 curl-pod 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
  labels:
    app: curl
spec:
  nodeName: k8s-ctr
  containers:
  - name: curl
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF
​```

```bash

# 설정
helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
  --set encryption.enabled=true --set encryption.type=wireguard

# 재시작
kubectl -n kube-system rollout restart ds/cilium

# 설치 확인
cilium config view | grep -i wireguard
# enable-wireguard                                  true
# wireguard-persistent-keepalive                    0s

# k8s-ctr Node에 생성된 인터페이스 확인
ip -d -c addr show cilium_wg0
# 24: cilium_wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default
#     link/none  promiscuity 0  allmulti 0 minmtu 0 maxmtu 2147483552
#     wireguard numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536

# wg show 명령어로 wireGuard 상태 확인
wg show
# interface: cilium_wg0
# ....


# curl 호출
kubectl exec -it curl-pod -- curl webpod

# tcpdump확인
tcpdump -eni any udp port 51871
# 20:40:46.840868 eth1  Out ifindex 3 08:00:27:04:81:24 ethertype IPv4 (0x0800), length 196: 192.168.10.100.51871 > 192.168.10.101.51871: UDP, length 148
# 20:40:46.846307 eth1  In  ifindex 3 08:00:27:32:9e:97 ethertype IPv4 (0x0800), length 140: 192.168.10.101.51871 > 192.168.10.100.51871: UDP, length 92
# 20:40:46.848194 eth1  Out ifindex 3 08:00:27:04:81:24 ethertype IPv4 (0x0800), length 144: 192.168.10.100.51871 > 192.168.10.101.51871: UDP, length 96

Cilium TLS Interception 실습

Cilium TLS Interception이란 Cilium을 사용하여 TLS 암호화 연결을 투명하게 검사 것을 말합니다.

이 검사를 수행하게되면, 클라이언트 간 통신이 TLS로 보호되는 연결에서도 Cilium API 인식 가시성 및 정책을 작동시킬 수 있게됩니다.

SDS 방식을 사용하여 TLS 검사를 수행합니다.

SDS 방식은 Cilium은 구성된 시크릿 네임스페이스에서 관련 시크릿을 읽고, 핵심 Envoy SDS API를 사용하여 Envoy에 노출합니다.

Envoy는 Ingress 또는 Gateway API 구성에 대한 시크릿을 찾는 것과 같은 방식으로 NPDS에 대한 SDS 시크릿을 찾습니다

이 벙법을 사용하게되면 Envoy의 Secret 저장의 중복을 막을 수 있어 NPDS방식보다 더 효율적입니다.

실습을 통해 더 자세한 내용을 확인해보겠습니다.

cat << EOF > tls-config.yaml
tls:
  readSecretsOnlyFromSecretsNamespace: true
  secretsNamespace:
    name: cilium-secrets # This setting is optional, as it is the default
  secretSync:
    enabled: true
EOF

helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
-f tls-config.yaml

kubectl -n kube-system rollout restart deploy/cilium-operator
kubectl -n kube-system rollout restart ds/cilium

cilium config view | grep -i secret
# enable-ingress-secrets-sync                       true
# enable-policy-secrets-sync                        true
# ingress-secrets-namespace                         cilium-secrets
# policy-secrets-namespace                          cilium-secrets
# policy-secrets-only-from-secrets-namespace        true

실습 과정은 다음과 같습니다.

  • TLS 가로채기를 활성화하기 위해 내부 인증 기관(CA)과 해당 CA가 서명한 관련 인증서를 생성
  • DNS 기반 정책 규칙을 사용하여 가로챌 트래픽을 선택하려면 Cilium 네트워크 정책을 사용
  • cilium monitor를 사용하여 HTTP 요청의 세부 사항을 검사

Figure 3.5 tls-visibility 설명

# 내부 인증 기관(CA) 생성
# 비밀번호는 qwe123으로 통일
openssl genrsa -des3 -out myCA.key 2048

openssl req -x509 -new -nodes -key myCA.key -sha256 -days 1825 -out myCA.crt

openssl x509 -in myCA.crt -noout -text


# 지정된 DNS 이름에 대한 개인 키 및 인증서 서명 요청 생성
openssl genrsa -out internal-httpbin.key 2048

openssl req -new -key internal-httpbin.key -out internal-httpbin.csr

# 내부 CA 개인 키를 사용하여 httpbin.org에 대한 서명된 인증서를 생성합니다 internal-httpbin.crt
openssl x509 -req -days 360 -in internal-httpbin.csr -CA myCA.crt -CAkey myCA.key -CAcreateserial -out internal-httpbin.crt -sha256

openssl x509 -in internal-httpbin.crt -noout -text
.....
        # Issuer: C = KR, ST = Seoul, L = Seoul, O = cloudneta, OU = study, CN = cloudneta.net
        # Validity
        #     Not Before: Sep  6 12:31:56 2025 GMT
        #     Not After : Sep  1 12:31:56 2026 GMT
        # Subject: C = KR, ST = Seoul, L = Seoul, O = cloudneta, OU = study, CN = httpbin.org
.....


# 다음으로 대상 서비스에 대한 개인 키와 서명된 인증서를 모두 포함하는 Kubernetes Secret 생성
kubectl create secret tls httpbin-tls-data -n kube-system --cert=internal-httpbin.crt --key=internal-httpbin.key

# 클라이언트 포드 내에서 신뢰할 수 있는 CA로 내부 CA 추가
kubectl cp myCA.crt default/mediabot:/usr/local/share/ca-certificates/myCA.crt

kubectl exec mediabot -- update-ca-certificates

# Cilium에 신뢰할 수 있는 CA 목록 제공
kubectl cp default/mediabot:/etc/ssl/certs/ca-certificates.crt ca-certificates.crt

kubectl create secret generic tls-orig-data -n kube-system --from-file=ca.crt=./ca-certificates.crt

# 정책 적용 전 호출
kubectl exec -it mediabot -- curl -sL 'https://httpbin.org/headers' -v
# * Server certificate:
# *  subject: CN=httpbin.org
# *  start date: Jul 20 00:00:00 2025 GMT
# *  expire date: Aug 17 23:59:59 2026 GMT
# *  subjectAltName: host "httpbin.org" matched cert's "httpbin.org"
# *  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
# *  SSL certificate verify ok.


# 정책 적용
kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-tls-inspection/l7-visibility-tls.yaml


# 정책 적용 후 호출
kubectl exec -it mediabot -- curl -sL 'https://httpbin.org/headers' -v
# * Server certificate:
# *  subject: C=KR; ST=Seoul; L=Seoul; O=cloudneta; OU=study; CN=httpbin.org
# *  start date: Sep  6 12:31:56 2025 GMT
# *  expire date: Sep  1 12:31:56 2026 GMT
# *  common name: httpbin.org (matched)
# *  issuer: C=KR; ST=Seoul; L=Seoul; O=cloudneta; OU=study; CN=cloudneta.net
# *  SSL certificate verify ok.
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "l7-visibility-tls"
spec:
  description: L7 policy with TLS
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
  - toFQDNs:
    - matchName: "httpbin.org"
    toPorts:
    - ports:
      - port: "443"
        protocol: "TCP"
      terminatingTLS:
        secret:
          namespace: "kube-system"
          name: "httpbin-tls-data"
      originatingTLS:
        secret:
          namespace: "kube-system"
          name: "tls-orig-data"
      rules:
        http:
        - {}
  - toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
          - matchPattern: "*"

Figure 3.6 가로채기 확인

4. Tetragon

✅ Tetragon이란

Tetragon은 Linux Kernel Layer에서 발생하는 이벤트를 탐지 및 통제하는 역할을 하는 도구입니다.

주로 프로세스 실행 이벤트, 시스템 콜 활동, I/O 활동 및 파일 접근과 관련된 이벤트 감지 및 통제를 수행합니다.

Tetragon은 Kernel의 eBPF에 정책과 필터링을 직접 적용하기 때문에 Context 전환 및 WakeUp을 방지하여, 리소스 사용량을 절감합니다.

Figure 4.1 Tetragon설명

Tetragon 설치

helm repo add cilium https://helm.cilium.io
helm repo update
helm install tetragon cilium/tetragon -n kube-system
kubectl rollout status -n kube-system ds/tetragon -w

# Demo Application 배포
kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.18.1/examples/minikube/http-sw-app.yaml

실행 모니터링

Tetragon을 이용하여 시스템의 모든 실행을 추적하는 예제를 실습해보겠습니다.


Figure 4.2 Tetragon 추적 설명

POD=$(kubectl -n kube-system get pods -l 'app.kubernetes.io/name=tetragon' -o name --field-selector spec.nodeName=$(kubectl get pod xwing -o jsonpath='{.spec.nodeName}'))

kubectl exec -ti -n kube-system $POD -c tetragon -- tetra getevents -o compact --pods xwing

# 🚀 process default/xwing /usr/bin/bash -c "curl https://ebpf.io/applications/#tetragon"
# 🚀 process default/xwing /usr/bin/curl https://ebpf.io/applications/#tetragon
# 💥 exit    default/xwing /usr/bin/curl https://ebpf.io/applications/#tetragon 0
# 🚀 process default/xwing /usr/bin/bash -c "curl https://httpbin.org"
# 🚀 process default/xwing /usr/bin/curl https://httpbin.org
# 💥 exit    default/xwing /usr/bin/curl https://httpbin.org 0
# 🚀 process default/xwing /usr/bin/bash -c "cat /etc/passwd"
# 🚀 process default/xwing /usr/bin/cat /etc/passwd
# 💥 exit    default/xwing /usr/bin/cat /etc/passwd 0


# 터미널2에서 xwing pod 시스템 수행
kubectl exec -ti xwing -- bash -c 'curl https://ebpf.io/applications/#tetragon'
kubectl exec -ti xwing -- bash -c 'curl https://httpbin.org'
kubectl exec -ti xwing -- bash -c 'cat /etc/passwd'

파일 접속 모니터링

모니터링 시 추적 정책을 추가해서 실습해보겠습니다.

Figure 4.3 파일 접속 모니터링 개요

kubectl apply -f https://raw.githubusercontent.com/cilium/tetragon/main/examples/quickstart/file_monitoring.yaml

# /etc/passwd 파일 cat 명령어 수행시 추적
kubectl exec -ti xwing -- bash -c 'cat /etc/passwd'
# 📚 read    default/xwing /usr/bin/cat /etc/shadow
# 📚 read    default/xwing /usr/bin/cat /etc/shadow
# 📚 read    default/xwing /usr/bin/cat /etc/shadow
# 📚 read    default/xwing /usr/bin/cat /etc/shadow
# 💥 exit    default/xwing /usr/bin/cat /etc/shadow 0

# 파일쓰기 수행 시 추적
kubectl exec -ti xwing -- bash -c 'echo foo >> /etc/bar'
# 📝 write   default/xwing /usr/bin/bash /etc/bar
# 📝 write   default/xwing /usr/bin/bash /etc/bar
# 💥 exit    default/xwing /usr/bin/bash -c "echo foo >> /etc/bar" 0

파일 액세스 제한 적용

민감한 파일이 읽히지 않도록 적용이 가능하며, 적용 시에는 은 애플리케이션에 작업 블록을 추가 SIGKILL하고 작업 시 오류를 반환합니다.

기존 정책과 다른점은 다음과 같은 블록이 추가되어있다는 점입니다.

      matchActions:
      - action: Sigkill
kubectl apply -f https://raw.githubusercontent.com/cilium/tetragon/main/examples/quickstart/file_monitoring_enforce.yaml

# 민감파일 읽기 작업 수행
kubectl exec -ti xwing -- bash -c 'cat /etc/shadow'

# 🚀 process default/xwing /usr/bin/bash -c "cat /etc/shadow"
# 🚀 process default/xwing /usr/bin/cat /etc/shadow
# 📚 read    default/xwing /usr/bin/cat /etc/shadow
# 📚 read    default/xwing /usr/bin/cat /etc/shadow
# 💥 exit    default/xwing /usr/bin/cat /etc/shadow SIGKILL