[Cilium] Cilium Security & Tetragon
들어가며
이번 포스트에서는 Cilium의 Security와 eBPF 기반의 런타임 보안 및 관찰 도구인 Tetragon 대해 실습을 통하여 알아보도록 하겠습니다.
실습환경 배포
- 실습 환경 소개
- 실습 환경 : k8s(1.33.4), cilium(1.18.1) - 기본 배포 가상 머신 : k8s-ctr, k8s-w1, k8s-w2
- 이번 실습에는 워커 노드간 보안 정책을 적용하고 통신 암호화 등을 살펴보기 위해서 워커노드를 2개 두는 구성을 하였습니다.
- 실습 환경 배포
$ mkdir cilium-lab && cd cilium-lab
$ curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/cilium-study/8w/Vagrantfile
$ vagrant up
# => ...
# ==> k8s-w2: Running provisioner: shell...
# k8s-w2: Running: /var/folders/7k/qy6rsdds57z3tmyn9_7hhd8r0000gn/T/vagrant-shell20250905-16290-5f5n6v.sh
# k8s-w2: >>>> K8S Node config Start <<<<
# k8s-w2: [TASK 1] K8S Controlplane Join
# k8s-w2: >>>> K8S Node config End <<<<
$ vagrant ssh k8s-ctr
Cilium Security 공식문서
Cilium이 제공하는 보안 개요
- 관련문서
- Cilium은 크게 다음의 보안 기능을 제공합니다.
- Identity-Based (Layer3)
- Port Level (Layer4)
- Application protocol Level (Layer7)
-
Cilium 보안 정책의 특징
- 정책이 없는 경우, 기본적으로 모든 트래픽이 허용됩니다.
- 첫번째 정책 규칙이 적용되면, 허용된 트래픽을 제외한 모든 트래픽이 차단됩니다.
- 세션 기반 프로토콜에 대해 상태저장 정책이 적용되며, 응답 패킷은 자동으로 허용됩니다. - 관련문서
-
Envoy Proxy 통합 - Docs
https://docs.cilium.io/en/stable/security/network/proxy/envoy/
- Cilium은 Envoy Proxy와 통합되어 Layer7 보안 정책을 적용할 수 있습니다.
- Envoy Proxy는 L7 프록시로서 HTTP, gRPC, Kafka 등의 프로토콜을 지원합니다.
- Envoy Proxy를 통해 세밀한 애플리케이션 레벨의 보안 정책을 적용할 수 있습니다.
-
투명한 암호화 :
IPSec or WireGuard 로 Cilium 관리하는 호스트 트래픽과 엔드포인트 간 트래픽의 투명한 암호화 지원합니다. - Docs , Youtube
Identity 기반 보안 정책 - Docs
Identity https://docs.cilium.io/en/stable/security/network/identity/
- Cilium은 Pod에 IP주소가 아닌 Identity를 부여하고 Identity를 기반으로 보안 정책을 적용합니다.
- Identity는 Namespace, Labels, ServiceAccount 등을 기반으로 생성됩니다.
- Pod의 IP주소는 변경될 수 있지만, Identity는 변경되기 때문에 보안 정책이 안정적으로 유지될 수 있습니다.
- 또한 동일한 Security Relevant Labels를 가진 Pod들은 동일한 Identity를 공유합니다.
$ kubectl get ciliumendpoints.cilium.io -n kube-system
# => NAME SECURITY IDENTITY ENDPOINT STATE IPV4 IPV6
# coredns-674b8bbfcf-2njqx <span style="color: green;">15719</span> ready 172.20.0.37
# coredns-674b8bbfcf-gwsh5 <span style="color: green;">15719</span> ready 172.20.0.1
# hubble-relay-fdd49b976-t5sms 64548 ready 172.20.0.102
# hubble-ui-655f947f96-vjkrg 3186 ready 172.20.0.197
# metrics-server-5dd7b49d79-rbbwk 883 ready 172.20.0.166
$ kubectl get ciliumidentities.cilium.io
# => NAME NAMESPACE AGE
# <span style="color: green;">15719</span> kube-system 57m
# 2887 cilium-monitoring 57m
# 3186 kube-system 57m
# 38953 local-path-storage 57m
# 61576 cilium-monitoring 57m
# 64548 kube-system 57m
# 883 kube-system 57m
- identity 부여 과정
- Pod 또는 Container가 생성되면, Cilium은 컨테이너 런타밈에서 수집한 이벤트를 기반으로 네트워크에서 Pod 또는 Container를 나타내는 엔드포인트를 생성합니다.
- 엔드포인트의 ID는 엔드포인트에서 파생된 Pod 또는 Container와 연관된 레이블을 기반으로 결정됩니다.
- 다음 단계로 Cilium은 생성된 엔드포인트의 ID를 확인하며, Pod 또는 Container의 레이블이 변경되면 엔드포인트 ID가 재확인 되며 필요에따라 변경됩니다.
Cilium Identity 정보 확인 및 변경 실습
$ kubectl get ciliumidentities.cilium.io 15719 -o yaml | yq
# => {
# "apiVersion": "cilium.io/v2",
# "kind": "CiliumIdentity",
# "metadata": {
# "creationTimestamp": "2025-09-05T14:14:26Z",
# "generation": 1,
# "labels": {
# "io.kubernetes.pod.namespace": "kube-system"
# },
# "name": "15719",
# "resourceVersion": "796",
# "uid": "e3b7045e-5a91-47fc-abff-0d8f86330dbf"
# },
# "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"
# }
# }
$ kubectl exec -it -n kube-system ds/cilium -- cilium identity list
# => ...
# 15719 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
# ...
$ kubectl get pod -n kube-system -l k8s-app=kube-dns --show-labels
# => NAME READY STATUS RESTARTS AGE LABELS
# coredns-674b8bbfcf-2njqx 1/1 Running 0 65m k8s-app=kube-dns,pod-template-hash=674b8bbfcf
# coredns-674b8bbfcf-gwsh5 1/1 Running 0 65m k8s-app=kube-dns,pod-template-hash=674b8bbfcf
# 기동 중인 파드에 label 추가 후 cilium id(보안 label)에 반영 될지? : 반영되는가? 얼마나 시간이 걸리는가? ID가 변경되지는 않는가?
$ kubectl label pods -n kube-system -l k8s-app=kube-dns study=8w
# => pod/coredns-674b8bbfcf-2njqx labeled
# pod/coredns-674b8bbfcf-gwsh5 labeled
$ kubectl exec -it -n kube-system ds/cilium -- cilium identity list
# => ...
# 15719 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
# <span style="color: green;">38787 k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=kube-system</span>
# <span style="color: green;">k8s:io.cilium.k8s.policy.cluster=default</span>
# <span style="color: green;">k8s:io.cilium.k8s.policy.serviceaccount=coredns</span>
# <span style="color: green;">k8s:io.kubernetes.pod.namespace=kube-system</span>
# <span style="color: green;">k8s:k8s-app=kube-dns</span>
# <span style="color: green;">k8s:study=8w</span>
# ...
# <span style="color: green;">👉 study=8w 라벨에 대한 새로운 ID(38787)가 생성되었습니다..</span>
# (위 내용 확인 후 아래 진행) coredns deployment 에 spec.template.metadata.labels에 아래 추가 시 반영 확인
$ kubectl edit deploy -n kube-system coredns
---
...
template:
metadata:
labels:
app: testing
k8s-app: kube-dns
...
---
# => deployment.apps/coredns edited
$ kubectl exec -it -n kube-system ds/cilium -- cilium identity list
# => ...
# <span style="color: green;">30461 k8s:app=testing</span>
# <span style="color: green;">k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=kube-system</span>
# <span style="color: green;">k8s:io.cilium.k8s.policy.cluster=default</span>
# <span style="color: green;">k8s:io.cilium.k8s.policy.serviceaccount=coredns</span>
# <span style="color: green;">k8s:io.kubernetes.pod.namespace=kube-system</span>
# <span style="color: green;">k8s:k8s-app=kube-dns</span>
# ...
- Cilium은 pod update 이벤트를 watch하므로, labels 변경 시 endpoint가 waiting-for-identity 상태로 전환되어 새로운 identity를 할당받습니다. 이로 인해 security labels와 관련된 네트워크 정책도 자동으로 재적용됩니다.
- 예시:
- 간단한 pod(simple-pod)를 생성하면 초기 security identity(예: 26830)가 할당됩니다.
- 이후
kubectl label pod/simple-pod run=not-simple-pod --overwrite로 labels를 변경하면,kubectl get ciliumendpoints명령에서 identity가 새 값(예: 8710)으로 업데이트된 것을 확인할 수 있습니다. 이는 policy enforcement에 즉시 반영됩니다.
- 단, 대규모 클러스터에서 자주 labels를 변경하면 identity 할당이 빈번해져 성능 저하가 발생할 수 있으므로, identity-relevant labels를 제한하는 것이 권장됩니다
Special Identities
- Cilium에서 관리하는 모든 엔드포인트는 ID가 할당 됩니다.
- Cilium에서 관리하지 않는 엔드포인트의 통신을 허용하기 위해서 특수 ID를 제공합니다.
- 예약된 특수 ID에는
reserved라는 접두사가 붙습니다.
# 예약된 특수 ID 확인
$ 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
# 7 reserved:kube-apiserver
# reserved:remote-node
# 8 reserved:ingress
# 9 reserved:world-ipv4
# 10 reserved:world-ipv6
# ...
| Identity | 숫자ID | 설명 |
|---|---|---|
reserved:unknown |
0 | ID을 확인할 수 없습니다. |
reserved:host |
1 | 로컬 호스트. 로컬 호스트 IP 중 하나에서 시작되거나 지정된 모든 트래픽입니다. |
reserved:world |
2 | 클러스터 외부의 모든 네트워크 엔드포인트 |
reserved:unmanaged |
3 | Cilium에서 관리하지 않는 엔드포인트, 예를 들어 Cilium이 설치되기 전에 시작된 Kubernetes Pod입니다. |
reserved:health |
4 | Cilium 에이전트가 생성한 상태 확인 트래픽입니다. |
reserved:init |
5 | ID가 아직 확인되지 않은 엔드포인트에는 init ID가 할당됩니다. 이는 보안 ID를 도출하는 데 필요한 일부 메타데이터가 아직 없는 엔드포인트 단계를 나타냅니다. 이는 일반적으로 부트스트래핑 단계에서 발생합니다. init ID는 엔드포인트 생성 시점에 레이블을 알 수 없는 경우에만 할당됩니다. Docker 플러그인의 경우가 여기에 해당할 수 있습니다. |
reserved:remote-node |
6 | 모든 원격 클러스터 호스트의 집합입니다. 로컬 노드를 제외한 연결된 클러스터 내 모든 호스트의 IP 중 하나에서 시작되거나 해당 IP로 지정된 모든 트래픽입니다. |
reserved:kube-apiserver |
7 | kube-apiserver를 실행하는 백엔드가 있는 원격 노드입니다. |
reserved:ingress |
8 | Ingress 프록시 연결에 대한 소스 주소로 사용되는 IP에 제공됩니다. |
클러스터에서의 Identity 관리

- ID는 전체 클러스터에서 유효합니다.
- 즉, 여러 클러스터 노드에서 여러 개의 포드 또는 컨테이너가 시작되더라도 ID 관련 레이블을 공유하는 경우 모든 포드 또는 컨테이너가 단일 ID를 공유합니다.
- 이를 위해서는 클러스터 노드 간의 조정이 필요합니다.
- 엔드포인트 ID를 확인하는 작업은 분산 키-값 저장소를 통해 수행됩니다.
- 분산 키-값 저장소는 다음 값이 이전에 확인되지 않은 경우 새로운 고유 식별자를 생성하는 형태의 원자적 연산을 수행할 수 있도록 합니다.
- 이를 통해 각 클러스터 노드는 ID 관련 레이블 하위 집합을 생성한 다음 키-값 저장소를 쿼리하여 ID를 도출할 수 있습니다.
- 레이블 집합이 이전에 쿼리되었는지 여부에 따라 새 ID가 생성되거나 초기 쿼리의 ID가 반환됩니다.
NetworkPolicy : 3가지 형식 제공 - Docs
https://isovalent.com/blog/post/intro-to-cilium-network-policies/
- 표준 Network Policy : Pod의 ingress 또는 egress 시 L3 및 L4 정책을 지원하는 표준 NetworkPolicy 리소스입니다.
- 확장 CiliumNetworkPolicy : 3~7 계층에서 수신 및 송신 모두에 대한 정책 지정을 지원하는 CustomResourceDefinition 으로 제공되는 확장된 CiliumNetworkPolicy 형식입니다.
- CiliumClusterwideNetworkPolicy : CiliumClusterwideNetworkPolicy 형식 은 Cilium에서 적용할 클러스터 전체 정책을 지정하는 클러스터 범위 CustomResourceDefinition 입니다. 이 사양은 네임스페이스가 지정되지 않은 CiliumNetworkPolicy 와 동일합니다 .
Policy Enforcement Modes by Cilium Network Policy - Docs
-
Policy Enforcement Modes
- default : 정책이 없는 경우 모든 트래픽이 허용됩니다. 정책이 적용되면 허용된 트래픽을 제외한 모든 트래픽이 차단됩니다.
- always : 정책이 없는 경우 모든 트래픽이 차단됩니다. 정책이 적용되면 허용된 트래픽을 제외한 모든 트래픽이 차단됩니다.
- never : 정책이 없는 경우 모든 트래픽이 허용됩니다. 정책이 적용되더라도 모든 트래픽이 허용됩니다.
-
Endpoint default policy
-
기본적으로 모든 엔드포인트에 대해 모든 송신 및 수신 트래픽이 허용됩니다. 네트워크 정책에 따라 엔드포인트가 선택되면 명시적으로 허용된 트래픽 만 허용되는 기본 거부 상태로 전환됩니다 . 이 상태는 방향별로 적용됩니다.
- 규칙이 엔드포인트를 선택 하고 해당 규칙에 수신 섹션이 있는 경우, 엔드포인트는 수신에 대해 기본 거부 모드로 전환됩니다.
- 규칙이 엔드포인트를 선택 하고 규칙에 송신 섹션이 있는 경우, 엔드포인트는 송신에 대해 기본 거부 모드로 전환됩니다.
-
EnableDefaultDeny7계층 정책 에는 적용되지 않습니다.- 7계층 모두 허용이 포함되지 않은 7계층 규칙을 추가하면 default-deny가 명시적으로 비활성화된 경우에도 삭제가 발생합니다.
-
기본적으로 모든 엔드포인트에 대해 모든 송신 및 수신 트래픽이 허용됩니다. 네트워크 정책에 따라 엔드포인트가 선택되면 명시적으로 허용된 트래픽 만 허용되는 기본 거부 상태로 전환됩니다 . 이 상태는 방향별로 적용됩니다.
-
Rule Basics
- endpointSelector / nodeSelector : 정책 규칙이 적용될 엔드포인트 또는 노드를 선택합니다. 정책 규칙은 선택기에 지정된 레이블과 일치하는 모든 엔드포인트에 적용됩니다.
- ingress : 엔드포인트의 유입 시, 즉 엔드포인트에 들어오는 모든 네트워크 패킷에 적용해야 하는 규칙 목록입니다.
- egress : 엔드포인트의 출구에서 적용되어야 하는 규칙 목록, 즉 엔드포인트를 떠나는 모든 네트워크 패킷에 적용되어야 하는 규칙 목록입니다.
-
labels :
레이블은 규칙을 식별하는 데 사용됩니다. 레이블을 사용하여 규칙을 나열하고 삭제할 수 있습니다. Kubernetes를
통해 가져온 정책 규칙에는 NetworkPolicy
또는 CiliumNetworkPolicy 리소스에
지정된 이름에 해당하는 레이블이 자동으로
io.cilium.k8s.policy.name=NAME지정됩니다.
Cilium Endpoint Lifecycle - Docs
https://docs.cilium.io/en/stable/security/policy/lifecycle/
- restoring : Cilium이 시작되기 전에 엔드포인트가 시작되었으며 Cilium이 네트워킹 구성을 복원하고 있습니다.
- waiting-for-identity : Cilium이 엔드포인트에 고유한 ID를 할당하고 있습니다.
- waiting-to-regenerate : 엔드포인트가 ID를 수신했으며 네트워킹 구성이 다시 생성될 때까지 기다리고 있습니다.
- regenerating : 엔드포인트의 네트워킹 구성이 재생성되고 있습니다. 여기에는 해당 엔드포인트에 대한 eBPF 프로그래밍이 포함됩니다.
- ready : 엔드포인트의 네트워킹 구성이 성공적으로 다시 생성되었습니다.
- disconnecting : 엔드포인트가 삭제되고 있습니다.
- disconnected : 엔드포인트가 삭제되었습니다.
기타 정보
- 정책 에디터 온라인 사이트 : https://editor.networkpolicy.io/, https://isovalent.com/blog/post/tutorial-network-policy-editor/
- Identity Management Mode : Docs
- Using Kubernetes Constructs In Policy : 정책에서 쿠버네티스 리소스를 활용 - Docs
- Network Policy Troubleshooting - Docs & Caveats - Docs
- Threat Model : Docs
- Tutorial: Cilium Network Policy in Practice (Part 2) : Blog
Cilium Security 실습
🔬 Lab1. Locking Down External Access with DNS-Based Policies
- DNS 기반 보안 정책 - Docs
- CIDR 또는 IP 기반 정책은 외부 서비스와 연결된 IP가 자주 변경될 수 있으므로 관리가 어렵고 번거롭습니다.
- Cilium의 DNS 기반 정책은 DNS-IP 매핑 추적과 같은 복잡한 측면을 관리하는 동시에 액세스 제어를 쉽게 지정할 수 있는 메커니즘을 제공합니다.
- 이 가이드에서는 다음 사항에 대해 알아봅니다.
- DNS 기반 정책을 사용하여 클러스터 외부 서비스에 대한 이탈 액세스 제어
- 패턴(또는 와일드카드)을 사용하여 DNS 도메인 하위 집합을 허용 목록에 추가
- 외부 서비스 접근 제한을 위한 DNS, 포트 및 L7 규칙 결합
데모 애플리케이션 배포
- Empire의 mediabot pod가 Empire의 git 저장소 관리를 위해 GitHub에 접근해야 하는 간단한 시나리오를 사용하겠습니다.
- pod는 다른 외부 서비스에 접근할 수 없어야 합니다.
# hubble ui 에 pod name 표기를 위해 app labels 추가 >> 빼고 배포해보고 차이점을 확인해보자.
$ 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
# => pod/mediabot created
$ kubectl wait pod/mediabot --for=condition=Ready
# => pod/mediabot condition met
# 확인
$ kubectl exec -it -n kube-system ds/cilium -- cilium identity list
# => ...
# 23762 k8s:app=mediabot
# k8s:class=mediabot
# 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:org=empire
# ...
$ kubectl get pods
# => NAME READY STATUS RESTARTS AGE
# mediabot 1/1 Running 0 86s
# 외부 통신 확인 : hubble ui 에서 확인
$ kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
# => HTTP/2 200
$ kubectl exec mediabot -- curl -I -s --max-time 5 https://support.github.com | head -1
# => HTTP/2 302

DNS Egress 정책 적용 1 : mediabot포드가 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
# => ciliumnetworkpolicy.cilium.io/fqdn created
# 확인
$ kubectl get cnp
# => NAME AGE VALID
# fqdn 11s True
$ kubectl exec -it -n kube-system ds/cilium -- cilium policy selectors
# => SELECTOR LABELS USERS IDENTITIES
# &LabelSelector{MatchLabels:map[string]string{any.class: mediabot,any.org: empire,k8s.io.kubernetes.pod.namespace: default,},MatchExpressions:[]LabelSelectorRequirement{},} default/fqdn 1 23762
$ 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
# tofqdns-dns-reject-response-code refused
# tofqdns-enable-dns-compression true
# tofqdns-endpoint-max-ip-per-hostname 1000
# tofqdns-idle-connection-grace-period 0s
# tofqdns-max-deferred-connection-deletes 10000
# tofqdns-preallocate-identities true
# tofqdns-proxy-response-max-delay 100ms
# 외부 통신 확인 : hubble ui 에서 확인
$ kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
# => HTTP/2 200
$ kubectl exec mediabot -- curl -I -s --max-time 5 https://support.github.com | head -1
api.github.com으로만 접속이 허용된 상태
# cilium-agent 내에 go 로 구현된 lightweight proxy 가 DNS 쿼리/응답 감시와 ~~캐싱 처리~~ : 기본 30초?
$ cilium hubble port-forward&
# => ℹ️ Hubble Relay is available at 127.0.0.1:4245
$ hubble observe --pod mediabot
# => ...
# Sep 6 07:57:02.807: default/mediabot:59702 (ID:23762) <- kube-system/coredns-674b8bbfcf-hcdsq:53 (ID:41039) dns-response proxy FORWARDED (DNS Answer "20.200.245.245" TTL: 30 (Proxy api.github.com. A))
# ...
# Sep 6 07:57:02.809: default/mediabot:59458 (ID:23762) -> api.github.com:443 (ID:16777217) policy-verdict:L3-Only EGRESS ALLOWED (TCP Flags: SYN)
# <span style="color: green;">👉 api.github.com 으로의 접속은 허용됨</span>
# ...
# Sep 6 07:48:26.004: default/mediabot:43181 (ID:23762) <- kube-system/coredns-674b8bbfcf-nxkpb:53 (ID:41039) dns-response proxy FORWARDED (DNS Answer TTL: 4294967295 (Proxy support.github.com. AAAA))
# ...
# Sep 6 07:48:26.025: default/mediabot:34138 (ID:23762) <> support.github.com:443 (world) policy-verdict:none EGRESS DENIED (TCP Flags: SYN)
# <span style="color: red;">👉 support.github.com 으로의 접속은 차단됨</span>
# ...
# (옵션) coredns 로그로 직접 확인해보기
# k9s -> configmap (coredns) : log 추가
## 로깅 활성화
$ kubectl logs -n kube-system -l k8s-app=kube-dns -f
# => [INFO] 172.20.1.42:46910 - 17176 "A IN api.github.com.default.svc.cluster.local. udp 58 false 512" NXDOMAIN qr,aa,rd 151 0.001380815s
# [INFO] 172.20.1.42:46910 - 10271 "AAAA IN api.github.com.default.svc.cluster.local. udp 58 false 512" NXDOMAIN qr,aa,rd 151 0.000691012s
# [INFO] 172.20.1.42:59040 - 34206 "A IN api.github.com.svc.cluster.local. udp 50 false 512" NXDOMAIN qr,aa,rd 143 0.001186301s
# [INFO] 172.20.1.42:59040 - 38304 "AAAA IN api.github.com.svc.cluster.local. udp 50 false 512" NXDOMAIN qr,aa,rd 143 0.001266842s
# [INFO] 172.20.1.42:47633 - 16595 "AAAA IN api.github.com.cluster.local. udp 46 false 512" NXDOMAIN qr,aa,rd 139 0.007288392s
# [INFO] 172.20.1.42:47633 - 42706 "A IN api.github.com.cluster.local. udp 46 false 512" NXDOMAIN qr,aa,rd 139 0.032957929s
# [INFO] 172.20.1.42:48160 - 48028 "AAAA IN api.github.com. udp 32 false 512" NOERROR qr,rd,ra 116 0.046812603s
# [INFO] 172.20.1.42:48160 - 49818 "A IN api.github.com. udp 32 false 512" NOERROR qr,rd,ra 62 0.043482105s
# ...
## 호출
$ kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
# => HTTP/2 200
$ kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
# => HTTP/2 200
$ hubble observe --pod mediabot
# => Sep 6 08:01:46.195: default/mediabot:39968 (ID:23762) -> kube-system/coredns-6c95f59f49-fgbmq:53 (ID:41039) to-endpoint FORWARDED (UDP)
# Sep 6 08:01:46.197: default/mediabot:39968 (ID:23762) <> kube-system/coredns-6c95f59f49-fgbmq (ID:41039) pre-xlate-rev TRACED (UDP)
# Sep 6 08:01:46.198: default/mediabot:39968 (ID:23762) <- kube-system/coredns-6c95f59f49-fgbmq:53 (ID:41039) to-network FORWARDED (UDP)
# Sep 6 08:01:46.198: default/mediabot:39968 (ID:23762) <> kube-system/coredns-6c95f59f49-fgbmq (ID:41039) pre-xlate-rev TRACED (UDP)
# Sep 6 08:01:46.228: default/mediabot:49139 (ID:23762) -> kube-system/coredns-6c95f59f49-fgbmq:53 (ID:41039) to-endpoint FORWARDED (UDP)
# Sep 6 08:01:46.230: default/mediabot:49139 (ID:23762) <> kube-system/coredns-6c95f59f49-fgbmq (ID:41039) pre-xlate-rev TRACED (UDP)
# Sep 6 08:01:46.238: default/mediabot:49139 (ID:23762) <> kube-system/coredns-6c95f59f49-fgbmq (ID:41039) pre-xlate-rev TRACED (UDP)
# Sep 6 08:01:46.238: default/mediabot:49139 (ID:23762) <- kube-system/coredns-6c95f59f49-fgbmq:53 (ID:41039) to-network FORWARDED (UDP)
# Sep 6 08:01:46.409: default/mediabot:49139 (ID:23762) <- kube-system/coredns-6c95f59f49-fgbmq:53 (ID:41039) dns-response proxy FORWARDED (DNS Answer TTL: 4294967295 (Proxy api.github.com. AAAA))
# Sep 6 08:01:46.410: default/mediabot:49139 (ID:23762) <- kube-system/coredns-6c95f59f49-fgbmq:53 (ID:41039) dns-response proxy FORWARDED (DNS Answer "20.200.245.245" TTL: 1 (Proxy api.github.com. A))
# Sep 6 08:01:46.410: kube-system/coredns-6c95f59f49-fgbmq:53 (ID:41039) <> default/mediabot (ID:23762) pre-xlate-rev TRACED (UDP)
# Sep 6 08:01:46.410: kube-system/kube-dns:53 (world) <> default/mediabot (ID:23762) post-xlate-rev TRANSLATED (UDP)
# Sep 6 08:01:46.410: kube-system/coredns-6c95f59f49-fgbmq:53 (ID:41039) <> default/mediabot (ID:23762) pre-xlate-rev TRACED (UDP)
# Sep 6 08:01:46.410: kube-system/kube-dns:53 (world) <> default/mediabot (ID:23762) post-xlate-rev TRANSLATED (UDP)
# Sep 6 08:01:46.412: default/mediabot:46244 (ID:23762) -> api.github.com:443 (ID:16777217) policy-verdict:L3-Only EGRESS ALLOWED (TCP Flags: SYN)
# Sep 6 08:01:46.412: default/mediabot:46244 (ID:23762) -> api.github.com:443 (ID:16777217) to-network FORWARDED (TCP Flags: SYN)
# Sep 6 08:01:46.421: default/mediabot:46244 (ID:23762) <- api.github.com:443 (ID:16777217) to-endpoint FORWARDED (TCP Flags: SYN, ACK)
# Sep 6 08:01:46.422: api.github.com:443 (ID:16777217) <> default/mediabot (ID:23762) pre-xlate-rev TRACED (TCP)
# Sep 6 08:01:46.422: default/mediabot:46244 (ID:23762) -> api.github.com:443 (ID:16777217) to-network FORWARDED (TCP Flags: ACK)
# Sep 6 08:01:46.424: default/mediabot:46244 (ID:23762) -> api.github.com:443 (ID:16777217) to-network FORWARDED (TCP Flags: ACK, PSH)
# Sep 6 08:01:46.433: default/mediabot:46244 (ID:23762) <- api.github.com:443 (ID:16777217) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
# Sep 6 08:01:46.464: api.github.com:443 (ID:16777217) <> default/mediabot (ID:23762) pre-xlate-rev TRACED (TCP)
# Sep 6 08:01:46.465: api.github.com:443 (ID:16777217) <> default/mediabot (ID:23762) pre-xlate-rev TRACED (TCP)
# Sep 6 08:01:46.685: default/mediabot:46244 (ID:23762) -> api.github.com:443 (ID:16777217) to-network FORWARDED (TCP Flags: ACK, FIN)
# Sep 6 08:01:46.703: default/mediabot:46244 (ID:23762) <- api.github.com:443 (ID:16777217) to-endpoint FORWARDED (TCP Flags: ACK, FIN)
# Sep 6 08:01:46.703: default/mediabot:46244 (ID:23762) -> api.github.com:443 (ID:16777217) to-network FORWARDED (TCP Flags: RST)
# Sep 6 08:01:46.703: default/mediabot:46244 (ID:23762) -> api.github.com:443 (ID:16777217) to-network FORWARDED (TCP Flags: RST)
# Sep 6 08:01:46.705: default/mediabot:46244 (ID:23762) <- api.github.com:443 (ID:16777217) to-endpoint FORWARDED (TCP Flags: ACK, RST)
## 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
# => cilium-p8r4t cilium-vrh7l cilium-rmd6z
## 단축키(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"
##
$ c0 fqdn cache list
# => Endpoint Source FQDN TTL ExpirationTime IPs
$ c1 fqdn cache list
# => Endpoint Source FQDN TTL ExpirationTime IPs
# 1039 connection support.github.com. 0 2025-09-06T08:26:39.259Z 185.199.108.133
# 1039 connection support.github.com. 0 2025-09-06T08:26:39.259Z 185.199.110.133
# 1039 connection support.github.com. 0 2025-09-06T08:26:39.259Z 185.199.109.133
# 1039 connection support.github.com. 0 2025-09-06T08:26:39.259Z 185.199.111.133
# 1039 connection api.github.com. 0 2025-09-06T08:26:39.259Z 20.200.245.245
$ c2 fqdn cache list
# => Endpoint Source FQDN TTL ExpirationTime IPs
$ c0 fqdn names
# => {
# "DNSPollNames": null,
# "FQDNPolicySelectors": []
# }
$ c1 fqdn names
# => {
# "DNSPollNames": null,
# "FQDNPolicySelectors": [
# {
# "regexString": "^api[.]github[.]com[.]$",
# "selectorString": "MatchName: api.github.com, MatchPattern: "
# }
# ]
# }
$ c2 fqdn names
# => {
# "DNSPollNames": null,
# "FQDNPolicySelectors": []
# }
- cilium-agent 내에 go 로 구현된 lightweight proxy 가 DNS 쿼리/응답 감시와
캐싱 처리- Code
DNS Egress 정책 적용 2 : 모든 GitHub 하위 도메인(예: 패턴)에 액세스.
# fqdn 캐시 초기화 및 정책 삭제
$ kubectl delete cnp fqdn
# => ciliumnetworkpolicy.cilium.io "fqdn" deleted
$ c1 fqdn cache clean -f
# => FQDN proxy cache cleared
$ c2 fqdn cache clean -f
# => FQDN proxy cache cleared
# dns-pattern.yaml 내용
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: "*"
$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-dns/dns-pattern.yaml
# => ciliumnetworkpolicy.cilium.io/fqdn created
$ c1 fqdn names
# => {
# "DNSPollNames": null,
# "FQDNPolicySelectors": [
# {
# "regexString": "^[-a-zA-Z0-9_]*[.]github[.]com[.]$",
# "selectorString": "MatchName: , MatchPattern: *.github.com"
# }
# ]
# }
$ c2 fqdn names
# => {
# "DNSPollNames": null,
# "FQDNPolicySelectors": []
# }
$ c1 fqdn cache list
# => Endpoint Source FQDN TTL ExpirationTime IPs
$ c2 fqdn cache list
# => Endpoint Source FQDN TTL ExpirationTime IPs
# 확인
$ kubectl get cnp
# => NAME AGE VALID
# fqdn 59s True
# 외부 통신 확인 : hubble ui 에서 확인 >> github.com 은 공식 문서 설명대로라면 안되야됨..
## It is important to note and test that this doesn’t allow access to github.com because the *.
## in the pattern requires one subdomain to be present in the DNS name
$ kubectl exec mediabot -- curl -I -s https://support.github.com | head -1
# => HTTP/2 302
$ kubectl exec mediabot -- curl -I -s https://gist.github.com | head -1
# => HTTP/2 302
$ kubectl exec mediabot -- curl -I -s --max-time 5 https://github.com | head -1
# => HTTP/2 200
$ kubectl exec mediabot -- curl -I -s --max-time 5 https://cilium.io| head -1
# => command terminated with exit code 28
# <span style="color: green;">👉 *.github.com 으로의 접속만 허용되기 때문에 cilium.io 접속은 차단됨</span>

DNS Egress 정책 적용 3 : DNS, Port 조합 적용
# dns-port.yaml 내용
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: "*"
$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-dns/dns-port.yaml
# => ciliumnetworkpolicy.cilium.io/fqdn configured
$ c1 fqdn names
# => {
# "DNSPollNames": null,
# "FQDNPolicySelectors": [
# {
# "regexString": "^[-a-zA-Z0-9_]*[.]github[.]com[.]$",
# "selectorString": "MatchName: , MatchPattern: *.github.com"
# }
# ]
# }
$ c2 fqdn names
# => {
# "DNSPollNames": null,
# "FQDNPolicySelectors": []
# }
$ c1 fqdn cache list
# => Endpoint Source FQDN TTL ExpirationTime IPs
# 1039 connection support.github.com. 0 2025-09-06T08:26:39.259Z 185.199.110.133
# 1039 connection support.github.com. 0 2025-09-06T08:26:39.259Z 185.199.109.133
# 1039 connection support.github.com. 0 2025-09-06T08:26:39.259Z 185.199.111.133
# 1039 connection gist.github.com. 0 2025-09-06T08:26:39.259Z 20.200.245.247
# 1039 connection github.com. 0 2025-09-06T08:26:39.259Z 20.200.245.247
# 1039 connection cilium.io. 0 2025-09-06T08:26:39.259Z 104.198.14.52
# 1039 connection support.github.com. 0 2025-09-06T08:26:39.259Z 185.199.108.133
$ c2 fqdn cache list
# => Endpoint Source FQDN TTL ExpirationTime IPs
$ c1 fqdn cache clean -f
# => FQDN proxy cache cleared
$ c2 fqdn cache clean -f
# => FQDN proxy cache cleared
# 외부 통신 확인 : hubble ui 에서 확인
$ 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
*.github.com의 443 포트만 허용되는 상태
실습 리소스 삭제
#
$ kubectl delete -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-dns/dns-sw-app.yaml
# => pod "mediabot" deleted
$ kubectl delete cnp fqdn
# => ciliumnetworkpolicy.cilium.io "fqdn" deleted
샘플 애플리케이션 배포 및 통신 문제 확인
- 샘플 애플리케이션 배포
# 샘플 애플리케이션 배포
$ 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
# => deployment.apps/webpod created
# service/webpod created
# 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
# => pod/curl-pod created
- 통신 확인
# 배포 확인
$ kubectl get deploy,svc,ep webpod -owide
# => NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
# deployment.apps/webpod 2/2 2 2 47s webpod traefik/whoami app=webpod
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
# service/webpod ClusterIP 10.96.194.188 <none> 80/TCP 47s app=webpod
#
# NAME ENDPOINTS AGE
# endpoints/webpod 172.20.1.252:80,172.20.2.124:80 47s
$ kubectl get endpointslices -l app=webpod
# => NAME ADDRESSTYPE PORTS ENDPOINTS AGE
# webpod-mr2xg IPv4 80 172.20.1.252,172.20.2.124 59s
$ kubectl get ciliumendpoints # IP 확인
# => NAME SECURITY IDENTITY ENDPOINT STATE IPV4 IPV6
# curl-pod 1846 ready 172.20.0.190
# webpod-697b545f57-p9hhs 4449 ready 172.20.2.124
# webpod-697b545f57-rcgf8 4449 ready 172.20.1.252
# 통신 문제 확인
$ kubectl exec -it curl-pod -- curl -s --connect-timeout 1 webpod | grep Hostname
# => Hostname: webpod-697b545f57-rcgf8
$ kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
# => Hostname: webpod-697b545f57-rcgf8
# ---
# Hostname: webpod-697b545f57-rcgf8
# ---
# Hostname: webpod-697b545f57-rcgf8
# ---
# Hostname: webpod-697b545f57-p9hhs
# ...
# cilium-dbg, map
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg ip list
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg endpoint list
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg service list
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg bpf lb list
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg bpf nat list
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg map list | grep -v '0 0'
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg map get cilium_lb4_services_v2
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg map get cilium_lb4_backends_v3
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg map get cilium_lb4_reverse_nat
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg map get cilium_ipcache_v2
Transparent Encryption with WireGuard
- 각 노드는 자체 암호화 키 쌍을 자동으로 생성하고 Kubernetes CiliumNode 커스텀 리소스 객체의
io.cilium.network.wg-pub-key어노테이션을 통해 공개 키를 배포합니다. - 각 노드의 공개키는 해당 노드에서 실행 중인 Cilium 관리 엔드포인트의 트래픽을 암호화 및 암호 해독하는 데 다른 노드에서 사용됩니다.
- 한 노드 내에서 파드(엔드포인트)간 통신 시에는 암호화 되지 않습니다.
- WireGuard 터널 엔드포인트는 각 노드의 UDP 포트 51871에서 노출됩니다.
- 제한 사항 : L7 정책 시행 및 가시성, eBPF 기반 호스트 라우팅

Wireguard를 이용한 암호화
🔬 Lab2. WireGuard 설정 및 실습
# [커널 구성 옵션] CONFIG_WIREGUARD=m on Linux 5.6 and newe
$ uname -ar
# => Linux k8s-ctr 6.8.0-71-generic #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:44:45 UTC 2025 aarch64 aarch64 aarch64 GNU/Linux
$ grep -E 'CONFIG_WIREGUARD=m' /boot/config-$(uname -r)
# => CONFIG_WIREGUARD=m
# 설정 전 기본 정보 확인
$ ip -c addr
$ ip -c route
$ ip rule show
# 설정
$ helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
--set encryption.enabled=true --set encryption.type=wireguard
# => Release "cilium" has been upgraded. Happy Helming!
# ...
$ kubectl -n kube-system rollout restart ds/cilium
# => daemonset.apps/cilium restarted
# 확인
$ cilium config view | grep -i wireguard
# => enable-wireguard true
# wireguard-persistent-keepalive 0s
$ kubectl exec -it -n kube-system ds/cilium -- cilium encrypt status
# => Encryption: Wireguard
# Interface: cilium_wg0
# Public key: chlVHrcv7vafkretDiAyIn0+1bU4xVDzxhSxEUBx9lw=
# Number of peers: 2
$ kubectl exec -it -n kube-system ds/cilium -- cilium status | grep Encryption
# => Encryption: Wireguard [NodeEncryption: Disabled, cilium_wg0 (Pubkey: chlVHrcv7vafkretDiAyIn0+1bU4xVDzxhSxEUBx9lw=, Port: 51871, Peers: 2)]
$ kubectl exec -it -n kube-system ds/cilium -- cilium debuginfo --output json
$ kubectl exec -it -n kube-system ds/cilium -- cilium debuginfo --output json | jq .encryption
# => {
# "wireguard": {
# "interfaces": [
# {
# "listen-port": 51871,
# "name": "cilium_wg0",
# "peer-count": 2,
# "peers": [
# {
# "allowed-ips": [
# "172.20.0.0/24",
# "192.168.10.100/32",
# "172.20.0.103/32",
# "172.20.0.93/32",
# "172.20.0.36/32",
# "172.20.0.5/32",
# "172.20.0.168/32",
# "172.20.0.27/32",
# "172.20.0.190/32"
# ],
# "endpoint": "192.168.10.100:51871",
# "last-handshake-time": "0001-01-01T00:00:00.000Z",
# "public-key": "9Pva2PmR3ETq7N/GVRHD4IsmmSYEoJTEFzXg7Qe/FGo="
# },
# {
# "allowed-ips": [
# "192.168.10.101/32",
# "172.20.1.93/32",
# "172.20.1.252/32",
# "172.20.1.0/24",
# "172.20.1.105/32",
# "172.20.1.6/32"
# ],
# "endpoint": "192.168.10.101:51871",
# "last-handshake-time": "0001-01-01T00:00:00.000Z",
# "public-key": "ZkvKJ95K9Bo6xE6DnkFfRPUHNCsuRAnIejCP7OqObB8="
# }
# ],
# "public-key": "chlVHrcv7vafkretDiAyIn0+1bU4xVDzxhSxEUBx9lw="
# }
# ],
# "node-encryption": "Disabled"
# }
# }
$ 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
$ ip rule show
# => 9: from all fwmark 0x200/0xf00 lookup 2004
# 10: from all fwmark 0xa00/0xf00 lookup 2005
# 100: from all lookup local
# 32766: from all lookup main
# 32767: from all lookup default
# wg 정보 확인
$ wg -h
$ wg show
# => interface: cilium_wg0
# public key: 9Pva2PmR3ETq7N/GVRHD4IsmmSYEoJTEFzXg7Qe/FGo=
# private key: (hidden)
# listening port: 51871
# fwmark: 0xe00
#
# peer: chlVHrcv7vafkretDiAyIn0+1bU4xVDzxhSxEUBx9lw=
# endpoint: 192.168.10.102:51871
# allowed ips: 192.168.10.102/32, 172.20.2.243/32, 172.20.2.226/32, 172.20.2.125/32, 172.20.2.124/32, 172.20.2.140/32, 172.20.2.0/24, 172.20.2.171/32
#
# peer: ZkvKJ95K9Bo6xE6DnkFfRPUHNCsuRAnIejCP7OqObB8=
# endpoint: 192.168.10.101:51871
# allowed ips: 172.20.1.0/24, 172.20.1.105/32, 172.20.1.93/32, 172.20.1.6/32, 192.168.10.101/32, 172.20.1.252/32
$ wg show all public-key
# => cilium_wg0 9Pva2PmR3ETq7N/GVRHD4IsmmSYEoJTEFzXg7Qe/FGo=
$ wg show all private-key
$ wg show all preshared-keys
$ wg show all endpoints
# => cilium_wg0 chlVHrcv7vafkretDiAyIn0+1bU4xVDzxhSxEUBx9lw= 192.168.10.102:51871
# ZkvKJ95K9Bo6xE6DnkFfRPUHNCsuRAnIejCP7OqObB8= 192.168.10.101:51871
$ wg show all transfer
# => cilium_wg0 chlVHrcv7vafkretDiAyIn0+1bU4xVDzxhSxEUBx9lw= 0 0
# cilium_wg0 ZkvKJ95K9Bo6xE6DnkFfRPUHNCsuRAnIejCP7OqObB8= 0 0
# 퍼블릭 키 확인
$ kubectl get cn -o yaml | grep annotations -A1
# => annotations:
# network.cilium.io/wg-pub-key: 9Pva2PmR3ETq7N/GVRHD4IsmmSYEoJTEFzXg7Qe/FGo=
# --
# annotations:
# network.cilium.io/wg-pub-key: ZkvKJ95K9Bo6xE6DnkFfRPUHNCsuRAnIejCP7OqObB8=
# --
# annotations:
# network.cilium.io/wg-pub-key: chlVHrcv7vafkretDiAyIn0+1bU4xVDzxhSxEUBx9lw=
- 통신 확인
# curl 호출
$ kubectl exec -it curl-pod -- curl webpod
# => Hostname: webpod-697b545f57-rcgf8
# ...
$ kubectl exec -it curl-pod -- curl webpod
# => Hostname: webpod-697b545f57-p9hhs
# ...
# tcpdump
$ tcpdump -i cilium_wg0 -n
$ tcpdump -eni any udp port 51871
$ tcpdump -eni any udp port 51871 -w /tmp/wg.pcap # vagrant scp k8s-ctr:/tmp/wg.pcap . > wireshark 로 확인
$ termshark /tmp/wg.pcap
# => termshark 2.4.0 | wg.pcap Analysis Misc
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃Filter: <Apply> <Recent> ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
# No. - Time - Source - Dest - Proto - Length - Info - ▲
# <span style="background-color: #2080fd; color: #fff;">1 0.000000 192.168.10.100 192.168.10.102 WireGuard 192 Transport Data, receiver=0x12FBD5BA, counter=28, datalen=112 █</span>
# 2 0.001455 192.168.10.100 192.168.10.102 WireGuard 192 Transport Data, receiver=0x12FBD5BA, counter=29, datalen=112
# 3 0.026152 192.168.10.102 192.168.10.100 WireGuard 240 Transport Data, receiver=0xF6874881, counter=22, datalen=160
# 4 0.026152 192.168.10.102 192.168.10.100 WireGuard 288 Transport Data, receiver=0xF6874881, counter=23, datalen=208
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# [+] Frame 1: 192 bytes on wire (1536 bits), 192 bytes captured (1536 bits)
# [+] Linux cooked capture v2
# [+] Internet Protocol Version 4, Src: 192.168.10.100, Dst: 192.168.10.102
# [+] User Datagram Protocol, Src Port: 51871, Dst Port: 51871
# [-] WireGuard Protocol
# Type: Transport Data (4)
# Reserved: 000000
# Receiver: 0x12fbd5ba
# Counter: 28
# <span style="background-color: #2080fd; color: #fff;"> Encrypted Packet [=] </span>
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 0000 08 00 00 00 00 00 00 03 00 01 04 06 08 00 27 5d ........ ......']
# 0010 09 97 00 00 45 00 00 ac 70 7e 00 00 40 11 73 a8 ....E... p~..@.s.
# 0020 c0 a8 0a 64 c0 a8 0a 66 ca 9f ca 9f 00 98 96 c4 ...d...f ........
# 0030 04 00 00 00 ba d5 fb 12 1c 00 00 00 00 00 00 00 ........ ........
# 0040 e6 <span style="background-color: #2080fd; color: #fff;">08 0f da 17 ab 05 18 ad a8 92 a6 53 17 5e 27</span> ........ ....S.^'
# 0050 <span style="background-color: #2080fd; color: #fff;">79 e8 ce 06 41 63 c2 9b e1 51 3b b7 a1 a9 82 09</span> y...Ac.. .Q;.....
# 0060 <span style="background-color: #2080fd; color: #fff;">f5 71 8a f3 f9 ad aa 78 40 01 cd e6 22 43 cb 6d</span> .q.....x @..."C.m
# 0070 <span style="background-color: #2080fd; color: #fff;">3e 06 60 40 67 27 5e 96 8b 6f 43 87 32 89 bb 54</span> >.`@g'^. .oC.2..T
# 0080 <span style="background-color: #2080fd; color: #fff;">cc dd 61 ff 0b 52 32 31 7a e0 9f 1f de 50 6e d5</span> ..a..R21 z....Pn.
# 0090 <span style="background-color: #2080fd; color: #fff;">0a 69 d4 d3 9c 73 8d a3 60 ff 34 2b 51 40 65 48</span> .i...s.. `.4+Q@eH
# 00a0 <span style="background-color: #2080fd; color: #fff;">19 8d 8c fb e3 41 40 13 63 06 b8 1a 96 64 10 58</span> .....A@. c....d.X
# 00b0 <span style="background-color: #2080fd; color: #fff;">44 3b fa 0c 7d f8 09 98 f3 50 98 7c 64 04 cd 59</span> D;..}... .P.|d..Y
# <span style="color: green;">👉 패킷이 암호화 되어 있는 것을 확인할 수 있습니다.</span>
# query the flow API and look for flows
$ hubble observe --pod curl-pod
-
Node-to-Node Encryption (beta) : 아직 베타 기능이지만
--set encryption.nodeEncryption=true옵션을 통해 노드 - 노드 , 노드 - 파드 간 트래픽도 암호화 할 수 있습니다. - 설정 원복
#
$ helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
--set encryption.enabled=false
# => Release "cilium" has been upgraded. Happy Helming!
# ...
$ kubectl -n kube-system rollout restart ds/cilium
# => daemonset.apps/cilium restarted
# 확인
$ cilium config view | grep -i wireguard
# => (없음)
$ kubectl exec -it -n kube-system ds/cilium -- cilium encrypt status
# => Encryption: Disabled
$ kubectl exec -it -n kube-system ds/cilium -- cilium status | grep Encryption
# => Encryption: Disabled
# <span style="color: green;">👉 WireGuard 인터페이스 및 설정이 제거된 것을 확인할 수 있습니다.</span>
Inspecting TLS Encrypted Connections
https://docs.cilium.io/en/stable/security/tls-visibility/
- 쿠버네티스 환경에서 네트워크 트래픽은 보안을 위해 TLS(Transport Layer Security)로 암호화되는 경우가 많습니다.
- 그러나 암호화된 연결은 정책 적용이나 가시성 확보 측면에서 제약이 생길 수 있습니다.
- Cilium은 이러한 문제를 해결하기 위해 TLS 암호화된 연결도 투명하게 검사할 수 있는 기능을 제공합니다.
소개
- Cilium은 HTTPS와 같은 TLS 보호 연결에서도 API 인식 가시성과 정책 적용을 지원합니다.
- 기존 하드웨어 방화벽과 유사하지만, 전적으로 쿠버네티스 워커 노드의 소프트웨어 방식으로 동작합니다.
- 네트워크 보안팀은 내부 인증 기관(CA)을 생성하여 클라이언트가 이를 신뢰하도록 설정합니다.
- Cilium은 TLS 연결을 중간에서 종료하고 내부 CA 서명 인증서를 사용하여 대상 서비스와 새로운 TLS 연결을 맺습니다.
- 이를 통해 애플리케이션 계층 데이터 검사와 정책 적용이 가능해집니다.
TLS 검사 구성 단계
TLS 검사를 구현하려면 CA 기반 모델을 따라야 하며, 기본 절차는 다음과 같습니다:
- 내부 CA 생성: CA 인증서를 생성하여 내부인증기관을 만듭니다.
- 대상 서비스 인증서 요청: DNS 이름과 일치하는 서명 요청(CSR: Certificate Signing Request)을 생성합니다.
- CA 서명 인증서 발급: 내부 CA로 서명된 인증서를 만듭니다.
- 클라이언트 신뢰 체인 구성: 클라이언트에 CA 인증서를 설치합니다.
- Cilium 구성: 새로운 TLS 연결을 생성할 때 사용할 신뢰할 CA 집합을 지정합니다.
⚠️ 운영 환경에서는 CA 개인 키 보관이 매우 중요합니다.
개인 키가 유출되면 TLS 트래픽을 누구나 가로채어 검사할 수 있기 때문입니다.
반대로 인증서는 공개 정보이므로 민감하지 않습니다.
TLS 검사 동작 방식
- Cilium은 클라이언트와의 원래 TLS 연결을 종료하고, Envoy를 통해 대상 서비스와 새로운 TLS 연결을 수립합니다.
- 네트워크 정책에는
terminatingTLS및originatingTLS설정이 필요합니다. - 이 과정에서 핵심은 Envoy에 인증서를 전달하는 방식입니다.
-
NPDS(원래의 버전)와 SDS(새롭고 향상된 버전) 두 가지 방식을 제공하고 있으며 각각 장단점이 있습니다.
-
NPDS (Network Policy Discovery Service)
- 인증서와 키를 Base64 텍스트로 인라인 전송합니다.
- 단순하지만 동일 인증서를 여러 정책에서 사용할 경우 Envoy 메모리에 중복 저장됩니다.
- 대규모 환경에서는 메모리 사용량이 급증할 수 있는 단점이 있습니다.
-
SDS (Secret Discovery Service)
- Cilium 1.17부터 도입된 개선된 방식입니다.
- Kubernetes 시크릿을 참조하여 Envoy SDS API를 통해 전달합니다.
- 인증서가 참조 기반으로 전달되므로 중복 저장이 사라집니다.
- 메모리 효율성과 보안이 뛰어나며, Cilium 1.17 이후 기본값으로 권장됩니다.
TLS Interception 구성 (Cilium 1.17+)
- Cilium은 세 가지 방법으로 TLS Interception을 구성할 수 있습니다:
-
SDS (권장 방식)
- 정책에서 참조한 시크릿을
cilium-secrets네임스페이스에 복사 후 SDS로 동기화합니다. - 기본값이자 가장 안전하고 효율적인 방식입니다.
- 정책에서 참조한 시크릿을
tls:
readSecretsOnlyFromSecretsNamespace: true
secretsNamespace:
name: cilium-secrets # This setting is optional, as it is the default
secretSync:
enabled: true
-
NPDS 인라인 방식 (비권장)
- 클러스터 내 어느 네임스페이스든 시크릿을 저장할 수 있고, Cilium이 직접 읽어 Envoy에 전달합니다.
- 보안 범위가 크게 확장되므로 권장되지 않습니다.
tls:
readSecretsOnlyFromSecretsNamespace: false
secretSync:
enabled: false
-
하위 호환 모드
-
cilium-secrets네임스페이스를 직접 참조합니다. - 업그레이드 호환성을 위해 유지되는 옵션입니다.
-
tls:
readSecretsOnlyFromSecretsNamespace: true
secretsNamespace:
name: cilium-secrets # This setting is optional, as it is the default
secretSync:
enabled: false
- Helm 설정 주요 항목
-
tls.secretsNamespace.name: 정책 비밀 네임스페이스 (기본값:cilium-secrets) -
tls.readSecretsOnlyFromSecretsNamespace: 지정 네임스페이스에서만 비밀 읽기 (기본값:true) -
tls.secretSync.enabled: sSDS 사용을 위한 비밀 동기화 활성화 (기본값:true)
-
Cilium TLS Interception 설정 (SDS Mode)
#
$ kubectl get all,secret,cm -n cilium-secrets
# => NAME DATA AGE
# configmap/kube-root-ca.crt 1 3h33m
#
$ 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
# => Release "cilium" has been upgraded. Happy Helming!
# ...
$ kubectl -n kube-system rollout restart deploy/cilium-operator
# => deployment.apps/cilium-operator restarted
$ kubectl -n kube-system rollout restart ds/cilium
# => daemonset.apps/cilium restarted
# 확인
$ 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
🔬 Lab3. TLS Interception 실습
- TLS 가로채기를 시연하기 위해 DNS 인식 정책 예제에 사용한 것과 동일한
mediabot애플리케이션을 사용할 것입니다. 이 애플리케이션은 HTTPS를 사용하여 Star Wars API 서비스에 액세스합니다. 이는 일반적으로 Cilium과 같은 네트워크 계층 메커니즘이 통신의 HTTP 계층 세부 정보를 볼 수 없음을 의미합니다. 모든 애플리케이션 데이터는 네트워크로 전송되기 전에 TLS를 사용하여 암호화되기 때문입니다. - 다음의 항목을 실습하면서 TLS 가로채기 기능을 이해해 보겠습니다.
- TLS 가로채기를 활성화하기 위해 내부 인증 기관(CA)과 해당 CA가 서명한 관련 인증서 생성
- DNS 기반 정책 규칙을 사용하여 가로챌 트래픽을 선택하려면 Cilium 네트워크 정책을 사용
- cilium monitor를 사용하여 HTTP 요청의 세부 사항을 검사
단일 포드 mediabot 애플리케이션을 생성
#
$ 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
# => pod/mediabot created
$ kubectl wait pod/mediabot --for=condition=Ready
# => pod/mediabot condition met
# 확인
$ kubectl get pods
# => NAME READY STATUS RESTARTS AGE
# mediabot 1/1 Running 0 16s
$ kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
# => HTTP/2 200
$ kubectl exec mediabot -- curl -I -s --max-time 5 https://support.github.com | head -1
# => HTTP/2 302
TLS 키 및 인증서 생성 및 설치
- 아래의 이미지는 생성되거나 복사되는 암호화 데이터가 포함된 다양한 파일과 시스템의 어떤 구성 요소가 해당 파일에 액세스해야 하는지 설명합니다.
암호화 데이터와 시스템의 구성요소간의 액세스 관계도
- 로컬 시스템에 openssl이 이미 설치되어 있다면 사용할 수 있지만, 그렇지 않은 경우 간단한 단축키를 사용하여 cilium 포드 내에서 실행한 후 결과 명령을 실행할 수 있습니다.
- Kubernetes 시크릿을 생성하거나 포드에 복사할 때 cilium 포드에서 결과 파일을 복사하는 데 사용할 수 있습니다 .
내부 인증 기관(CA) 만들기
# 'myCA.key'라는 이름의 CA 개인 키를 생성합니다.
$ openssl genrsa -des3 -out myCA.key 2048
# => Enter PEM pass phrase: qwe123 # <span style="color: green;">CA 인증서에서 사용할 비밀번호 입력</span>
# Verifying - Enter PEM pass phrase: qwe123
$ ls *.key
# => myCA.key
# 개인 키에서 CA 인증서를 생성
$ openssl req -x509 -new -nodes -key myCA.key -sha256 -days 1825 -out myCA.crt
# => Enter pass phrase for myCA.key: qwe123 # <span style="color: green;">개인 키 비밀번호 입력</span>
# You are about to be asked to enter information that will be incorporated
# into your certificate request.
# What you are about to enter is what is called a Distinguished Name or a DN.
# There are quite a few fields but you can leave some blank
# For some fields there will be a default value,
# If you enter '.', the field will be left blank.
# -----
# Country Name (2 letter code) [AU]:KR
# State or Province Name (full name) [Some-State]:Seoul
# Locality Name (eg, city) []:Seoul
# Organization Name (eg, company) [Internet Widgits Pty Ltd]:cloudneta
# Organizational Unit Name (eg, section) []:study
# Common Name (e.g. server FQDN or YOUR name) []:cloudneta.net
# Email Address []:
$ ls *.crt
# => myCA.crt
$ openssl x509 -in myCA.crt -noout -text
# => ...
# Issuer: C = KR, ST = Seoul, L = Seoul, O = cloudneta, OU = study, CN = cloudneta.net
# Validity
# Not Before: Sep 6 10:55:56 2025 GMT
# Not After : Sep 5 10:55:56 2030 GMT
# Subject: C = KR, ST = Seoul, L = Seoul, O = cloudneta, OU = study, CN = cloudneta.net
# ...
# X509v3 Basic Constraints: critical
# CA:TRUE
# ...
지정된 DNS 이름에 대한 개인 키 및 인증서 서명 요청 생성
# 검사를 위해 가로채려는 대상 서비스의 DNS 이름과 일치하는 일반 이름을 사용하여 내부 개인 키와 인증서 서명을 생성합니다
## 이 예에서는 httpbin.org
# 먼저 개인 키를 만듭니다.
$ openssl genrsa -out internal-httpbin.key 2048
$ ls internal-httpbin.key
# => internal-httpbin.key
# 다음으로, 인증서 서명 요청을 생성하고, 메시지가 표시되면 일반 이름 필드에 대상 서비스의 DNS 이름을 지정합니다.
# 다른 모든 메시지에는 원하는 값을 입력할 수 있습니다.
# 특정 값이어야 하는 유일한 필드는 클라이언트에 제공될 정확한 DNS 대상을 보장하는 것입니다. Common Name: httpbin.org
# 이 예제 워크플로는 정책 YAML(아래)의 toFQDNs 규칙도 인증서의 DNS 이름과 일치하도록 업데이트되는 한 모든 DNS 이름에 적용됩니다.
$ openssl req -new -key internal-httpbin.key -out internal-httpbin.csr
# => ...
# Common Name (e.g. server FQDN or YOUR name) []:httpbin.org
# ...
$ ls internal-httpbin.csr
# => internal-httpbin.csr
CA를 사용하여 DNS 이름에 대한 서명된 인증서 생성
# 내부 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
# => Certificate request self-signature ok
# subject=C = KR, ST = Seoul, L = Seoul, O = cloudneta, OU = study, CN = httpbin.org
# Enter pass phrase for myCA.key: qwe123 <span style="color: green;">CA 개인 키 비밀번호 입력</span>
$ ls internal-httpbin.crt
# => internal-httpbin.crt
$ openssl x509 -in internal-httpbin.crt -noout -text
# => ...
# Issuer: C = KR, ST = Seoul, L = Seoul, O = cloudneta, OU = study, CN = cloudneta.net # <span style="color: green;">내부 CA 인증서 정보</span>
# Validity
# Not Before: Sep 6 11:00:18 2025 GMT
# Not After : Sep 1 11:00:18 2026 GMT
# Subject: C = KR, ST = Seoul, L = Seoul, O = cloudneta, OU = study, CN = httpbin.org # <span style="color: green;">대상 서비스 인증서 정보</span>
# 다음으로 대상 서비스에 대한 개인 키와 서명된 인증서를 모두 포함하는 Kubernetes 비밀을 생성합니다.
$ kubectl create secret tls httpbin-tls-data -n kube-system --cert=internal-httpbin.crt --key=internal-httpbin.key
# => secret/httpbin-tls-data created
$ kubectl get secret -n kube-system httpbin-tls-data
# => NAME TYPE DATA AGE
# httpbin-tls-data kubernetes.io/tls 2 10s
클라이언트 포드 내에서 신뢰할 수 있는 CA로 내부 CA 추가
- CA 인증서가 클라이언트 포드에 저장된 후에도 애플리케이션에서 사용하는 TLS 라이브러리가 CA 파일을 인식하는지 확인해야 합니다.
- 대부분의 Linux 애플리케이션은 Linux 배포판과 함께 제공되는 신뢰할 수 있는 CA 인증서 세트를 자동으로 사용합니다.
- 이 가이드에서는 Ubuntu 컨테이너를 클라이언트로 사용하므로 Ubuntu별 지침에 따라 업데이트합니다. 다른 Linux 배포판은 다른 메커니즘을 사용합니다.
- 개별 애플리케이션은 OS 인증서 저장소를 사용하는 대신 자체 인증서 저장소를 활용할 수 있습니다. 자세한 내용은 애플리케이션 또는 애플리케이션 런타임 설명서를 참조하십시오.
- 대부분의 Linux 애플리케이션은 Linux 배포판과 함께 제공되는 신뢰할 수 있는 CA 인증서 세트를 자동으로 사용합니다.
#
$ kubectl exec -it mediabot -- ls -l /usr/local/share/ca-certificates/
# => total 0
# Ubuntu의 경우 먼저 추가 CA 인증서를 클라이언트 Pod 파일 시스템에 복사합니다.
$ kubectl cp myCA.crt default/mediabot:/usr/local/share/ca-certificates/myCA.crt
$ kubectl exec -it mediabot -- ls -l /usr/local/share/ca-certificates/
# => total 4
# -rw-r--r-- 1 root root 1342 Sep 6 11:03 myCA.crt
# /etc/ssl/certs/ca-certificates.crt에 있는 신뢰할 수 있는 인증 기관의 글로벌 세트에 이 인증서를 추가하는 Ubuntu 전용 유틸리티 실행
$ kubectl exec -it mediabot -- ls -l /etc/ssl/certs/ca-certificates.crt # 사이즈, 생성/수정 날짜 확인
# => -rw-r--r-- 1 root root 213777 Jan 9 2024 /etc/ssl/certs/ca-certificates.crt
$ kubectl exec mediabot -- update-ca-certificates
# => Updating certificates in /etc/ssl/certs...
# rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
# 1 added, 0 removed; done.
# Running hooks in /etc/ca-certificates/update.d...
# done.
$ kubectl exec -it mediabot -- ls -l /etc/ssl/certs/ca-certificates.crt # 사이즈, 생성/수정 날짜 확인
# => -rw-r--r-- 1 root root 215119 Sep 6 11:04 /etc/ssl/certs/ca-certificates.crt
# <span style="color: green;">👉 신규 CA 인증서가 추가되어 파일 크기와 수정 날짜가 변경된 것을 확인할 수 있습니다.</span>
Cilium에 신뢰할 수 있는 CA 목록 제공
- 다음으로, Cilium이 보조 TLS 연결을 시작할 때 신뢰해야 하는 CA 세트를 제공합니다. 이 목록은 조직에서 신뢰하는 표준 글로벌 CA 세트와 일치해야 합니다. 논리적인 옵션 중 하나는 운영 체제에서 신뢰하는 표준 CA 세트입니다. 이는 TLS 검사 도입 이전에 사용되던 CA 세트이기 때문입니다.
- 간단하게 설명하기 위해 이 예에서 우리는 mediabot 포드의 Ubuntu 파일 시스템에서 이 목록을 복사할 것입니다. 하지만 이 신뢰할 수 있는 CA 목록은 특정 TLS 클라이언트나 서버에만 국한되지 않는다는 점을 이해하는 것이 중요합니다. 따라서 TLS 검사에 참여하는 TLS 클라이언트나 서버의 수에 관계없이 이 단계는 한 번만 수행하면 됩니다.
#
$ kubectl cp default/mediabot:/etc/ssl/certs/ca-certificates.crt ca-certificates.crt
# 이 인증서 번들을 사용하여 Kubernetes 비밀을 생성하여 Cilium이 인증서 번들을 읽고 이를 사용하여 나가는 TLS 연결을 검증할 수 있도록 합니다.
$ kubectl create secret generic tls-orig-data -n kube-system --from-file=ca.crt=./ca-certificates.crt
# => secret/tls-orig-data created
$ kubectl get secret -n kube-system tls-orig-data
# => NAME TYPE DATA AGE
# tls-orig-data Opaque 1 7s
Apply DNS and TLS-aware Egress Policy
- 지금까지 TLS 검사를 활성화하기 위한 키와 인증서를 생성했지만, Cilium에 어떤 트래픽을 가로채서 검사할지 알려주지 않았습니다. 이 작업은 다른 Cilium 네트워크 정책에 사용되는 것과 동일한 Cilium 네트워크 정책 구조를 사용하여 수행됩니다.
- 다음 Cilium 네트워크 정책은 Cilium이
mediabot포드와 . 사이의 통신에 대해 HTTP 인식 검사를 수행해야 함을 나타냅니다httpbin.org.
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: "*"
- 즉 , 이 정책은 출구 접근 권한이 있는
endpointSelector라벨이 있는 포드에만 적용됩니다 .class: mediabot, org:empire - 첫 번째 출구 섹션에서는 사양을 사용하여 TCP 포트 443 출구를 허용합니다 .
toFQDNs: matchNamehttpbin.org -
httptoFQDNs 규칙 아래의 섹션은 이러한 연결이 모든 요청을 허용하는 정책에 따라 HTTP로 구문 분석되어야 함을 나타 냅니다{}. - 및 섹션은 TLS 가로채기를 사용하여 mediabot에서의 초기 TLS 연결을 종료하고 .에 대한 새로운 아웃바운드 TLS 연결을 시작해야 함을 나타
terminatingTLS냅니다 .originatingTLShttpbin.org - 두 번째 출구 섹션은
mediabot포드가kube-dns서비스에 접근할 수 있도록 합니다. Cilium이 지정된 패턴과 일치하는 DNS 조회를 검사하고 허용하도록 지시합니다. 이 경우 모든 DNS 쿼리를 검사하고 허용합니다.rules: dns
# 모니터링 : hubble cli 나 ui 에서 https 이니 L7 정보 확인 불가능
$ hubble observe --pod mediabot -f
#
$ kubectl get pods
# => NAME READY STATUS RESTARTS AGE
# mediabot 1/1 Running 0 19m
$ kubectl exec -it mediabot -- curl -sL 'https://httpbin.org/anything'
# => {
# "args": {},
# "data": "",
# "files": {},
# "form": {},
# "headers": {
# "Accept": "*/*",
# "Host": "httpbin.org",
# "User-Agent": "curl/7.88.1",
# "X-Amzn-Trace-Id": "Root=1-68bc1691-6a809d304ab6834b5a89a5ca"
# },
# "json": null,
# "method": "GET",
# "origin": "211.229.62.113",
# "url": "https://httpbin.org/anything"
# }
$ 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: <span style="color: green;">C=US; O=Amazon; CN=Amazon RSA 2048 M03</span> # <span style="color: green;">👉 원래의 인증기관(CA)에서 발급한 인증서</span>
# * SSL certificate verify ok.
# 정책 적용
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-tls-inspection/l7-visibility-tls.yaml
# => ciliumnetworkpolicy.cilium.io/l7-visibility-tls created
$ kubectl get cnp
# => NAME AGE VALID
# l7-visibility-tls 7s True
Demonstrating TLS Inspection
- 우리가 푸시한 정책은 에서
mediabot로의 모든 HTTPS 요청을 허용httpbin.org하지만 모든 데이터는 HTTP 계층에서 구문 분석한다는 점을 기억하세요. 즉, cilium monitor는 모든 HTTP 요청과 응답을 보고합니다. - 이를 확인하려면 새 창을 열고 다음 명령을 실행하여
mediabot포드와 동일한 Kubernetes 워커 노드에서 실행 중인 cilium 포드의 이름(예: cilium-97s78)을 식별합니다.
# 모니터링 : hubble cli 나 ui 에서 https 이니 L7 정보 확인 불가능
$ hubble observe --pod mediabot -f
#
$ kubectl exec -it mediabot -- curl -sL 'https://httpbin.org/anything'
$ 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 11:00:18 2025 GMT
# * expire date: Sep 1 11:00:18 2026 GMT
# * common name: httpbin.org (matched)
# * issuer: <span style="color: green;">C=KR; ST=Seoul; L=Seoul; O=cloudneta; OU=study; CN=cloudneta.net</span>
# <span style="color: green;">👉 내부 인증기관(CA)에서 발급한 인증서로 교체되어 있습니다!</span>
# <span style="color: green;"> 즉, 우리가 만든 CA에서 발급한 인증서로 TLS 연결이 맺어진 것을 확인할 수 있으며</span>
# <span style="color: green;"> TLS 가로채기가 성공적으로 이루어진 것입니다.</span>
# * SSL certificate verify ok.

- 원래는 알 수 없었던 L7 정보가 인증서 교체후 “GET /anything” 으로 확인되는 것을 확인할 수 있습니다.
실습 리소스 삭제
$ kubectl delete -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-dns/dns-sw-app.yaml
# => pod "mediabot" deleted
$ kubectl delete cnp l7-visibility-tls
# => ciliumnetworkpolicy.cilium.io "l7-visibility-tls" deleted
$ kubectl delete secret -n kube-system tls-orig-data
# => secret "tls-orig-data" deleted
$ kubectl delete secret -n kube-system httpbin-tls-data
# => secret "httpbin-tls-data" deleted
Tetragon
- Tetragon 소개 Youtube 영상 : eCHO Episode 194: Tetragon Everywhere
- Tetragon Resources - Link
Tetragon 소개
- Tetragon은 eBPF 기반의 런타임 보안 및 관찰 도구입니다. - Docs
- Linux 커널의 모든 함수에 연결하여 인수, 반환 값, Tetragon이 프로세스에 대해 수집하는 관련 메타데이터(예: 실행 파일 이름), 파일 및 기타 속성을 필터링할 수 있습니다.
- Kubernetes 환경을 인식하며, 네임스페이스, 포드 등의 Kubernetes ID를 이해하므로 개별 작업 부하와 관련하여 보안 이벤트 감지를 구성할 수 있습니다.

-
Tetragon은 다음과 같은 보안에 중요한 이벤트를 감지하고 대응할 수 있습니다.
- Process 실행 및 종료 events
- System 콜 호출 및 반환
- 네트워크와 파일 액세스를 포함한 I/O 활동
- Kubernetes 환경에서 Tetragon을 사용하면 Kubernetes를 인식합니다.
- 즉, 네임스페이스, 포드 등의 Kubernetes ID를 이해하므로 개별 작업 부하와 관련하여 보안 이벤트 감지를 구성할 수 있습니다.
기능 개요
-
eBPF 실시간 처리
- 모든 정책과 필터링을 커널 내부 eBPF에서 직접 수행합니다.
- 이벤트를 유저 공간으로 보내지 않고 커널에서 차단/반응 처리 → 성능 향상.
- 고빈도 이벤트(send, read, write 등)도 커널에서 바로 필터링하므로 관측 오버헤드가 감소됩니다.
- 파일, 소켓, 바이너리 이름, 네임스페이스, 권한(capabilities) 등 세부 필터링을 지원합니다.
- 이렇게 지정된 이벤트만 유저 공간으로 전달하므로 자원 사용 효율적입니다.
- 모든 정책과 필터링을 커널 내부 eBPF에서 직접 수행합니다.
-
eBPF 유연한 추적
- Tetragon은 리눅스 커널의 모든 함수에 연결(hook)이 가능하고, 이를 통해 함수의 인자, 반환값, 프로세스/파일 메타데이터 등 다양한 기준으로 필터링합니다.
- 사용자가 직접 트레이싱 정책(tracing policy)을 작성해 보안/관측 시나리오를 해결 가능합니다.
- 저장소에는 여러 예시가 제공되며, 사용자는 이를 기반으로 자신만의 정책을 만들 수 있습니다.
- 특정 커널 함수 추적도 가능하며, 엔진에 하드코딩된 제한 없습니다.
- 시스템콜 트레이싱에서 흔히 발생하는 문제(잘못된 데이터 읽기, 악의적 조작, 페이지 폴트 누락 등)를 커널 깊숙한 곳에서 안정적으로 훅킹하여 회피합니다.
- 개발자 다수가 커널 개발 경험을 갖고 있어, 다양한 보안 및 관측 유스케이스를 지원하는 정책 세트를 제공합니다.
-
eBPF을 통한 커널 인식
- eBPF를 통해 Tetragon은 리눅스 커널 상태에 직접 접근합니다.
- 이를 쿠버네티스 정보나 사용자 정책과 결합해 실시간 커널 레벨 규칙 적용이 가능합니다.
- 예를 들어, 애플리케이션이 권한을 변경할 때, 해당 프로세스가 시스템 호출을 완료하고 추가 시스템 호출을 실행하기 전에 경고를 발생시키거나 프로세스를 종료하는 정책을 생성할 수 있습니다.
Tetragon 설치
# /proc 파일 시스템 액세스 필요.
# 기본적으로 Tetragon은 이벤트 로그의 노이즈를 줄이기 위해 kube-system 이벤트를 필터링합니다.
$ helm repo add cilium https://helm.cilium.io
$ helm repo update
# => ...Successfully got an update from the "cilium" chart repository
# Update Complete. ⎈Happy Helming!⎈
$ helm install tetragon cilium/tetragon -n kube-system
# => NAME: tetragon
# LAST DEPLOYED: Sat Sep 6 21:14:53 2025
# NAMESPACE: kube-system
# STATUS: deployed
# REVISION: 1
# TEST SUITE: None
$ kubectl rollout status -n kube-system ds/tetragon -w
# => daemon set "tetragon" successfully rolled out
# 확인
$ k -n kube-system get deploy tetragon-operator -owide
# => NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
# tetragon-operator 1/1 1 1 37s tetragon-operator quay.io/cilium/tetragon-operator:v1.5.0 app.kubernetes.io/instance=tetragon,app.kubernetes.io/name=tetragon-operator
$ k -n kube-system get cm tetragon-operator-config tetragon-config
# => NAME DATA AGE
# tetragon-operator-config 9 45s
# tetragon-config 33 45s
$ k -n kube-system get ds tetragon -owide
# => NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR
# tetragon 3 3 3 3 3 <none> 54s export-stdout,tetragon quay.io/cilium/hubble-export-stdout:v1.1.0,quay.io/cilium/tetragon:v1.5.0 app.kubernetes.io/instance=tetragon,app.kubernetes.io/name=tetragon
$ k -n kube-system get svc,ep tetragon
# => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/tetragon ClusterIP 10.96.248.15 <none> 2112/TCP 75s
#
# NAME ENDPOINTS AGE
# endpoints/tetragon 192.168.10.100:2112,192.168.10.101:2112,192.168.10.102:2112 75s
$ k -n kube-system get svc,ep tetragon-operator-metrics
# => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# service/tetragon-operator-metrics ClusterIP 10.96.105.200 <none> 2113/TCP 83s
#
# NAME ENDPOINTS AGE
# endpoints/tetragon-operator-metrics 172.20.1.32:2113 83s
$ k -n kube-system get pod -l app.kubernetes.io/part-of=tetragon -owide
$ k -n kube-system get pod -l app.kubernetes.io/name=tetragon
$ kc -n kube-system describe pod -l app.kubernetes.io/name=tetragon
# => ...
# Containers:
# export-stdout:
# Container ID: containerd://eb8abd117200c08e26b7efc02ae15ff14a618d1fa282b30530db973468ad1dcf
# Image: quay.io/cilium/hubble-export-stdout:v1.1.0
# ...
# tetragon:
# Container ID: containerd://7b283b22db614b6288a01c09e302349720d85e64ab85d449173abec9597ddce8
# Image: quay.io/cilium/tetragon:v1.5.0
# ...
- 데모 애플리케이션 배포
#
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.18.1/examples/minikube/http-sw-app.yaml
# => service/deathstar created
# deployment.apps/deathstar created
# pod/tiefighter created
# pod/xwing created
# 확인
$ kubectl get pods
# => NAME READY STATUS RESTARTS AGE
# deathstar-86f85ffb4d-2z9dv 1/1 Running 0 23s
# deathstar-86f85ffb4d-djnhm 1/1 Running 0 23s
# tiefighter 1/1 Running 0 23s
# xwing 1/1 Running 0 23s
실행 모니터링
https://isovalent.com/blog/post/top-tetragon-use-cases/
https://yuki-nakamura.com/2024/05/23/tetragon-process-lifecycle-observation-tetragon-agent-part/
- Tetragon은 실행 이벤트를 JSON 로그와 GRPC 스트림을 통해 공개합니다. 사용자는 이를 통해 시스템의 모든 실행 과정을 관찰할 수 있습니다.
# 여러 노드가 있는 클러스터에서는 사용하는 Tetragon Pod가 "xwing" Pod와 동일한 노드에 있어야 실행 이벤트를 캡처할 수 있습니다.
# 이 명령을 사용하면 "**xwing**" Pod와 동일한 Kubernetes **노드**에 있는 **Tetragon Pod의 이름을** 가져올 수 있습니다.
$ 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}'))
$ echo $POD
# => pod/tetragon-fwccq
# 터미널1
# 일치하는 Pod를 식별한 후 해당 Pod를 대상으로 지정하여 명령을 실행합니다 : Tetragon에서 캡처한 실행 이벤트를 반환
$ kubectl exec -ti -n kube-system $POD -c tetragon -- tetra -h
# 압축 실행 이벤트에는 이벤트 유형, 포드 이름, 바이너리, 인수가 포함됩니다. 종료 이벤트에는 반환 코드가 포함됩니다.
$ kubectl exec -ti -n kube-system $POD -c tetragon -- tetra getevents -o compact --pods xwing
## JSON 형식으로 전체 실행 이벤트를 보려면 명령 -o compact에서 옵션을 제거
$ kubectl exec -ti -n kube-system $POD -c tetragon -- tetra getevents --pods xwing
# 터미널2
$ kubectl exec -ti xwing -- bash -c 'curl https://ebpf.io/applications/#tetragon'
# 터미털 1
$ 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
# 터미널2
$ kubectl exec -ti xwing -- bash -c 'curl https://httpbin.org'
# 터미털 1
$ kubectl exec -ti -n kube-system $POD -c tetragon -- tetra getevents -o compact --pods xwing
# => 🚀 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
# 터미널2
$ kubectl exec -ti xwing -- bash -c 'cat /etc/passwd'
# 터미털 1
$ kubectl exec -ti -n kube-system $POD -c tetragon -- tetra getevents -o compact --pods xwing
# => 🚀 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
파일 접속 모니터링
https://isovalent.com/blog/post/file-monitoring-with-ebpf-and-tetragon-part-1/
Figure 3: In-kernel vs user-space filtering
- YAML 구성 파일을 통해 Tetragon에 추적 정책을 추가하면 Tetragon의 기본 실행 추적 기능을 확장할 수 있습니다.
- 이러한 정책은 커널에서 필터링을 수행하여 커널에서 실행 중인 BPF 프로그램에서 관심 있는 이벤트만 사용자 공간에 게시되도록 합니다.
- 이를 통해 사용량이 많은 시스템에서도 오버헤드를 낮게 유지할 수 있습니다.
- 아래의 Manifest는 실행 모니터링 의 예시를 확장하여 Linux에서 민감한 파일을 모니터링하는 정책을 적용합니다.
- 민감 파일 모니터링 정책 적용
# file_monitoring.yaml
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: "file-monitoring-filtered"
spec:
kprobes:
- call: "security_file_permission"
syscall: false
return: true
args:
- index: 0
type: "file" # (struct file *) used for getting the path
- index: 1
type: "int" # 0x04 is MAY_READ, 0x02 is MAY_WRITE
returnArg:
index: 0
type: "int"
returnArgAction: "Post"
selectors:
- matchArgs:
- index: 0
operator: "Prefix"
values:
- "/boot" # Reads to sensitive directories
- "/root/.ssh" # Reads to sensitive files we want to know about
- "/etc/shadow"
- "/etc/profile"
- "/etc/sudoers"
- "/etc/pam.conf" # Reads global shell configs bash/csh supported
- "/etc/bashrc"
- "/etc/csh.cshrc"
- "/etc/csh.login" # Add additional sensitive files here
- index: 1
operator: "Equal"
values:
- "4" # MAY_READ
# Manifest 적용
$ kubectl apply -f https://raw.githubusercontent.com/cilium/tetragon/main/examples/quickstart/file_monitoring.yaml
# => tracingpolicy.cilium.io/file-monitoring-filtered created
$ kubectl get tracingpolicy
# => NAME AGE
# file-monitoring-filtered 55s
- Tetragon 파일 액세스 이벤트 관찰
# 터미널2 : 이벤트를 생성하기위애 정책에 참조된 중요한(민감한) 파일을 읽어보세요
$ kubectl exec -ti xwing -- bash -c 'cat /etc/shadow'
# 터미널1
$ 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 "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
# 📚 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
# 터미널2 : 추적 정책에 따라 Tetragon은 민감한 디렉토리에 쓰기를 시도하는 경우(예: /etc디렉토리에 쓰기를 시도하는 경우)에 대한 응답으로 쓰기 이벤트를 생성합니다.
$ kubectl exec -ti xwing -- bash -c 'echo foo >> /etc/bar'
# 터미널1
$ kubectl exec -ti -n kube-system $POD -c tetragon -- tetra getevents -o compact --pods xwing
# => 🚀 process default/xwing /usr/bin/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
# 정책 삭제
$ kubectl delete -f https://raw.githubusercontent.com/cilium/tetragon/main/examples/quickstart/file_monitoring.yaml
# => tracingpolicy.cilium.io "file-monitoring-filtered" deleted
Policy Enforcement
- 커널 수준에서 정책 제한을 적용관련 내용을 살펴보겠습니다. - Docs
- Tetragon의 추적 정책은 파일 액세스 이벤트나 네트워크 연결 이벤트와 같은 이벤트를 보고하기 위해 커널 함수를 모니터링하고, 해당 커널 함수에 제한을 적용할 수 있도록 지원합니다.
- Tetragon에서 커널 내 필터링을 사용하면 커널에서 사용자 공간으로 전송되는 이벤트를 제한하여 성능을 크게 향상시킬 수 있습니다.
- 또한, 커널 내 필터링을 통해 Tetragon은 커널 수준에서 정책 제한을 적용할 수 있습니다.
- 예를 들어,
SIGKILL정책 위반이 감지되었을 때 프로세스에 를 실행하면 해당 프로세스는 더 이상 실행되지 않습니다. - 정책 적용이 시스템 호출을 통해 트리거되는 경우, 애플리케이션은 시스템 호출에서 반환되지 않고 종료됩니다.
- 이 섹션에서는 이 시작 가이드에서 이미 배포한 Tetragon 기능(실행, 파일 추적 및 네트워크 추적 정책)에 아래의 네트워크 및 파일 정책 적용을 추가합니다.
- Kubernetes 클러스터에서 나가는 네트워크 트래픽을 제한하는 정책 적용
- 민감한 파일에 블록 쓰기 및 읽기 작업 적용
- 구체적인 구현 세부 사항은 시행 개념 섹션을 참조하세요.
파일 액세스 제한 적용
- 다음은 파일 액세스 모니터링 의 예시를 적용하여 민감한 파일이 읽히지 않도록 확장하는 방법입니다.
- 사용되는 정책은 이며
file_monitoring_enforce.yaml, 필요에 따라 검토하고 확장할 수 있습니다. - 관찰 정책과 적용 정책의 유일한 차이점은 애플리케이션에 작업 블록을 추가
SIGKILL하고 작업 시 오류를 반환한다는 것입니다.
# file_monitoring_enforce.yaml : matchaction 확인
apiVersion: cilium.io/v1alpha1
kind: TracingPolicyNamespaced
metadata:
name: "file-monitoring-filtered"
spec:
kprobes:
- call: "security_file_permission"
syscall: false
return: true
args:
- index: 0
type: "file" # (struct file *) used for getting the path
- index: 1
type: "int" # 0x04 is MAY_READ, 0x02 is MAY_WRITE
returnArg:
index: 0
type: "int"
returnArgAction: "Post"
selectors:
- matchArgs:
- index: 0
operator: "Prefix"
values:
- "/boot" # Reads to sensitive directories
- "/root/.ssh" # Reads to sensitive files we want to know about
- "/etc/shadow"
- "/etc/profile"
- "/etc/sudoers"
- "/etc/pam.conf" # Reads global shell configs bash/csh supported
- "/etc/bashrc"
- "/etc/csh.cshrc"
- "/etc/csh.login" # Add additional sensitive files here
- index: 1
operator: "Equal"
values:
- "4" # MAY_READ
matchActions:
- action: Sigkill
...
$ kubectl apply -f https://raw.githubusercontent.com/cilium/tetragon/main/examples/quickstart/file_monitoring_enforce.yaml
# => tracingpolicynamespaced.cilium.io/file-monitoring-filtered created
$ kubectl get tracingpolicynamespaced
# => NAME AGE
# file-monitoring-filtered 11s
# 터미널1
$ 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 "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 <span style="color: green;">SIGKILL</span>
# 터미널2 : 정의된 정책에 포함된 파일 중 하나인 민감한 파일을 읽어보세요
$ kubectl exec -ti xwing -- bash -c 'cat /etc/shadow'
# => command terminated with exit code 137
# <span style="color: green;">👉 이번에는 "cat /etc/shadow" 명령이 SIGKILL로 인해 종료된 것을 확인할 수 있습니다.</span>
# 시행된 파일 정책에 포함되지 않은 파일을 읽거나 쓰려는 시도는 영향을 받지 않습니다.
$ kubectl exec -ti xwing -- bash -c 'echo foo > /tmp/test.txt'
$ kubectl exec -ti -n kube-system $POD -c tetragon -- tetra getevents -o compact --pods xwing
# => 🚀 process default/xwing /usr/bin/bash -c "echo foo > /tmp/test.txt"
# 💥 exit default/xwing /usr/bin/bash -c "echo foo > /tmp/test.txt" 0
마치며
Cilium Security와 Tetragon에 대해 알아보았습니다. Cilium 자체도 다양한 보안 기능을 제공하고 있는데 Tetragon까지 더해지니 다양한 보안 요구사항을 충족시킬 수 있을 것 같습니다. TLS Interception 기능도 흥미로웠는데 완전 MITM 공격인것 같으면서도 트러블 슈팅에 큰 도움이 될 수 있을 것 같습니다.
벌써 Cilium 스터디가 끝났습니다. Cilium 설치와 기본 설정에서 부터, Hubble을 비롯한 관측성과 파드 네트워크 상세, BGP 라우팅, 서비스메시, Performance, Security와 Tetragon 등등 따라가기도 벅찼지만 많은 것을 배울 수 있었던 시간이었습니다. 매주 스터디 마다 느낀 점은 Cilium의 생태계는 정말 넓고 깊다는 것입니다. 앞으로도 꾸준히 공부하고 실습하면서 익혀나가야 할 것 같습니다.
실습할때 복잡한 Kubernetes 구성이 필요한 경우도 많았는데
매번 편리하게 환경을 구축할 수 있도록 해주셔서 환경 구성에는 어려움없이 진행할 수 있었습니다.
매주 실습자료 준비와 진행에 애써주신 가시다 님께 감사드립니다. ![]()