Cilium Security
보안 계층
- L3 (Identity 기반): Pod/엔드포인트의 Security Identity를 기반으로 통신 제어
- L4 (Port 레벨): 특정 포트 단위 접근 제어
- L7 (애플리케이션 프로토콜): Envoy Proxy 활용, HTTP/gRPC/Kafka 등 프로토콜 인식 기반 정책
보안 정책 특징
- 상태 저장 정책 (stateful)
- 세션 기반 프로토콜에 대해 요청 패킷 허용 시, 응답 패킷은 자동 허용
- 적용 방향: Ingress(수신) / Egress(송신) 구분 적용
- 기본 정책 동작
- 정책 없음 → 기본적으로 모든 통신 허용
- 첫 정책이 로드되면 → 허용 기반(whitelist) 모드로 전환, 명시되지 않은 트래픽은 차단
- L4 정책 없음 → 모든 포트 허용
- L4 정책 존재 → 지정되지 않은 포트는 자동 차단
Envoy Proxy 통합
- Cilium은 Envoy 프록시를 내장하여 L7 정책 처리
- HTTP path, method, header, gRPC 메소드, Kafka topic 수준까지 제어 가능
투명한 암호화 (Transparent Encryption)
- IPSec
- 호스트 간 트래픽 암호화 지원
- 제약: BPF 호스트 라우팅과 미호환, 터널당 CPU 코어 하나에 제한
- WireGuard
- 더 단순하고 고성능, 커널 내 암호화 지원
- 단, 터널 모드에서는 두 번 캡슐화됨
Identity란?
- 모든 엔드포인트(Pod/컨테이너) 는 Security Relevant Labels를 기반으로 고유 ID를 부여받음
- 같은 레이블 집합을 가진 엔드포인트는 동일한 Identity를 공유
- Identity는 클러스터 전체에서 유효 (노드가 달라도 동일 레이블 → 동일 Identity)
Identity 동작 원리
- Pod/컨테이너 시작 시
- Cilium이 컨테이너 런타임 이벤트를 감지 → Endpoint 생성
- Endpoint의 Labels 기반으로 Identity 조회/할당
- Labels 변경 시
- Endpoint는 waiting-for-identity 상태로 전환
- 새로운 Identity를 분산 키-값 저장소에서 재할당
- 네트워크 정책(Policy)이 즉시 재적용
- 분산 키-값 저장소(KV store)
- Label 집합을 키로 사용
- 존재하지 않으면 새로운 Identity 생성
- 이미 존재하면 해당 Identity 반환
- 원자적 연산을 통해 클러스터 전역에서 일관성 보장
Identity와 Policy
- Cilium 정책은 Identity 기반으로 정의됨
- Pod Labels → Identity → Policy 적용으로 이어짐
- Label 변경 → Identity 변경 → Policy 자동 재평가
Reserved Identity
- Cilium이 관리하지 않는 엔드포인트와 통신을 허용하기 위해 예약 ID 사용
- 예: reserved:world, reserved:host, reserved:init
예시
- Pod simple-pod 생성 → Identity 26830 할당
- Label 변경 (kubectl label pod simple-pod run=not-simple-pod --overwrite) → Identity가 8710으로 변경
- kubectl get ciliumendpoints 로 Identity 확인 가능
- 변경된 Identity는 즉시 정책 적용에 반영됨
주의사항
- 대규모 클러스터에서 Label을 자주 변경하면 Identity 재할당이 빈번 → 성능 저하 유발
- 따라서 Identity-relevant labels는 최소화/제한하는 것이 권장됨
Cilium Security 실습
Locking Down External Access with DNS-Based Policies
→ 클러스터 외부 서비스 접근 제어를 DNS 이름 기반으로 수행
- 전통적인 CIDR/IP 기반 정책:
- 외부 서비스(IP)가 자주 변경됨 → 관리 복잡, 번거로움
- Cilium DNS 기반 정책:
- Cilium이 DNS ↔ IP 매핑을 자동 추적
- 관리자는 **도메인 이름 패턴(와일드카드 포함)**으로만 정책 작성 → 단순화
목표
- DNS 기반 정책으로 외부 서비스 접근 제어
- 예: *.example.com 만 허용
- DNS 패턴/와일드카드 사용
- 특정 도메인 하위 집합만 허용 가능
- 예: *.google.com 허용, 나머지 차단
- DNS + Port + L7 규칙 결합
- 도메인 + 특정 포트 + L7 HTTP 규칙까지 조합
- 예: *.example.com:443 HTTPS만 허용
SampleApp 배포
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
DNS Egrees 정책 적용 1
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
외부 통신 확인
kubectl exec mediabot -- curl -I -s https://api.github.com | head -1
DNS Egress 정책 적용 2
모든 GibHub 하위 도메인에 엑세스
fqdn 캐시 초기화 및 정책 삭제
kubectl delete cnp fqdn
c1 fqdn cache clean -f
c2 fqdn cache clean -f
dns-patthern.yml 내용
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: "*"
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: "*"
샘플 애플리케이션 배포 및 통신 문제 확인
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: webpod
spec:
replicas: 2
selector:
matchLabels:
app: webpod
template:
metadata:
labels:
app: webpod
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- sample-app
topologyKey: "kubernetes.io/hostname"
containers:
- name: webpod
image: traefik/whoami
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: webpod
labels:
app: webpod
spec:
selector:
app: webpod
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
EOF
# k8s-ctr 노드에 curl-pod 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: curl-pod
labels:
app: curl
spec:
nodeName: k8s-ctr
containers:
- name: curl
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
통신 확인
# 배포 확인
kubectl get deploy,svc,ep webpod -owide
kubectl get endpointslices -l app=webpod
kubectl get ciliumendpoints # IP 확인
# 통신 문제 확인
kubectl exec -it curl-pod -- curl -s --connect-timeout 1 webpod | grep Hostname
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
# 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
WireGuard
WireGuard 터널 엔드포인트는 51817 각 노드의 UDP 포트에 노출.
wiregurad 설정
helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
--set encryption.enabled=true --set encryption.type=wireguard
kubectl -n kube-system rollout restart ds/cilium
wg 정보 확인
wg show
curl 호출 후 tcpdump 확인
kubectl exec -it curl-pod -- curl webpod
kubectl exec -it curl-pod -- curl webpod
Cilium TLS Interception Demo
Cilium TLS Interception 설정
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
- 내부 CA 및 인증서 생성
- 자체 서명된 내부 인증 기관(CA) 생성
- Cilium이 TLS를 가로채기 위해 사용할 CA 서명 인증서 준비
- Cilium 네트워크 정책 작성
- DNS 기반 규칙을 이용해 어떤 외부 트래픽을 가로챌지 선택
- 예: swapi.dev 도메인에 대한 HTTPS 요청만 TLS 가로채기
- cilium monitor 활용
- 트래픽 모니터링 시, 평소에는 볼 수 없었던 HTTP 요청 세부 정보 출력 확인
- 예: GET /api/people/1/ 요청 등
- TLS 암호화된 트래픽도 Cilium 정책과 관측에 포함 가능
- DNS 기반 정책 + TLS 가로채기 조합으로, HTTPS 요청까지 L7 레벨 제어 가능
- 네트워크 관리자가 외부 서비스 HTTPS 트래픽에 대해 세밀한 보안·가시성을 확보
단일 Pod 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
kubectl wait pod/mediabot --for=condition=Ready
TLS 키 및 인증서 생성 및 설치
- 이제 TLS를 이해하고 Cilium을 구성하여 TLS 가로채기를 사용했으므로 유틸리티를 사용하여 적절한 키와 인증서를 생성하는 구체적인 단계를 살펴보겠습니다.
- 다음 이미지는 생성되거나 복사되는 암호화 데이터가 포함된 다양한 파일과 시스템의 어떤 구성 요소가 해당 파일에 액세스해야 하는지 설명합니다.
- 로컬 시스템에 openssl이 이미 설치되어 있다면 사용할 수 있지만, 그렇지 않은 경우 간단한 단축키를 사용하여 cilium 포드 내에서 실행한 후 결과 명령을 실행할 수 있습니다.
- Kubernetes 시크릿을 생성하거나 포드에 복사할 때 cilium 포드에서 결과 파일을 복사하는 데 사용할 수 있습니다 .
내부기관 (CA) 만들기
openssl genrsa -des3 -out myCA.key 2048
openssl req -x509 -new -nodes -key myCA.key -sha256 -days 1825 -out myCA.crt
확인
openssl x509 -in myCA.crt -noout -text
지정된 DNS 이름에 대한 개인 키 및 인증서 서명 요청 생성
openssl genrsa -out internal-httpbin.key 2048
openssl req -new -key internal-httpbin.key -out internal-httpbin.csr
Common Name (e.g. server FQDN or YOUR name) []:httpbin.org
CA를 사용하여 DNS 이름에 대한 서명된 인증서 생성
openssl x509 -req -days 360 -in internal-httpbin.csr -CA myCA.crt -CAkey myCA.key -CAcreateserial -out internal-httpbin.crt -sha256
openssl x509 -in internal-httpbin.crt -noout -text
kubectl create secret tls httpbin-tls-data -n kube-system --cert=internal-httpbin.crt --key=internal-httpbin.key
클라이언트 포드 내에서 신뢰할 수 있는 CA로 내부 CA 추가
- CA 인증서가 클라이언트 포드에 저장된 후에도 애플리케이션에서 사용하는 TLS 라이브러리가 CA 파일을 인식하는지 확인해야 합니다. 대부분의 Linux 애플리케이션은 Linux 배포판과 함께 제공되는 신뢰할 수 있는 CA 인증서 세트를 자동으로 사용합니다. 이 가이드에서는 Ubuntu 컨테이너를 클라이언트로 사용하므로 Ubuntu별 지침에 따라 업데이트합니다. 다른 Linux 배포판은 다른 메커니즘을 사용합니다. 또한, 개별 애플리케이션은 OS 인증서 저장소를 사용하는 대신 자체 인증서 저장소를 활용할 수 있습니다. Java 애플리케이션과 aws-cli가 대표적인 예입니다. 자세한 내용은 애플리케이션 또는 애플리케이션 런타임 설명서를 참조
kubectl exec -it mediabot -- ls -l /usr/local/share/ca-certificates/
# 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/
# /etc/ssl/certs/ca-certificates.crt에 있는 신뢰할 수 있는 인증 기관의 글로벌 세트에 이 인증서를 추가하는 Ubuntu 전용 유틸리티 실행
kubectl exec -it mediabot -- ls -l /etc/ssl/certs/ca-certificates.crt # 사이즈, 생성/수정 날짜 확인
kubectl exec mediabot -- update-ca-certificates
kubectl exec -it mediabot -- ls -l /etc/ssl/certs/ca-certificates.crt # 사이즈, 생성/수정 날짜 확인
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
kubectl get secret -n kube-system tls-orig-data
Apply DNS and TLS-aware Egress Policy
- 지금까지 TLS 검사를 활성화하기 위한 키와 인증서를 생성했지만, Cilium에 어떤 트래픽을 가로채서 검사할지 알려주지 않았습니다. 이 작업은 다른 Cilium 네트워크 정책에 사용되는 것과 동일한 Cilium 네트워크 정책 구조를 사용하여 수행됩니다.
- 다음 Cilium 네트워크 정책은 Cilium이 mediabot포드와 . 사이의 통신에 대해 HTTP 인식 검사를 수행해야 함을 나타냅니다 httpbin.org.
Cilium Network Policy 배포
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: "*"
Tetragon
- kube-burner
- Go 기반 바이너리
- 리소스 생성/삭제/패치/조회 대규모 반복
- Prometheus 메트릭 수집, SLA/SLI 알림
- 옵션으로 QPS, burst, jobIterations, replicas 등 부하 패턴 제어
- ClusterLoader2 (CL2)
- Kubernetes 공식 확장성 테스트 프레임워크
- 클러스터에 원하는 상태 집합(예: 1만 Pod) 정의 후, 도달 속도(처리량) 측정
- Prometheus와 연동해 API 지연, Pod 시작 시간, 스케줄러 처리량, 리소스 사용량 측정
- kind 단일 노드 → 최대 5,000 노드 규모까지 검증 가능
네트워크 보안
- Identity 기반 보안
- L3 (Identity), L4 (Port), L7 (Protocol) 단위 정책
- 정책은 stateful → 응답 패킷 자동 허용
- 기본 동작: 정책 없으면 전체 허용, 첫 정책 적용 시 whitelist 모드 전환
- Identity
- 모든 엔드포인트에 Labels 기반 ID 부여
- 같은 Security Labels → 동일 ID 공유
- Labels 변경 시 → Identity 재할당 → 정책 자동 재적용
- 클러스터 전역에서 유일, KV store로 관리
- Envoy Proxy 연계
- HTTP/gRPC/Kafka 등 L7 필터링 및 로깅
- 투명 암호화
- IPSec (제약: 단일 CPU 코어, BPF 라우팅과 충돌)
- WireGuard (커널 네이티브, 고성능)
- 실습 시나리오
- DNS 기반 정책: 외부 서비스 접근을 DNS 이름/와일드카드로 제어
- TLS 가로채기: 내부 CA로 HTTPS 트래픽 복호화, HTTP 세부 정보까지 정책 적용
런타임 보안
- eBPF 기반 실시간 보안/관찰 도구
- 주요 이벤트:
- Process 실행
- System call
- 네트워크/파일 I/O 활동
- 커널 레벨에서 직접 필터링/차단 → 오버헤드 최소화
- Kubernetes 인식: Pod/Namespace 단위로 이벤트 매핑 가능
- 예시: 권한 상승 syscall 발생 → 즉시 경고 또는 프로세스 종료
Tetragon 배포
helm repo add cilium https://helm.cilium.io
helm repo update
helm install tetragon cilium/tetragon -n kube-system
여러 노드가 있는 클러스터에서는 사용하는 Tetragon Pod가 "xwing" Pod와 동일한 노드에 있어야 실행 이벤트를 캡처할 수 있습니다. 이 명령을 사용하면 "xwing" Pod와 동일한 Kubernetes 노드에 있는 Tetragon Pod의 이름을가져올 수 있습니다.
일치하는 Pod을 식별 후 해당 Pod을 대상으로 지정하여 명령을 실행
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
tetragon Pod 에서 curl 실행 후 event 확인
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
Tetragon 파일 액세스 이벤트 관찰
POD=$(kubectl -n kube-system get pods -l 'app.kubernetes.io/name=tetragon' -o name --field-selector spec.nodeName=$(kubectl get pod xwing -o jsonpath='{.spec.nodeName}'))
kubectl exec -ti -n kube-system $POD -c tetragon -- tetra getevents -o compact --pods xwing
파일 이벤트를 생성하기 위해 파일 읽기
kubectl exec -ti xwing -- bash -c 'cat /etc/shadow'
파일 엑세스 정책 제한
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