devlos
Devlos Archive
devlos
전체 방문자
오늘
어제
12-11 12:40

최근 글

  • 분류 전체보기 (107)
    • 프로젝트 (1)
    • MSA 설계 & 도메인주도 설계 (9)
    • 클라우드 컴퓨팅 & NoSQL (87)
      • [Cilium Study] 실리움 스터디 (8)
      • [KANS] 쿠버네티스 네트워크 심화 스터디 (12)
      • [T101] 테라폼 4기 스터디 (8)
      • [CICD] CICD 맛보기 스터디 (3)
      • [T101] 테라폼 기초 입문 스터디 (6)
      • [AEWS] Amazon EKS 워크숍 스터디 (7)
      • [PKOS] 쿠버네티스 실무 실습 스터디 (7)
      • Kubernetes (13)
      • Docker (7)
      • Redis (1)
      • Jenkins (3)
      • Terraform (1)
      • Ansible (4)
      • Kafka (1)
    • 프로그래밍 (7)
      • Spring Boot (5)
      • Broker (1)
    • 성능과 튜닝 (1)
    • ALM (0)
    • 기타 (2)

인기 글

태그

  • Kubernetes
  • 쿠버네티스 스터디
  • t101 4기
  • cilium
  • 데브옵스
  • PKOS
  • CloudNet@
  • 테라폼
  • 도커
  • 쿠버네티스
  • kOps
  • DevOps
  • terraform
  • docker
  • MSA

티스토리

최근 댓글

hELLO · Designed By 정상우.
devlos

Devlos Archive

[8주차 - Cilium 스터디] Cilium Security & Tetragon (25.08.31)
클라우드 컴퓨팅 & NoSQL/[Cilium Study] 실리움 스터디

[8주차 - Cilium 스터디] Cilium Security & Tetragon (25.08.31)

2025. 9. 6. 23:31
반응형

 

 

들어가며

안녕하세요! Devlos입니다.

이번 포스팅은 CloudNet@ 커뮤니티에서 주최하는 Cilium Study 8주 차 주제인 "Cilium Security & Tetragon"에 대해서 정리한 내용입니다.

 


 

실습환경 구성

이번 주차 실습 환경은 mac M3 Pro max 환경에서 실습을 진행했고, VirtualBox + Vagrant로 환경을 구성했어요.

실습 환경은 다음과 같습니다.
버전
k8s: 1.33.4 (스터디 기간동안 버전이 빠르게 올라가네요!)
cilium: 1.18.1

가상머신 구성
k8s-ctr, k8s-w1, k8s-w2

Vagrant 기반 구성

mkdir cilium-lab && cd cilium-lab

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

vagrant up

vagrant ssh k8s-ctr

모니터링

# 프로메테우스 접속
http://192.168.10.100:30001

# 그라파나 접속
http://192.168.10.100:30002

# 허블 UI 접속
http://192.168.10.100:30003

Cilium 공식 문서 보안

Cilium은 다양한 계층에서 보안 정책을 제공합니다.

  • Identity-Based (Layer 3): 네트워크의 IP 주소가 아니라, 각 워크로드(파드 등)에 부여된 고유한 Identity(정책 ID)를 기반으로 트래픽을 제어합니다. 이를 통해 IP가 동적으로 변해도 일관된 보안 정책 적용이 가능합니다.
  • Port Level (Layer 4): 트래픽의 포트와 프로토콜(TCP/UDP 등)을 기준으로 세분화된 접근 제어가 가능합니다. 예를 들어, 특정 포트(80, 443 등)만 허용하는 정책을 설정할 수 있습니다.
  • Application Protocol Level (Layer 7): HTTP, gRPC, Kafka 등 애플리케이션 계층의 프로토콜을 인식하여, 경로(path), 메서드(GET, POST 등), 헤더 등 세부 조건까지 정책을 적용할 수 있습니다. 이를 통해 더욱 정밀한 보안 제어가 가능합니다.

즉, Cilium은 네트워크 계층(L3), 전송 계층(L4), 애플리케이션 계층(L7)까지 다양한 수준의 보안 정책을 지원하여, 클라우드 네이티브 환경에서 유연하고 강력한 보안 구성을 할 수 있습니다.
링크 - https://docs.cilium.io/en/stable/security/network/intro/

 


Cilium의 보안 정책은 "Identity" 기반으로 동작합니다. 여기서 Identity란, 각 워크로드(예: 파드)에 부여되는 고유한 정책 ID를 의미합니다.
예를 들어, role=frontends라는 라벨을 가진 파드와 role=backend라는 라벨을 가진 파드가 있다고 가정해봅시다.

  • identity => role=frontends는 "role=frontends" 라벨을 가진 파드의 Identity(정책 ID)를 의미합니다.
  • identity => role=backend는 "role=backend" 라벨을 가진 파드의 Identity를 의미합니다.

Cilium에서는 다음과 같이 정책을 정의할 수 있습니다.

  • "role=frontends" Identity를 가진 파드에서 "role=backend" Identity를 가진 파드로의 트래픽을 허용한다.
  • 즉, "frontends" 역할을 하는 파드가 "backend" 역할을 하는 파드에 접근할 수 있도록 허용하는 정책입니다.

기본 보안 정책 동작 방식

Cilium에서 보안 정책은 다음과 같이 동작합니다.

먼저, 클러스터에 아무런 네트워크 정책이 적용되어 있지 않은 상태라면, Cilium은 기본적으로 모든 파드 간의 통신을 허용합니다. 즉, 별도의 정책을 만들지 않았다면 파드들끼리 자유롭게 네트워크로 연결될 수 있습니다. 이때는 정책 적용이 명시적으로 활성화되어 있지 않기 때문에, 네트워크 트래픽이 차단되지 않습니다.

하지만 사용자가 처음으로 네트워크 정책(예: CiliumNetworkPolicy 등)을 클러스터에 생성하면, Cilium은 자동으로 정책 적용 모드로 전환됩니다. 이 시점부터는 정책에 명시적으로 허용된 트래픽만 통과할 수 있고, 정책에 포함되지 않은 트래픽은 모두 차단(드롭)됩니다. 즉, 허용 목록(allow-list)에 추가된 통신만 가능하고, 나머지는 모두 거부되는 방식입니다.

또한, 포트 단위의 제어(Layer 4, L4 정책)도 마찬가지입니다. 엔드포인트(파드 등)에 L4 정책이 전혀 적용되어 있지 않다면, 해당 엔드포인트는 모든 포트로의 통신이 허용됩니다. 하지만 L4 정책이 하나라도 적용되면, 정책에서 명시적으로 허용한 포트만 접근이 가능하고, 나머지 포트로의 연결은 모두 차단됩니다.

정리하면, Cilium은 정책이 없을 때는 모든 통신을 허용하지만, 정책이 한 번이라도 적용되면 그때부터는 허용된 트래픽만 통과시키고 나머지는 모두 차단하는 방식으로 동작합니다. 포트 단위(L4) 정책도 동일하게 적용되어, 정책이 없으면 모든 포트가 열려 있지만, 정책이 생기면 지정된 포트만 열리게 됩니다.

Envoy Proxy


출처 - https://docs.cilium.io/en/stable/security/network/proxy/envoy/

Cilium이 네트워크 트래픽을 제어하기 위해 Envoy Proxy를 어떻게 활용하는지 보여줍니다.

  • Cilium은 L7(애플리케이션 계층) 정책을 적용할 때 Envoy Proxy를 파드의 네트워크 경로에 프록시로 삽입합니다.
  • 파드에서 나가는 트래픽(egress)이나 들어오는 트래픽(ingress)이 있을 때, 해당 트래픽은 먼저 Envoy Proxy를 거치게 됩니다.
  • Envoy Proxy는 HTTP, gRPC, Kafka 등 다양한 애플리케이션 프로토콜을 이해하고, 요청의 경로(path), 메서드, 헤더 등 세부 조건에 따라 허용/차단 정책을 적용할 수 있습니다.
  • Cilium은 Envoy Proxy와 연동하여, 사용자가 정의한 L7 네트워크 정책을 실시간으로 Envoy에 전달하고, Envoy는 이 정책에 따라 트래픽을 필터링합니다.
  • 이를 통해 단순한 IP/포트 기반 제어를 넘어서, 애플리케이션 레벨에서 세밀한 보안 정책을 구현할 수 있습니다.

투명한 암호화

"투명한 암호화"란 파드나 서비스에 별도의 암호화 설정을 하지 않아도, Cilium이 네트워크 레벨에서 자동으로 트래픽을 암호화해주는 기능입니다.


Cilium의 "투명한 암호화(Transparent Encryption)" 기능은 클러스터 내 파드 간 트래픽을 자동으로 암호화해주는 기능입니다.

즉, 사용자가 파드나 애플리케이션에서 별도의 암호화 설정을 하지 않아도, Cilium이 네트워크 계층에서 트래픽을 자동으로 암호화하고 복호화합니다.

 

이 기능을 사용하면, 파드 개발자는 암호화에 신경 쓸 필요 없이 평소처럼 네트워크 통신을 하면 되고, Cilium이 내부적으로 IPsec 또는 WireGuard와 같은 기술을 활용해 트래픽을 보호합니다.
이를 통해 클러스터 내부 트래픽의 기밀성과 무결성을 보장할 수 있습니다.


Cilium은 두 가지 주요 암호화 방식을 지원합니다.

  • IPsec: 네트워크 계층에서 동작하는 표준 암호화 프로토콜입니다. 각 노드의 커널에 IPsec 터널이 설정되어, 파드 간 트래픽이 자동으로 암호화되어 전송됩니다. 관리자는 별도의 파드 설정 없이 Cilium 설정만으로 IPsec을 활성화할 수 있습니다.
  • WireGuard: 경량화된 최신 VPN 프로토콜로, 빠른 성능과 간단한 설정이 특징입니다. Cilium은 WireGuard를 내장 지원하여, 노드 간 트래픽을 효율적으로 암호화할 수 있습니다.

그림에서처럼, 파드에서 나가는 트래픽은 Cilium 에이전트에 의해 자동으로 암호화(IPsec 또는 WireGuard)되어 네트워크를 통해 전송되고, 목적지 노드에서 복호화되어 파드로 전달됩니다.
이 과정은 파드/애플리케이션이 별도 암호화 코드를 작성하지 않아도 자동으로 처리되므로, 개발자는 보안에 신경 쓰지 않고도 안전한 네트워크 환경을 구축할 수 있습니다.

Identity

Cilium의 Identity 시스템은 Kubernetes 라벨을 기반으로 각 워크로드에 고유한 보안 식별자를 할당하는 핵심 보안 메커니즘입니다. 이 시스템을 통해 IP 주소가 아닌 Identity를 기반으로 네트워크 정책을 적용할 수 있습니다.

Identity 시스템의 핵심 장점:

  1. 라벨 기반 보안: Kubernetes 라벨을 기반으로 Identity가 자동 할당되어, 라벨이 동일한 파드들은 같은 보안 정책을 공유합니다.
  2. IP 독립성: 파드가 재시작되어 IP가 변경되어도 Identity는 유지되므로, 네트워크 정책이 안정적으로 동작합니다.
  3. 확장성: 수천 개의 파드가 있어도 Identity 기반으로 효율적인 정책 관리가 가능합니다.
  4. 기본 보안: Identity 간 통신은 명시적인 정책이 없으면 기본적으로 차단되는 Deny-all 정책이 적용됩니다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get ciliumendpoints.cilium.io -n kube-system
NAME                              SECURITY IDENTITY   ENDPOINT STATE   IPV4           IPV6
coredns-674b8bbfcf-f7w7r          3120                ready            172.20.0.181   
coredns-674b8bbfcf-j8k5j          3120                ready            172.20.0.159   
hubble-relay-fdd49b976-c45d9      7895                ready            172.20.0.102   
hubble-ui-655f947f96-kwjcl        43418               ready            172.20.0.155   
metrics-server-5dd7b49d79-85ntf   40254               ready            172.20.0.103   

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get ciliumidentities.cilium.io 
NAME    NAMESPACE            AGE
3120    kube-system          16m
40254   kube-system          16m
43418   kube-system          16m
4582    local-path-storage   16m
45930   cilium-monitoring    16m
57499   cilium-monitoring    16m
7895    kube-system          16m

CiliumEndpoints 분석:

  • CoreDNS 파드들 (Identity: 3120): 동일한 라벨을 가진 파드들이 같은 Identity를 공유합니다. 이는 라벨 기반 Identity 할당의 대표적인 예시입니다.
  • Hubble Relay (Identity: 7895): 클러스터 내 모니터링 데이터를 중계하는 역할을 담당합니다.
  • Hubble UI (Identity: 43418): 웹 기반 모니터링 인터페이스를 제공합니다.
  • Metrics Server (Identity: 40254): 클러스터 리소스 메트릭을 수집합니다.

CiliumIdentities 분석:

  • kube-system 네임스페이스: 클러스터 핵심 컴포넌트들이 위치하며, 각각 고유한 Identity를 가집니다.
  • local-path-storage 네임스페이스: 로컬 스토리지 관련 워크로드가 Identity 4582를 사용합니다.
  • cilium-monitoring 네임스페이스: Cilium 모니터링 관련 워크로드들이 Identity 45930, 57499를 사용합니다.

Cilium은 파드나 컨테이너가 시작될 때 레이블을 기반으로 엔드포인트 ID(Identity)를 할당합니다. 만약 파드의 레이블이 변경되면, Cilium은 이를 감지하여 새로운 Identity를 생성하고 기존 ID에서 새로운 ID로 자동으로 갱신합니다. 이 과정은 보통 수 초 이내(1~10초 내외)에 반영되며, 레이블이 바뀌면 실제로 ID 값도 변경됩니다.

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl label pods -n kube-system -l k8s-app=kube-dns study=8w
pod/coredns-674b8bbfcf-f7w7r labeled
pod/coredns-674b8bbfcf-j8k5j labeled

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it -n kube-system ds/cilium -- cilium identity list | grep 8w
        k8s:study=8w


(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl edit deploy -n kube-system coredns
...
  template:
    metadata:
      labels:
        app: testing
        k8s-app: kube-dns
...
deployment.apps/coredns edited

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it -n kube-system ds/cilium -- cilium identity list | grep testing
65347   k8s:app=testing

자주 labels를 변경하면 identity 할당이 빈번해져 성능 저하가 발생할 수 있으므로, identity-relevant labels를 제한하는 것이 권장됩니다

Special Identities

Cilium은 클러스터 내에서 관리하는 모든 네트워크 엔드포인트(예: 파드, 서비스 등)에 대해 고유한 Identity(ID)를 자동으로 할당합니다. 이 Identity는 주로 엔드포인트의 레이블 정보를 기반으로 생성되며, 네트워크 정책 적용이나 트래픽 제어에 활용됩니다.

하지만 클러스터 외부에 있는 엔드포인트(예: 외부 인터넷, 클러스터에 속하지 않은 노드 등)와의 통신도 필요할 수 있습니다. 이런 경우, Cilium은 내부적으로 "특수한 ID(Special Identity)"를 사용하여 클러스터 외부 또는 Cilium이 직접 관리하지 않는 엔드포인트를 구분합니다. 이 특수 ID들은 일반적으로 reserved라는 문자열이 앞에 붙어 있으며, 예를 들어 reserved:world, reserved:host와 같은 형태로 나타납니다.

이러한 reserved ID는 Cilium이 네트워크 정책을 적용할 때, 외부 트래픽이나 특정 시스템 엔드포인트를 식별하고 별도의 정책을 적용할 수 있도록 도와줍니다. 즉, 클러스터 내부 엔드포인트와 외부 엔드포인트를 명확하게 구분하여, 보다 세밀한 네트워크 제어가 가능해집니다.

(⎈|HomeLab:N/A) root@k8s-ctr:~# 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
3120    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 Numeric ID 설명
reserved:unknown 0 Identity를 파악할 수 없는 경우 할당됩니다.
reserved:host 1 로컬 호스트. 로컬 호스트 IP에서 발생하거나 해당 IP로 지정된 모든 트래픽에 해당합니다.
reserved:world 2 클러스터 외부의 모든 네트워크 엔드포인트에 해당합니다.
reserved:unmanaged 3 Cilium이 관리하지 않는 엔드포인트(예: Cilium 설치 전에 생성된 Kubernetes 파드 등)에 할당됩니다.
reserved:health 4 Cilium 에이전트가 생성하는 헬스 체크 트래픽에 사용됩니다.
reserved:init 5 Identity가 아직 할당되지 않은 엔드포인트에 임시로 할당됩니다. 보통 부트스트랩 단계에서 메타데이터가 부족할 때 사용됩니다.
reserved:remote-node 6 모든 원격 클러스터 호스트의 집합. 로컬 노드를 제외한 연결된 클러스터의 호스트 IP에서 발생하거나 해당 IP로 지정된 트래픽.
reserved:kube-apiserver 7 kube-apiserver를 제공하는 백엔드가 있는 원격 노드에 할당됩니다.
reserved:ingress 8 Ingress 프록시에서 연결의 소스 주소로 사용되는 IP에 할당됩니다.

Identity Management in the Cluster


Identity의 특성은 다음과 같습니다.

  • Cilium의 Identity(ID)는 클러스터 전체에서 유효하며, 동일한 레이블을 가진 엔드포인트(포드, 컨테이너)는 동일한 ID를 공유합니다.
  • 이 ID 할당 및 관리는 클러스터 내 모든 노드가 분산 키-값 저장소를 통해 조율하여 이루어집니다.
  • 새로운 레이블 조합이 등장하면 키-값 저장소에서 원자적으로 새로운 ID가 생성되고, 이미 존재하는 조합이면 기존 ID가 재사용됩니다.
  • 이를 통해 클러스터 전체에서 일관된 네트워크 정책 적용이 가능합니다.

Cilium Network Policy

Cilium Network Policy는 Kubernetes의 기본 NetworkPolicy를 확장하여 더 강력하고 세밀한 네트워크 보안 제어를 제공합니다. 벤 다이어그램에서 보여주는 것처럼, 다양한 계층에서의 정책 적용이 가능합니다.

분류 Kubernetes NetworkPolicy Cilium NetworkPolicy 공통 기능
Selectors (선택자) • Pod Labels
• Namespace Labels
• Endpoints (Pod 라벨 사용)
• Node
• IP/CIDR
• Entities
• DNS Names
• Services
• Pod Labels
• Namespace Labels
Traffic Filtering • Pod Labels
• Namespace Labels
• Endpoints
• IP/CIDR
• Entities
• DNS Names
• Services
• L4 Protocols (TCP, UDP, SCTP)
• ICMP/ICMPv6 Policy
• L7 Protocols (HTTP, gRPC, Kafka)
• CIDR
• L4 Protocols (TCP, UDP, SCTP)
Traffic Direction • Ingress
• Egress
• Ingress
• Egress
• Ingress
• Egress
Behavior • Null rule, default deny
• No policy, whitelist posture
• Null rule, default deny
• No policy, whitelist posture
• Null rule, default deny
• No policy, whitelist posture
Additional Features - • Advanced rule querying and reachability
• Host policies
-

 

Cilium Network Policy의 주요 특징:

  1. L7 애플리케이션 레벨 정책
    • HTTP/HTTPS: URL 경로, 헤더, 메서드 기반 제어
    • DNS: 도메인 이름 기반 쿼리 제어
    • Kafka: 토픽, 클러스터 기반 메시지 제어
  2. Identity 기반 정책
    • IP 주소가 아닌 Kubernetes 라벨 기반 Identity 사용
    • 파드 재시작 시에도 안정적인 정책 유지
    • 동적 환경에서의 확장성 보장
  3. 고급 트래픽 제어
    • Node Selector를 통한 노드별 정책 적용
    • 클러스터 간 통신 제어
    • 외부 엔드포인트와의 통신 정책

핵심 차이점은 다음과 같습니다.

  • Kubernetes NetworkPolicy: 기본적인 L3/L4 정책만 지원, IP 기반
  • Cilium NetworkPolicy: L7 정책, Identity 기반, DNS 정책, 호스트 정책 등 확장된 기능
  • 공통 기능: Ingress/Egress, CIDR, L4 프로토콜, 기본 정책 동작

Layer 4 정책 예시:

정책 유형 설명 주요 특징
엔드포인트 기반 라벨 기반 엔드포인트 간 통신 IP 주소 독립적, 라벨 기반 정책
서비스 기반 Kubernetes 서비스 기반 통신 서비스 엔드포인트 자동 관리
엔티티 기반 특수 엔티티(host, world 등) 통신 클러스터 외부/시스템 리소스 접근
노드 기반 특정 노드 간 통신 노드 라벨 기반 제어
IP/CIDR 기반 IP 주소/서브넷 기반 통신 외부 서비스 접근, 최후 수단
DNS 기반 도메인 이름 기반 통신 DNS 프록시를 통한 동적 IP 관리

 

Layer 4 정책 예시:

정책 유형 설명 예시
기본 L4 정책 특정 포트/프로토콜 허용 TCP 80 포트 허용
포트 범위 포트 범위 지정 80~444 포트 범위 허용
CIDR 의존 L4 특정 IP 대역 + 포트 조합 192.0.2.0/24 대역의 80 포트만 허용
TLS SNI 제한 TLS 핸드셰이크 시 서버 이름 제한 특정 도메인만 TLS 연결 허용

 

Layer 7 정책 예시:

프로토콜 정책 유형 설명 예시
HTTP Path 기반 특정 URL 경로만 허용 GET /public 경로만 허용
HTTP Method 기반 HTTP 메서드 제한 GET, POST만 허용
HTTP Header 기반 특정 헤더 포함 시만 허용 X-My-Header가 있는 요청만 허용
HTTP Host 기반 특정 호스트 헤더만 허용 foo.com 호스트만 허용
DNS 도메인 기반 특정 도메인 쿼리만 허용 cilium.io 도메인만 허용
DNS 패턴 기반 와일드카드 패턴 매칭 *.cilium.io 하위 도메인 허용

 

정책 적용 우선순위:

  1. L3 정책: 기본적인 네트워크 접근 제어
  2. L4 정책: 포트/프로토콜 레벨 제어
  3. L7 정책: 애플리케이션 레벨 세밀한 제어

Cilium Security 실습

Lab 1: Locking Down External Access with DNS-Based Policy

Cilium의 DNS 기반 네트워크 정책을 활용하여 클러스터 내부에서 외부 서비스로 나가는 트래픽을 세밀하게 제어하는 방법을 다룹니다.

기존의 CIDR 또는 IP 기반 정책은 외부 서비스의 IP가 자주 변경될 경우 관리가 어렵다는 단점이 있습니다. 이를 해결하기 위해 Cilium은 DNS 이름을 기반으로 정책을 설정할 수 있는 기능을 제공합니다. 이 기능을 사용하면 도메인 이름(예: *.example.com)에 따라 외부 접근을 허용하거나 차단할 수 있습니다.

실습에서는 다음과 같은 내용을 단계별로 진행합니다:

  1. DNS 기반 정책을 적용하여 특정 외부 도메인(예: api.github.com)으로의 접근만 허용하고, 그 외의 외부 트래픽은 차단합니다.
  2. 와일드카드 패턴을 활용해 여러 하위 도메인에 대한 접근을 한 번에 허용하는 방법을 실습합니다.
  3. DNS 정책과 함께 DNS, 포트 규칙을 결합하여 더욱 세밀하게 외부 서비스 접근을 제어하는 방법을 실습합니다.

실습을 통해 실제로 정책을 적용하고, 적용 전후의 네트워크 접근 결과를 비교해보며 Cilium DNS 정책의 효과와 활용법을 익힐 수 있습니다.

 

애플리케이션 배포

# 1. 테스트용 파드 생성 (mediabot)
(⎈|HomeLab:N/A) root@k8s-ctr:~# cat << EOF > dns-sw-app.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mediabot
  labels:
    org: empire          # 조직 라벨
    class: mediabot      # 클래스 라벨
    app: mediabot        # 애플리케이션 라벨
spec:
  containers:
  - name: mediabot
    # Cilium에서 제공하는 테스트용 이미지 (HTTP 요청 가능)
    image: quay.io/cilium/json-mock:v1.3.8@sha256:5aad04835eda9025fe4561ad31be77fd55309af8158ca8663a72f6abb78c2603
EOF

# 2. 파드 배포
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl apply -f dns-sw-app.yaml
pod/mediabot created

# 3. 파드 준비 상태 대기
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl wait pod/mediabot --for=condition=Ready
pod/mediabot condition met

# 4. Cilium Identity 확인 (라벨 기반으로 자동 할당된 보안 ID)
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it -n kube-system ds/cilium -- cilium identity list | grep k8s:app=mediabot -A6
34017   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

# 5. 정책 적용 전 외부 통신 테스트 - api.github.com 접근
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
HTTP/2 200 

# 6. 정책 적용 전 외부 통신 테스트 - support.github.com 접근
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s --max-time 5 https://support.github.com | head -1
HTTP/2 302 

Hubble 접속을 통해 통신 상태를 확인해봅니다.
http://192.168.10.100:30003/

 

 

DNS Egress 정책 적용 1 : mediabot포드가 api.github.com에만 액세스하도록 허용

# 1. DNS 기반 네트워크 정책 생성 (특정 도메인만 허용)
(⎈|HomeLab:N/A) root@k8s-ctr:~# cat << EOF | kubectl apply -f -
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "fqdn"
spec:
  endpointSelector:
    matchLabels:
      org: empire      # 정책이 적용될 파드 선택 (mediabot)
      class: mediabot
  egress:
  - toFQDNs:
    - matchName: "api.github.com"  # 특정 도메인만 허용
  - toEndpoints:
    - matchLabels:
        "k8s:io.kubernetes.pod.namespace": kube-system
        "k8s:k8s-app": kube-dns    # DNS 서버 접근 허용
    toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
        - matchPattern: "*"        # 모든 DNS 쿼리 허용
EOF
ciliumnetworkpolicy.cilium.io/fqdn created

# 2. 정책 생성 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get cnp
NAME   AGE   VALID
fqdn   5s    True

# 3. 정책 셀렉터 확인 (어떤 엔드포인트에 정책이 적용되는지)
(⎈|HomeLab:N/A) root@k8s-ctr:~# 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       34017   
&LabelSelector{MatchLabels:map[string]string{k8s.io.kubernetes.pod.namespace: kube-system,k8s.k8s-app: kube-dns,},MatchExpressions:[]LabelSelectorRequirement{},}             default/fqdn   3       3120    
                                                                                                                                                                                                     56192   
                                                                                                                                                                                                     65347   
&LabelSelector{MatchLabels:map[string]string{reserved.none: ,},MatchExpressions:[]LabelSelectorRequirement{},}                                                                default/fqdn   1       
MatchName: api.github.com, MatchPattern:                                                                                                                                      default/fqdn   1       16777217   

# 4. 정책 적용 후 통신 테스트 - 허용된 도메인 (api.github.com)
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
HTTP/2 200 

# 5. 정책 적용 후 통신 테스트 - 차단된 도메인 (support.github.com)
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s --max-time 5 https://support.github.com | head -1
command terminated with exit code 28

 

 

# 6. Hubble를 통한 네트워크 트래픽 모니터링
# cilium-agent 내에 go로 구현된 lightweight proxy가 DNS 쿼리/응답 감시와 캐싱 처리 (기본 30초 TTL)

(⎈|HomeLab:N/A) root@k8s-ctr:~# cilium hubble port-forward&

# 7. mediabot 파드의 네트워크 트래픽 관찰
(⎈|HomeLab:N/A) root@k8s-ctr:~# hubble observe --pod mediabot
...
# DNS 응답: support.github.com의 IP 주소들 (TTL: 30초)
Sep  5 17:05:05.625: default/mediabot:38444 (ID:34017) <- kube-system/coredns-99ff8c6c4-7cjm9:53 (ID:65347) dns-response proxy FORWARDED (DNS Answer "185.199.110.133,185.199.111.133,185.199.109.133,185.199.108.133" TTL: 30 (Proxy support.github.com. A))
...
# 정책 위반으로 인한 TCP 연결 차단
Sep  5 17:05:05.728: default/mediabot:43058 (ID:34017) <> support.github.com:443 (world) policy-verdict:none EGRESS DENIED (TCP Flags: SYN)
Sep  5 17:05:05.728: default/mediabot:43058 (ID:34017) <> support.github.com:443 (world) Policy denied DROPPED (TCP Flags: SYN)

# 8. (옵션) CoreDNS 로그로 직접 확인
# k9s -> configmap (coredns) : log 추가

## CoreDNS 로깅 활성화
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl logs -n kube-system -l k8s-app=kube-dns -f
maxprocs: Leaving GOMAXPROCS=4: CPU quota undefined
.:53
...
# DNS 쿼리 로그: 클러스터 내부 도메인 검색 (NXDOMAIN)
[INFO] 172.20.0.72:49470 - 63468 "A IN api.github.com.cluster.local. udp 46 false 512" NXDOMAIN qr,aa,rd 139 0.001659204s
[INFO] 172.20.0.72:49470 - 31982 "AAAA IN api.github.com.cluster.local. udp 46 false 512" NXDOMAIN qr,aa,rd 139 0.001664331s
# DNS 쿼리 로그: 외부 도메인 검색 (성공)
[INFO] 172.20.0.72:57889 - 12767 "AAAA IN api.github.com. udp 32 false 512" NOERROR qr,rd,ra 116 0.015605508s
[INFO] 172.20.0.72:57889 - 58846 "A IN api.github.com. udp 32 false 512" NOERROR qr,rd,ra 62 0.016829577s
# DNS 쿼리 로그: 서비스 도메인 검색 (NXDOMAIN)
[INFO] 172.20.0.72:52626 - 8324 "AAAA IN api.github.com.svc.cluster.local. udp 50 false 512" NXDOMAIN qr,aa,rd 143 0.00037106s
[INFO] 172.20.0.72:52626 - 15237 "A IN api.github.com.svc.cluster.local. udp 50 false 512" NXDOMAIN qr,aa,rd 143 0.000373812s

## 9. 테스트 호출 및 모니터링
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
(⎈|HomeLab:N/A) root@k8s-ctr:~# hubble observe --pod mediabot

## 10. 각 노드별 Cilium 파드 이름 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# 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-mlmfq cilium-lv822 cilium-fsxxl

## 11. 각 노드별 Cilium 명령어 단축키 설정
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"

## 12. 각 노드별 FQDN 캐시 확인 (DNS 프록시가 수집한 도메인-IP 매핑)
c0 fqdn cache list
c1 fqdn cache list
c2 fqdn cache list
Endpoint   Source       FQDN                  TTL   ExpirationTime             IPs               
1942       connection   api.github.com.       0     2025-09-05T17:32:17.281Z   20.200.245.245    
1942       connection   support.github.com.   0     2025-09-05T17:32:17.281Z   185.199.111.133   
1942       connection   support.github.com.   0     2025-09-05T17:32:17.281Z   185.199.109.133   
1942       connection   support.github.com.   0     2025-09-05T17:32:17.281Z   185.199.108.133   
1942       connection   support.github.com.   0     2025-09-05T17:32:17.281Z   185.199.110.133 

## 13. 각 노드별 FQDN 정책 셀렉터 확인
c0 fqdn names
c1 fqdn names
c2 fqdn names
{
  "DNSPollNames": null,
  "FQDNPolicySelectors": [
    {
      "regexString": "^api[.]github[.]com[.]$",  # 정규식 패턴
      "selectorString": "MatchName: api.github.com, MatchPattern: "  # 정책 셀렉터
    }
  ]
}

DNS Egress 정책 적용 2 : 모든 GitHub 하위 도메인(예: 패턴)에 액세스

# 1. 기존 정책 삭제 및 FQDN 캐시 초기화
kubectl delete cnp fqdn
c1 fqdn cache clean -f
c2 fqdn cache clean -f

# 2. 와일드카드 패턴을 사용한 DNS 정책 (dns-pattern.yaml 내용)
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "fqdn"
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
  - toFQDNs:
    - matchName: "*.github.com"  # 와일드카드 패턴으로 모든 GitHub 하위 도메인 허용
  - toEndpoints:
    - matchLabels:
        "k8s:io.kubernetes.pod.namespace": kube-system
        "k8s:k8s-app": kube-dns
    toPorts:
    - ports:
      - port: "53"
        protocol: ANY
      rules:
        dns:
        - matchPattern: "*"

# 3. 와일드카드 패턴 정책 적용
kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-dns/dns-pattern.yaml

# 4. 정책 적용 확인
c1 fqdn names
c2 fqdn names
c1 fqdn cache list
c2 fqdn cache list

# 5. 정책 생성 확인
kubectl get cnp
NAME   AGE   VALID
fqdn   4s    True

# 6. 와일드카드 패턴 정책 테스트 - GitHub 하위 도메인들
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s https://support.github.com | head -1
HTTP/2 302 
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s https://gist.github.com | head -1
HTTP/2 302 
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s --max-time 5 https://github.com | head -1
HTTP/2 200 

# 7. 와일드카드 패턴 정책 테스트 - 허용되지 않은 도메인
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s --max-time 5 https://cilium.io| head -1
command terminated with exit code 28

 

 

 

DNS Egress 정책 적용 3 : DNS, Port 조합 적용

# 1. DNS와 포트를 조합한 정책 (dns-port.yaml 내용)
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "fqdn"
spec:
  endpointSelector:
    matchLabels:
      org: empire
      class: mediabot
  egress:
  - toFQDNs:
    - matchPattern: "*.github.com"  # GitHub 하위 도메인 패턴
    toPorts:
    - ports:
      - port: "443"                 # HTTPS 포트만 허용
        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: "*"

# 2. DNS+포트 조합 정책 적용
kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-dns/dns-port.yaml

# 3. 정책 적용 확인
c1 fqdn names
c2 fqdn names
c1 fqdn cache list
c2 fqdn cache list

# 4. FQDN 캐시 초기화 (새로운 정책 적용을 위해)
c1 fqdn cache clean -f
c2 fqdn cache clean -f

# 5. DNS+포트 조합 정책 테스트 - HTTPS (443 포트) 허용
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s https://support.github.com | head -1
HTTP/2 302 

# 6. DNS+포트 조합 정책 테스트 - HTTP (80 포트) 차단
(⎈|HomeLab:N/A) root@k8s-ctr:~# 
kubectl exec mediabot -- curl -I -s --max-time 5 http://support.github.com | head -1
command terminated with exit code 28

 

 

리소스 삭제

# 1. 테스트 파드 삭제
kubectl delete -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes-dns/dns-sw-app.yaml

# 2. 네트워크 정책 삭제
kubectl delete cnp fqdn

Lab 2 : Transparent Encfyption with WireGuard

각 노드는 자동으로 자신만의 암호화 키 쌍(공개키/비밀키)을 생성합니다. 그리고 생성된 공개키는 쿠버네티스의 CiliumNode 커스텀 리소스 오브젝트에 io.cilium.network.wg-pub-key라는 annotation(주석) 형태로 저장되어, 클러스터 내 다른 노드들에게 공유됩니다.

 

이렇게 공유된 각 노드의 공개키는, 다른 노드들이 해당 노드에 있는 Cilium이 관리하는 엔드포인트(파드 등)와 통신할 때 트래픽을 암호화하거나 복호화하는 데 사용됩니다.


단, 같은 노드 내에 있는 파드(엔드포인트)끼리 통신할 때는 암호화가 적용되지 않습니다. 즉, 암호화는 노드 간 트래픽에만 적용됩니다.
WireGuard 터널의 엔드포인트는 각 노드에서 UDP 51871번 포트로 열려 있습니다.

 

제한사항: L7(애플리케이션 계층) 정책 적용 및 가시성, eBPF 기반 호스트 라우팅 등 일부 기능에는 제약이 있습니다.

WireGuard를 사용하는 Cilium에서는, 노드의 엔드포인트(예: 파드의 IP나 네트워크 인터페이스)가 변경되더라도 해당 노드는 동일한 피어(상대 노드)로 인식합니다. 이는 각 노드가 고유한 WireGuard 공개키/비밀키 쌍을 가지고 있고, 이 공개키가 CiliumNode 리소스의 annotation에 저장되어 클러스터 내에서 공유되기 때문입니다.


따라서 엔드포인트(예: 파드 IP)가 바뀌어도, 노드의 WireGuard 공개키가 동일하다면 다른 노드들은 계속해서 해당 노드를 같은 피어로 인식하여 암호화 통신을 유지할 수 있습니다.

 

샘플 애플리케이션 배포

# 1. 테스트용 웹 애플리케이션 배포 (2개 파드, 서로 다른 노드에 배치)
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 2                    # 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    # HTTP 응답을 반환하는 테스트 이미지
        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

# 2. 테스트용 클라이언트 파드 배포 (k8s-ctr 노드에 고정 배치)
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

# 3. 배포 상태 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get deploy,svc,ep webpod -owide
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES           SELECTOR
deployment.apps/webpod   2/2     2            2           27s   webpod       traefik/whoami   app=webpod

NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE   SELECTOR
service/webpod   ClusterIP   10.96.66.60   <none>        80/TCP    27s   app=webpod

NAME               ENDPOINTS                        AGE
endpoints/webpod   172.20.1.215:80,172.20.2.24:80   27s

# 4. EndpointSlice 확인 (최신 Kubernetes에서 권장)
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get endpointslices -l app=webpod
NAME           ADDRESSTYPE   PORTS   ENDPOINTS                  AGE
webpod-9v2dz   IPv4          80      172.20.1.215,172.20.2.24   31s

# 5. Cilium 엔드포인트 확인 (각 파드의 Identity와 IP 주소)
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get ciliumendpoints 
NAME                      SECURITY IDENTITY   ENDPOINT STATE   IPV4           IPV6
curl-pod                  27076               ready            172.20.0.48    
webpod-697b545f57-98fkp   18621               ready            172.20.1.215   
webpod-697b545f57-ncz6z   18621               ready            172.20.2.24    

# 6. 정책 적용 전 통신 테스트 (로드밸런싱 확인)
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- curl -s --connect-timeout 1 webpod | grep Hostname
Hostname: webpod-697b545f57-ncz6z
Hostname: webpod-697b545f57-98fkp

 

설정

# 1. WireGuard 커널 모듈 지원 확인 (Linux 5.6 이상 필요)
(⎈|HomeLab:N/A) root@k8s-ctr:~# 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

# 2. WireGuard 커널 모듈 설정 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# grep -E 'CONFIG_WIREGUARD=m' /boot/config-$(uname -r)
CONFIG_WIREGUARD=m

# 3. 설정 전 네트워크 상태 확인 (암호화 적용 전 상태)
ip -c addr
ip -c route
ip rule show

# 4. Cilium에 WireGuard 암호화 활성화
helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
  --set encryption.enabled=true --set encryption.type=wireguard

# 5. Cilium DaemonSet 재시작 (새 설정 적용)
kubectl -n kube-system rollout restart ds/cilium

# 6. WireGuard 설정 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# cilium config view | grep -i wireguard
enable-wireguard                                  true
wireguard-persistent-keepalive                    0s

# 7. 암호화 상태 확인 (현재 노드의 WireGuard 정보)
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it -n kube-system ds/cilium -- cilium encrypt status
Encryption: Wireguard                 
Interface: cilium_wg0
        Public key: bzaNcsYge0xDV5JZFRFLTBpGhuST9Hi3ACOnJYnn3xQ=
        Number of peers: 2

# 8. 모든 노드의 암호화 상태 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it -n kube-system ds/cilium -- cilium status | grep Encryption
Encryption:              Wireguard   [NodeEncryption: Disabled, cilium_wg0 (Pubkey: bzaNcsYge0xDV5JZFRFLTBpGhuST9Hi3ACOnJYnn3xQ=, Port: 51871, Peers: 2)]
Encryption:              Wireguard   [NodeEncryption: Disabled, cilium_wg0 (Pubkey: o4ISkG+hN88144j0FCwTEo0NtfvLnID3/q48eJ1pfVQ=, Port: 51871, Peers: 2)]   

# 9. 상세 디버그 정보 확인 (JSON 형태)
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

# 10. WireGuard 인터페이스 상세 정보 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# ip -d -c addr show cilium_wg0
30: 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 

# 11. 라우팅 규칙 확인 (WireGuard 트래픽을 위한 특별한 라우팅 테이블)
(⎈|HomeLab:N/A) root@k8s-ctr:~# ip rule show
9:      from all fwmark 0x200/0xf00 lookup 2004    # WireGuard 트래픽용 라우팅 테이블
10:     from all fwmark 0xa00/0xf00 lookup 2005    # 추가 WireGuard 라우팅 테이블
100:    from all lookup local
32766:  from all lookup main
32767:  from all lookup default


# 12. WireGuard 상세 정보 확인
wg -h
wg show
interface: cilium_wg0
  public key: KRPnNERp1ASflPo7gq+epV3+caS7/LiYngp+dG2ICBA=    # 현재 노드의 공개키
  private key: (hidden)                                        # 현재 노드의 비밀키 (보안상 숨김)
  listening port: 51871                                        # WireGuard 수신 포트
  fwmark: 0xe00                                                # 방화벽 마크

# 13. 피어(다른 노드) 정보 확인
peer: bzaNcsYge0xDV5JZFRFLTBpGhuST9Hi3ACOnJYnn3xQ=           # k8s-w2 노드의 공개키
  endpoint: 192.168.10.102:51871                              # k8s-w2 노드의 IP와 포트
  allowed ips: 172.20.2.24/32, 192.168.10.102/32, 172.20.2.11/32, 172.20.2.14/32, 172.20.2.0/24  # 허용된 IP 대역

peer: 0qlbw23iQKZkbHWImhQkltSWvlH94OsC5dEMrmLnxzo=           # k8s-w1 노드의 공개키
  endpoint: 192.168.10.101:51871                              # k8s-w1 노드의 IP와 포트
  allowed ips: 172.20.1.215/32, 192.168.10.101/32, 172.20.1.139/32, 172.20.1.244/32, 172.20.1.0/24  # 허용된 IP 대역

# 14. 각 노드별 공개키 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# wg show all public-key
cilium_wg0      KRPnNERp1ASflPo7gq+epV3+caS7/LiYngp+dG2ICBA=

# 15. 각 노드별 비밀키 확인 (보안상 실제로는 숨겨짐)
(⎈|HomeLab:N/A) root@k8s-ctr:~# wg show all private-key
cilium_wg0      gHZ/bNSIRjZ0UjVGEk+ehaxkWx5XHUocIv0tn5IXpGQ=

# 16. 사전 공유 키 확인 (Cilium에서는 사용하지 않음)
(⎈|HomeLab:N/A) root@k8s-ctr:~# wg show all preshared-keys
cilium_wg0      bzaNcsYge0xDV5JZFRFLTBpGhuST9Hi3ACOnJYnn3xQ=    (none)
cilium_wg0      0qlbw23iQKZkbHWImhQkltSWvlH94OsC5dEMrmLnxzo=    (none)

# 17. 피어 엔드포인트 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# wg show all endpoints
cilium_wg0      bzaNcsYge0xDV5JZFRFLTBpGhuST9Hi3ACOnJYnn3xQ=    192.168.10.102:51871
0qlbw23iQKZkbHWImhQkltSWvlH94OsC5dEMrmLnxzo=    192.168.10.101:51871

# 18. 전송 통계 확인 (송신/수신 바이트 수)
(⎈|HomeLab:N/A) root@k8s-ctr:~# wg show all transfer
cilium_wg0      bzaNcsYge0xDV5JZFRFLTBpGhuST9Hi3ACOnJYnn3xQ=    0       0
cilium_wg0      0qlbw23iQKZkbHWImhQkltSWvlH94OsC5dEMrmLnxzo=    0       0

# 19. CiliumNode 리소스의 공개키 annotation 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get cn -o yaml | grep annotations -A1
    annotations:
      network.cilium.io/wg-pub-key: KRPnNERp1ASflPo7gq+epV3+caS7/LiYngp+dG2ICBA=  # k8s-ctr 노드
    annotations:
      network.cilium.io/wg-pub-key: 0qlbw23iQKZkbHWImhQkltSWvlH94OsC5dEMrmLnxzo=  # k8s-w1 노드
    annotations:
      network.cilium.io/wg-pub-key: bzaNcsYge0xDV5JZFRFLTBpGhuST9Hi3ACOnJYnn3xQ=  # k8s-w2 노드

통신 확인

# 1. WireGuard 암호화 적용 후 통신 테스트
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it curl-pod -- curl webpod
Hostname: webpod-697b545f57-ncz6z
IP: 127.0.0.1
IP: ::1
IP: 172.20.2.24
IP: fe80::bcca:8cff:fe4e:5f4e
RemoteAddr: 172.20.0.48:45812
GET / HTTP/1.1
Host: webpod
User-Agent: curl/8.14.1
Accept: */*

# 2. WireGuard 인터페이스에서 패킷 캡처 (암호화된 트래픽 확인)
(⎈|HomeLab:N/A) root@k8s-ctr:~# tcpdump -i cilium_wg0 -n
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on cilium_wg0, link-type RAW (Raw IP), snapshot length 262144 bytes
# TCP 3-way handshake (SYN, SYN-ACK, ACK)
12:24:12.552628 IP 172.20.0.48.55128 > 172.20.1.215.80: Flags [S], seq 3235838095, win 64860, options [mss 1380,sackOK,TS val 1216038472 ecr 0,nop,wscale 7], length 0
12:24:12.556103 IP 172.20.1.215.80 > 172.20.0.48.55128: Flags [S.], seq 1342418554, ack 3235838096, win 64296, options [mss 1380,sackOK,TS val 3783546713 ecr 1216038472,nop,wscale 7], length 0
# HTTP 요청 (GET / HTTP/1.1)
12:24:12.557433 IP 172.20.0.48.55128 > 172.20.1.215.80: Flags [P.], seq 1:71, ack 1, win 507, options [nop,nop,TS val 1216038477 ecr 3783546713], length 70: HTTP: GET / HTTP/1.1
12:24:12.557634 IP 172.20.0.48.55128 > 172.20.1.215.80: Flags [.], ack 1, win 507, options [nop,nop,TS val 1216038476 ecr 3783546713], length 0
# HTTP 응답 (HTTP/1.1 200 OK)
12:24:12.559558 IP 172.20.1.215.80 > 172.20.0.48.55128: Flags [.], ack 71, win 502, options [nop,nop,TS val 3783546717 ecr 1216038477], length 0
12:24:12.563123 IP 172.20.1.215.80 > 172.20.0.48.55128: Flags [.], ack 71, win 502, options [nop,nop,TS val 3783546720 ecr 1216038477], length 0
12:24:12.563315 IP 172.20.1.215.80 > 172.20.0.48.55128: Flags [P.], seq 1:321, ack 71, win 502, options [nop,nop,TS val 3783546720 ecr 1216038477], length 320: HTTP: HTTP/1.1 200 OK
12:24:12.563543 IP 172.20.0.48.55128 > 172.20.1.215.80: Flags [.], ack 321, win 505, options [nop,nop,TS val 1216038483 ecr 3783546720], length 0
# 연결 종료 (FIN, FIN-ACK, ACK)
12:24:12.564056 IP 172.20.0.48.55128 > 172.20.1.215.80: Flags [F.], seq 71, ack 321, win 505, options [nop,nop,TS val 1216038484 ecr 3783546720], length 0
12:24:12.566467 IP 172.20.1.215.80 > 172.20.0.48.55128: Flags [F.], seq 321, ack 72, win 502, options [nop,nop,TS val 3783546723 ecr 1216038484], length 0
12:24:12.567163 IP 172.20.0.48.55128 > 172.20.1.215.80: Flags [.], ack 322, win 505, options [nop,nop,TS val 1216038487 ecr 3783546723], length 0

# 3. 물리 네트워크에서 WireGuard UDP 패킷 캡처 (암호화된 터널 트래픽)
(⎈|HomeLab:N/A) root@k8s-ctr:~# tcpdump -eni any udp port 51871
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
# WireGuard 핸드셰이크 및 데이터 패킷 (UDP 51871 포트)
12:24:47.769472 eth1  Out ifindex 3 08:00:27:7a:3c:0e ethertype IPv4 (0x0800), length 144: 192.168.10.100.51871 > 192.168.10.102.51871: UDP, length 96
12:24:47.771310 eth1  In  ifindex 3 08:00:27:a6:9a:06 ethertype IPv4 (0x0800), length 144: 192.168.10.102.51871 > 192.168.10.100.51871: UDP, length 96
12:24:47.772430 eth1  Out ifindex 3 08:00:27:7a:3c:0e ethertype IPv4 (0x0800), length 144: 192.168.10.100.51871 > 192.168.10.102.51871: UDP, length 96
12:24:47.773079 eth1  Out ifindex 3 08:00:27:7a:3c:0e ethertype IPv4 (0x0800), length 208: 192.168.10.100.51871 > 192.168.10.102.51871: UDP, length 160
12:24:47.776041 eth1  In  ifindex 3 08:00:27:a6:9a:06 ethertype IPv4 (0x0800), length 144: 192.168.10.102.51871 > 192.168.10.100.51871: UDP, length 96
12:24:47.776833 eth1  In  ifindex 3 08:00:27:a6:9a:06 ethertype IPv4 (0x0800), length 464: 192.168.10.102.51871 > 192.168.10.100.51871: UDP, length 416
12:24:47.778305 eth1  Out ifindex 3 08:00:27:7a:3c:0e ethertype IPv4 (0x0800), length 144: 192.168.10.100.51871 > 192.168.10.102.51871: UDP, length 96
12:24:47.778554 eth1  Out ifindex 3 08:00:27:7a:3c:0e ethertype IPv4 (0x0800), length 144: 192.168.10.100.51871 > 192.168.10.102.51871: UDP, length 96
12:24:47.781394 eth1  In  ifindex 3 08:00:27:a6:9a:06 ethertype IPv4 (0x0800), length 144: 192.168.10.102.51871 > 192.168.10.100.51871: UDP, length 96
12:24:47.781889 eth1  Out ifindex 3 08:00:27:7a:3c:0e ethertype IPv4 (0x0800), length 144: 192.168.10.100.51871 > 192.168.10.102.51871: UDP, length 96

# 4. 패킷 캡처 파일 저장 (Wireshark로 분석 가능)
tcpdump -eni any udp port 51871 -w /tmp/wg.pcap  # vagrant scp k8s-ctr:/tmp/wg.pcap . > wireshark 로 확인

# 5. Hubble를 통한 네트워크 플로우 관찰
hubble observe --pod curl-pod

WireGuard 프로토콜이 적용된 것을 패킷 캡쳐를 통해 확인할 수 있습니다.

 

Inspecting TLS Encryption

Cilium을 사용한 TLS 암호화 연결의 투명한 검사 방법을 소개합니다.

 

 

TLS 검사의 핵심 개념:

구성 요소 설명 역할
내부 CA 자체 생성한 인증 기관 대체 인증서 서명
대체 인증서 내부 CA가 서명한 인증서 클라이언트와의 TLS 연결 종료
Cilium Envoy TLS 프록시 역할 연결 종료 및 재시작
클라이언트 TLS 연결을 시작하는 워크로드 내부 CA 인증서 신뢰 필요

TLS 검사 구현 단계:

  1. 내부 CA 생성
    • CA 개인 키와 CA 인증서 생성
    • 자체 인증 기관 구축
  2. 대상별 인증서 생성
    • 각 대상 서비스(예: httpbin.org)에 대한 개인 키 생성
    • DNS 이름과 일치하는 일반 이름으로 인증서 서명 요청(CSR) 생성
    • CA 개인 키로 서명된 인증서 생성
  3. 클라이언트 설정
    • 모든 클라이언트 워크로드에 내부 CA 인증서 설치
    • 내부 CA가 서명한 인증서 신뢰 설정
  4. Cilium 설정
    • 대상 서비스 검증을 위한 CA 집합 정보 제공
    • TLS 연결 종료 및 재시작을 위한 인증서 구성

TLS 검사 작동 방식:

sequenceDiagram
    participant Client as 클라이언트
    participant Cilium as Cilium Envoy
    participant Target as 대상 서비스

    Client->>Cilium: 1. HTTPS 연결 요청
    Cilium->>Client: 2. 대체 인증서 제공 (내부 CA 서명)
    Client->>Cilium: 3. TLS 연결 완료
    Cilium->>Target: 4. 새로운 TLS 연결 시작
    Target->>Cilium: 5. 원본 인증서 제공
    Cilium->>Target: 6. TLS 연결 완료
    Cilium->>Client: 7. 애플리케이션 데이터 전달

인증서 전달 방식 비교:

방식 설명 장점 단점
NPDS Base64 인코딩된 텍스트로 인라인 전송 간단한 구축 메모리 사용량 증가, 중복 저장
SDS 참조 기반으로 인증서 전달 메모리 효율성, 중복 제거 복잡한 설정

Cilium 1.17+ TLS 설정 방법:

  1. SDS 방식 (권장)
    • 네트워크 정책에서 참조되는 비밀을 어디에나 배치 가능
    • Cilium Operator가 cilium-secrets 네임스페이스로 복사
    • SDS를 통해 Envoy에 동기화
  2. 전체 클러스터 접근 방식 (비권장)
    • Cilium 에이전트가 모든 네임스페이스의 시크릿 읽기 권한
    • 보안 범위 확대로 인한 보안 위험
  3. 직접 배치 방식 (하위 호환)
    • cilium-secrets 네임스페이스에 직접 비밀 배치
    • 업그레이드된 클러스터의 기본값

Helm 설정 옵션:

# TLS 관련 Helm 설정
tls:
  secretsNamespace: "cilium-secrets"                    # 비밀 네임스페이스
  readSecretsOnlyFromSecretsNamespace: true            # 지정된 네임스페이스에서만 읽기
  secretSync:
    enabled: true                                       # SDS 사용 활성화

보안 고려사항:

  • CA 개인 키: 매우 민감한 정보, 안전한 보관 필수
  • 대상 서비스 개인 키: Kubernetes Secret으로 저장
  • 네임스페이스 격리: cilium-secrets 네임스페이스에만 접근 허용
  • 오프라인 처리: CA 개인 키는 Cilium에 제공하지 않고 오프라인에서 인증서 생성

Lab 3: Cilium TLS Interception Demo 실습

SDS모드 - Cilium TLS Interception 설정

kubectl get all,secret,cm -n cilium-secrets
NAME                         DATA   AGE
configmap/kube-root-ca.crt   1      7h31m

#
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

이 섹션에서는 mediabot 애플리케이션을 활용하여 Cilium의 TLS 가로채기 기능을 실습합니다. TLS로 암호화된 트래픽을 가로채기 위해 내부 CA와 인증서를 생성하고, Cilium 네트워크 정책을 통해 특정 트래픽을 선택적으로 가로챕니다. 마지막으로 cilium monitor를 사용해 HTTP 요청의 세부 정보를 확인하는 방법을 다룹니다.

단일 Pod 애플리케이션 생성

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 wait pod/mediabot --for=condition=Ready

# 확인
kubectl get pods
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
HTTP/2 200 
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec mediabot -- curl -I -s --max-time 5 https://support.github.com | head -1
HTTP/2 302 

TLS 가로채기를 위해 Cilium을 구성한 후, openssl 등 유틸리티를 사용해 키와 인증서를 생성하고, 이를 Kubernetes 시크릿으로 등록하거나 포드에 복사하는 과정을 진행합니다. 필요한 경우 cilium 포드 내에서 명령을 실행할 수도 있습니다.

내부 인증 기관(CA) 만들기

# 'myCA.key'라는 이름의 CA 개인 키를 생성합니다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# openssl genrsa -des3 -out myCA.key 2048
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:

(⎈|HomeLab:N/A) root@k8s-ctr:~# ls *.key
myCA.key

(⎈|HomeLab:N/A) root@k8s-ctr:~# openssl req -x509 -new -nodes -key myCA.key -sha256 -days 1825 -out myCA.crt
Enter pass phrase for myCA.key:
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]:Daegu
Locality Name (eg, city) []:Daegu
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 []:

(⎈|HomeLab:N/A) root@k8s-ctr:~# ls *.crt
myCA.crt

(⎈|HomeLab:N/A) root@k8s-ctr:~# openssl x509 -in myCA.crt -noout -text
        Issuer: C = KR, ST = Seoul, L = Seoul, O = cloudneta, OU = study, CN = cloudneta.net
        Validity
            Not Before: Aug 31 08:31:07 2025 GMT
            Not After : Aug 30 08:31:07 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

(⎈|HomeLab:N/A) root@k8s-ctr:~# 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

ls 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
        Validity
            Not Before: Aug 31 08:36:33 2025 GMT
            Not After : Aug 26 08:36:33 2026 GMT
        Subject: C = KR, ST = Seoul, L = Seoul, O = cloudneta, OU = study, CN = httpbin.org

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

클라이언트 포드 내에서 신뢰할 수 있는 CA로 내부 CA 추가

CA 인증서를 클라이언트 포드에 저장한 후, 애플리케이션에서 해당 CA를 신뢰하도록 TLS 라이브러리 설정이 필요합니다. 대부분의 Linux 앱은 OS의 기본 CA 저장소를 사용하지만, 일부 앱(Java, aws-cli 등)은 별도 저장소를 쓸 수 있으니 앱별 문서를 참고해야 합니다.

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it mediabot -- ls -l /usr/local/share/ca-certificates/
total 0
# Ubuntu의 경우 먼저 추가 CA 인증서를 클라이언트 Pod 파일 시스템에 복사합니다.
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl cp myCA.crt default/mediabot:/usr/local/share/ca-certificates/myCA.crt
(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it mediabot -- ls -l /usr/local/share/ca-certificates/
total 4
-rw-r--r-- 1 root root 1342 Sep  6 08:58 myCA.crt

# /etc/ssl/certs/ca-certificates.crt에 있는 신뢰할 수 있는 인증 기관의 글로벌 세트에 이 인증서를 추가하는 Ubuntu 전용 유틸리티 실행
(⎈|HomeLab:N/A) root@k8s-ctr:~# 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

(⎈|HomeLab:N/A) root@k8s-ctr:~# 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.

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl exec -it mediabot -- ls -l /etc/ssl/certs/ca-certificates.crt # 사이즈, 생성/수정 날짜 확인
-rw-r--r-- 1 root root 215119 Sep  6 08:58 /etc/ssl/certs/ca-certificates.crt

Cilium에 신뢰할 수 있는 CA 목록 제공
Cilium이 TLS 연결 시 신뢰할 CA 목록을 필요로 하며, 일반적으로 운영체제의 표준 글로벌 CA 세트를 사용합니다. 이 목록은 한 번만 준비하면 되고, 예시에서는 mediabot 포드의 Ubuntu에서 복사합니다.

kubectl cp default/mediabot:/etc/ssl/certs/ca-certificates.crt ca-certificates.crt

#  이 인증서 번들을 사용하여 Kubernetes 비밀을 생성하여 Cilium이 인증서 번들을 읽고 이를 사용하여 나가는 TLS 연결을 검증할 수 있도록 합니다.

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl create secret generic tls-orig-data -n kube-system --from-file=ca.crt=./ca-certificates.crt
secret/tls-orig-data created

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get secret -n kube-system tls-orig-data
NAME            TYPE     DATA   AGE
tls-orig-data   Opaque   1      5s
# 정책 yaml

# 모니터링 : hubble cli 나 ui 에서 https 이니 L7 정보 확인 불가능
hubble observe --pod mediabot -f

# 
kubectl get pods
kubectl exec -it mediabot -- curl -sL '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: 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

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get cnp
NAME                AGE    VALID
l7-visibility-tls   101s   True

 

 

TLS inspection 기능이 활성화되면, 이전에는 암호화되어 보이지 않았던 HTTPS 트래픽의 L7 레이어 정보(HTTP 헤더, 메서드, URL 등)를 Hubble을 통해 관찰할 수 있게 됩니다.

Tetragon

Tetragon은 Cilium 프로젝트의 일부로, eBPF 기반의 런타임 보안 강화 및 관찰 도구입니다. Linux 커널에서 직접 실행되어 실시간으로 보안 이벤트를 감지하고 대응할 수 있는 강력한 플랫폼을 제공합니다.

감지 가능한 보안 이벤트

Tetragon은 다음과 같은 다양한 보안 이벤트들을 실시간으로 감지할 수 있습니다:

시스템 레벨 이벤트:

  • Process Execution - 프로세스 실행 이벤트
  • Syscall Activity - 시스템 콜 활동 모니터링
  • File Access - 파일 접근 및 I/O 활동
  • Data Access - 저장소 데이터 접근

보안 위험 이벤트:

  • NS Escapes - 네임스페이스 탈출 시도
  • Priv Escalations - 권한 상승 공격
  • Seq Attack - 순차적 공격 패턴

네트워크 활동:

  • HTTP, DNS, TLS - 네트워크 프로토콜 모니터링
  • TCP/IP - 네트워크 연결 추적
  • I/O Activity - 네트워크 및 파일 I/O 활동

Kubernetes 인식 기능

  • 네임스페이스, 포드 등의 Kubernetes ID를 완전히 이해
  • 개별 워크로드와 관련하여 세밀한 보안 이벤트 감지 구성
  • Pod 단위의 정확한 컨텍스트와 함께 이벤트 추적
  • Kubernetes 환경에 최적화된 보안 정책 적용

eBPF 실시간 처리의 장점

커널 레벨 직접 처리:

  • 커널의 eBPF에 정책과 필터링을 직접 적용
  • 사용자 공간으로 이벤트 전송 대신 커널에서 직접 필터링, 차단, 대응
  • 값비싼 컨텍스트 전환 및 웨이크업 방지로 성능 오버헤드 최소화

지능형 이벤트 필터링:

  • 파일, 소켓, 바이너리 이름, 네임스페이스/기능 등 다양한 필터 제공
  • 특정 컨텍스트에서 중요하고 관련성 있는 이벤트만 선별
  • 해당 이벤트만 사용자 공간 에이전트로 효율적 전달

eBPF 유연성과 추적 정책

광범위한 커널 함수 연결:

  • Linux 커널의 모든 함수에 연결 가능
  • 인수, 반환 값, 프로세스 메타데이터(실행 파일 이름, 파일 등) 필터링
  • 추적 정책(Tracing Policy) 작성으로 다양한 보안/관측성 사용 사례 해결

정책의 유연성:

  • 사용자 정의 정책으로 새로운 커널 함수까지 추적 가능
  • 어떤 함수를 추적하고 어떤 필터를 적용할지 엔진에 하드코딩되지 않음
  • 구체적이고 새로운 정책 배포를 자유롭게 생성

보안상 이점:

  • 커널 깊숙이 후킹하여 사용자 공간 애플리케이션의 데이터 구조 조작 방지
  • 시스템 콜 추적의 일반적 문제들 회피:
    • 데이터 오독 방지
    • 공격자의 악의적 변경 차단
    • 페이지 오류 및 사용자/커널 경계 오류로 인한 데이터 누락 방지

eBPF 커널 인식과 실시간 대응

커널 상태 직접 접근:

  • Linux 커널 상태에 직접 접근
  • Kubernetes 인식과 사용자 정책을 결합하여 커널에서 실시간 규칙 생성
  • 프로세스 네임스페이스, 기능, 소켓-프로세스 매핑 등 상세한 주석 제공

즉각적 대응 메커니즘:

  • 애플리케이션 권한 변경 감지 시
  • 해당 프로세스가 시스템 호출을 완료하기 전에 즉각 대응
  • 경고 발생 또는 프로세스 종료 정책 실행

Tetragon 아키텍처

 

데이터 수집 계층:

  • Tetragon Agent: 메인 에이전트 역할
  • Kernel Runtime: 커널 런타임 모니터링
  • Smart Collector: 지능형 데이터 수집
    • Stack Traces, Ring Buffer 관리
    • Metrics, Hash Maps 처리

관측성 출력:

  • Metrics: 성능 지표 및 통계
  • Events: 실시간 이벤트 스트림
  • Logs: 구조화된 로그 데이터
  • Traces: 상세한 추적 정보

외부 시스템 통합:

  • SIEM 시스템과 연동
  • Fluentd 등 로그 수집 도구 연결
  • JSON 형태로 구조화된 이벤트 데이터 제공

Tetragon 설치

# /proc 파일 시스템 액세스 필요.
# 기본적으로 Tetragon은 이벤트 로그의 노이즈를 줄이기 위해 kube-system 이벤트를 필터링합니다. 
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

# 확인
(⎈|HomeLab:N/A) root@k8s-ctr:~# k -n kube-system get deploy tetragon-operator -owide
NAME                READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS          IMAGES                                    SELECTOR
tetragon-operator   0/1     1            0           58s   tetragon-operator   quay.io/cilium/tetragon-operator:v1.5.0   app.kubernetes.io/instance=tetragon,app.kubernetes.io/name=tetragon-operator

(⎈|HomeLab:N/A) root@k8s-ctr:~# k -n kube-system get cm tetragon-operator-config tetragon-config
NAME                       DATA   AGE
tetragon-operator-config   9      65s
tetragon-config            33     65s
(⎈|HomeLab:N/A) root@k8s-ctr:~# 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>          70s   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

(⎈|HomeLab:N/A) root@k8s-ctr:~# k -n kube-system get svc,ep tetragon
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/tetragon   ClusterIP   10.96.187.149   <none>        2112/TCP   73s

NAME                 ENDPOINTS                                                     AGE
endpoints/tetragon   192.168.10.100:2112,192.168.10.101:2112,192.168.10.102:2112   73s
(⎈|HomeLab:N/A) root@k8s-ctr:~# k -n kube-system get svc,ep tetragon-operator-metrics
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME                                TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/tetragon-operator-metrics   ClusterIP   10.96.188.26   <none>        2113/TCP   77s

NAME                                  ENDPOINTS           AGE
endpoints/tetragon-operator-metrics   172.20.1.124:2113   77s

(⎈|HomeLab:N/A) root@k8s-ctr:~# k -n kube-system get pod -l app.kubernetes.io/part-of=tetragon -owide
NAME                                READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
tetragon-84m7b                      2/2     Running   0          81s   192.168.10.101   k8s-w1    <none>           <none>
tetragon-9lfz2                      2/2     Running   0          81s   192.168.10.102   k8s-w2    <none>           <none>
tetragon-operator-58c6ddf88-k7vbc   1/1     Running   0          81s   172.20.1.124     k8s-w1    <none>           <none>
tetragon-zpzs8                      2/2     Running   0          81s   192.168.10.100   k8s-ctr   <none>           <none>

(⎈|HomeLab:N/A) root@k8s-ctr:~# k -n kube-system get pod -l app.kubernetes.io/name=tetragon
NAME             READY   STATUS    RESTARTS   AGE
tetragon-84m7b   2/2     Running   0          86s
tetragon-9lfz2   2/2     Running   0          86s
tetragon-zpzs8   2/2     Running   0          86s
(⎈|HomeLab:N/A) root@k8s-ctr:~# kc -n kube-system describe pod -l app.kubernetes.io/name=tetragon
...
Containers:
  export-stdout:
    Container ID:  containerd://49be8a9a11ce98c188e0cc964a09454cae39702fad90cc43f5d07e3f6f2e3ba5
    Image:         quay.io/cilium/hubble-export-stdout:v1.1.0

  tetragon:
    Container ID:  containerd://c1956aef6236c2359e4d9905b3c2bceeb1f3b4069ff086eb3c0792c731b3b7bf
    Image:         quay.io/cilium/tetragon:v1.5.0
...

Tetragon 실습

데모 애플리케이션 배포

kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.18.1/examples/minikube/http-sw-app.yaml
kubectl get pods

실행 모니터링

# 여러 노드가 있는 클러스터에서는 사용하는 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-62svb

# 터미널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
# 압축 실행 이벤트에는 이벤트 유형, 포드 이름, 바이너리, 인수가 포함됩니다. 종료 이벤트에는 반환 코드가 포함됩니다.
🚀 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              


## 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'
kubectl exec -ti xwing -- bash -c 'curl https://httpbin.org'
kubectl exec -ti xwing -- bash -c 'cat /etc/passwd'

파일 엑세스 모니터링 설정

# 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
...
kubectl apply -f https://raw.githubusercontent.com/cilium/tetragon/main/examples/quickstart/file_monitoring.yaml

(⎈|HomeLab:N/A) root@k8s-ctr:~# kubectl get tracingpolicy
NAME                       AGE
file-monitoring-filtered   4s

Tetragon 파일 액세스 이벤트 관찰

# 터미널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}'))


# 터미널2 : 이벤트를 생성하기위애 정책에 참조된 중요한(민감한) 파일을 읽어보세요
kubectl exec -ti xwing -- bash -c 'cat /etc/shadow'

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

마치며

이번 8주차 실습을 통해 Cilium의 고급 보안 기능들과 eBPF 기반 런타임 보안 도구들에 대해 심도 있게 학습했습니다.

 

이번 실습에서는 DNS 기반 네트워크 정책을 통한 외부 접근 제어, WireGuard를 활용한 투명한 암호화, TLS Inspection을 통한 암호화 트래픽 분석, 그리고 Tetragon을 활용한 실시간 보안 모니터링까지 Cilium의 포괄적인 보안 생태계를 직접 체험해보았습니다.

 

특히 DNS FQDN 정책을 통해 특정 도메인만 허용하는 세밀한 외부 접근 제어, WireGuard 암호화로 노드 간 통신을 완전히 암호화하는 방법, 그리고 TLS 인터셉션을 통해 HTTPS 트래픽도 L7 레벨에서 모니터링할 수 있는 강력한 기능들을 확인할 수 있었습니다.

 

또한 Tetragon을 통해 eBPF의 힘을 활용하여 커널 레벨에서 실시간으로 프로세스 실행, 파일 접근, 시스템 콜 등의 보안 이벤트를 감지하고 즉각 대응할 수 있는 최첨단 보안 플랫폼의 동작 원리를 이해할 수 있었습니다.

 

실습을 통해 Zero Trust 네트워크 보안 모델 구현 방법과 eBPF 기반 런타임 보안의 실제 적용 사례를 체험해볼 수 있었으며, 특히 Kubernetes 환경에서 발생할 수 있는 다양한 보안 위협에 대한 실질적인 대응 방안들을 학습할 수 있어서 매우 유익했습니다.

 

어느덧 Cilium 스터디 8주 과정이 마무리되었습니다.

이전에 간단히 접했던 Cilium에 대해 이번 스터디를 통해 훨씬 더 깊이 있게 이해할 수 있었던 소중한 시간이었습니다.

 

스터디를 이끌어 주시고 항상 열정적으로 도와주신 가시다 님께 진심으로 감사드리며, 이번 주차 글을 여기서 마치겠습니다.

긴 글 읽어주셔서 감사합니다 :)

 

반응형

'클라우드 컴퓨팅 & NoSQL > [Cilium Study] 실리움 스터디' 카테고리의 다른 글

[7주차 - Cilium 스터디] K8S/Cilium Performance (25.08.24)  (1) 2025.08.30
[6주차 - Cilium 스터디] Cilium ServiceMesh (25.08.17)  (0) 2025.08.23
[5주차 - Cilium 스터디] BGP Control Plane & ClusterMesh (25.08.10)  (4) 2025.08.16
[4주차 - Cilium 스터디] Networking - 노드에 파드들간 통신 2 & K8S 외부 노출 (25.08.03)  (4) 2025.08.08
[3주차 - Cilium 스터디] Networking (25.07.27)  (0) 2025.08.02
    devlos
    devlos
    안녕하세요, Devlos 입니다. 새로 공부 중인 지식들을 공유하고, 명확히 이해하고자 블로그를 개설했습니다 :) 여러 DEVELOPER 분들과 자유롭게 지식을 공유하고 싶어요! 방문해 주셔서 감사합니다 😀 - DEVLOS -

    티스토리툴바