Kubernetes는 기본적으로 CNI 플러그인 및 kube-proxy에 의해 Pod와 Pod 간 통신이 Flat하게 가능한 네트워킹 아키텍처이다.
이는 MSA 구조 및 분산된 환경에서 매우 유용하게 작용하지만, 반대로 기본적으로 별도의 방화벽 등을 구성하지 않기 때문에 하나의 Pod가 공격자에 의해 탈취되면 클러스터 내 모든 Pod를 비롯한 리소스에 임의로 접근할 수 있다.
이는 꽤나 자주 발생되는 보안 취약 포인트로, 이 포스팅에선 Zero Trust 및 Microsegmentation 구조로 이러한 보안 인시던트 문제를 완화해볼 것이다.
1. Zero Trust 및 Microsegmentation
위와 같은 문제를 완화하기 위해 Zero Trust 모델을 도입해야 한다. Zero Trust는 기본적으로 모든 요청이나 접속에 대해 위협으로 간주하여 차단한다.
이는 같은 네트워크 내부의 애플리케이션이나 서비스도 마찬가지로, 이 애플리케이션이 공격자에 의해 탈취될 가능을 절대적으로 배제할 수 없기 때문이다.
이에 따라 최소 권한 원칙(Principle of Least Privilege, PoLP)을 도입하고 지속적으로 인증/인가를 검증을 통해 침해를 예방할 수 있다.
MSA 아키텍처에서 Zero Trust 철학 중 가장 중요한 개념은 마이크로세그멘테이션(Microsegmentation)인데, 이는 운영 환경에서 네트워크를 애플리케이션이나 워크로드 등에 따라 작게 세분화하고 각 영역(Chunk)을 지정한다.
여기에 Zero Trust 철학을 도입한다면 공격자가 하나의 영역에 진입하더라도 다른 영역으로의 이동(Lateral Movement)을 예방한다. 이를 위해선 단순히 IP/Protocol 수준의 제어를 넘어 애플리케이션/워크로드 단위의 세밀한 제어가 필요하다.
Microsegmentation 도입시 Zero Trust 철학을 쉽게 도입할 뿐만 아니라, 적합성 유지, 복잡한 Observability의 간소화, 정책 간소화 등의 여러 이점을 얻을 수 있을 것이다.
이러한 Microsegmentation는 그 대상에 따라 애플리케이션, 환경, 프로세스/가상머신이나 N계층, 사용자 세분화 등의 여러 유형으로 영역을 구분할 수 있다. (이 포스팅에선 애플리케이션 레벨의 Microsegmentation을 다룬다.)
2. L3/L4 및 L7의 Microsegmentation
앞서 애플리케이션이나 워크로드의 계층별로 Segmentation하는 것은 추상적인 설명이고, 일반적인 경우 L3/L4 및 L7에 대한 정책을 지정하는 것 부터 시작한다.
L3/L4에 대한 정책은 IP/CIDR, 포트 및 프로토콜을 통해 연결/접속 자체를 통제하는 최소 경계이고, L7는 해당 요청에 대한 경로, HTTP 메서드, gRPC 메타데이터, JWT Claims 등을 통해 세부적으로 제어할 수 있다.
이 포스팅에선 L3/L4에 대한 정책을 eBPF 기반의 Cilium NetworkPolicy Enforcement를 통해 작성해보고, L7에 대한 정책을 Istio(Envoy Sidecar)의 mTLS Strict 및 AuthorizationPolicy를 적용하여 제한하고 제어해보겠다.
NetworkPolicy Enforcement
기본적으로 Kubernetes에선 NetworkPolicy를 통해 L3/L4 네트워킹 정책을 구성할 수 있다. NetworkPolicy 리소스는 네임스페이스 스코프의 리소스로 Pod에 대한 Inbound(Ingress) 및 Outbound(Egress) 트래픽을 제어한다.
정책이 적용된 Pod는 해당 정책에 명시된 트래픽만 허용되는 화이트리스트의 구조로, 예시의 NetworkPolicy 정책은 아래와 같다.
위 예시는 해당 네임스페이스의 모든 트래픽을 차단하는 정책으로, 이는 Zero Trust의 기본적인 원칙이다. 두번째 allow-frontend-to-api 정책은 app=frontend Label이 붙은 Pod에게만 api-server에 8080 포트로 접근할 수 있는 권한을 준다.
이러한 NetworkPolicy는 CNI Plugin에 따라 상의하며, 아래의 Terraform 코드는 EKS 환경에선 VPC CNI Addon(Plugin)에서 NetworkPolicy Enforcement Engine을 제공한다. (기본적으로 비활성화되어 있으며 따로 활성화를 해야한다.)
이는 VPC CNI에 포함된 Network Policy Engine으로, VPC CNI가 아닌 Calico CNI 또는 Cilium CNI 등의 다른 CNI에서도 Network Policy를 지원한다.
다만 모든 CNI Plugin에서 NetworkPolicy를 지원하지는 않는다. 그 예시로 Flannel CNI는 Overlay Networking에 중점을 두고 NetworkPolicy Enforcement Engine은 지원하지 않는다.
(L3/L4) Cilium NetworkPolicy Engine
NetworkPolicy Enforcement Engine으로서 자주 사용되는 CNI Plugin엔 앞서 언급한 Calico CNI 및 Cilium CNI가 존재한다. Calico CNI의 Engine은 Tier 개념을 적용하여 정책의 우선순위를 지정할 수 있는 특이 사항이 있다.
네트워킹 CNI 자체를 Cilium으로 대체하거나 기존 CNI(예: VPC CNI)를 유지한채로 Cilium CNI를 Data Path/NetworkPolicy Engine으로서 함께 사용하는 Chaining 모드 등을 사용해볼 수 있다.
이 포스팅에선 Cilium을 NetworkPolicy Enforcement Engine으로서 Chaining하여 사용하도록 하겠다. Calico CNI와는 다르게 Tier나 Order의 개념이 없으며, 기본적으로 Allow 트래픽에 대해 합집합(Union)으로 동작하며 Deny 정책을 우선시한다.
Cilium NetworkPolicy Engine의 특징 중 하나는 Pod IP가 아닌 Label 기반의 Security Identity를 통해 정책을 적용한다.
기존의 Calico CNI와 같이 iptables 기반의 CNI에서는 Pod가 재생성되면 IP가 변경되기 때문에 iptables Rule도 재구성이 필요하다. 즉 Pod의 수가 늘어나고 스케일링이 잦은 경우엔 iptables가 선형 O(N)으로 동작하여 성능이 저하될 수 있다.
이에 비해 Cilium은 Label을 기반으로 하나의 Identity를 생성한다.
labels: app: frontend env: prod# Identity: 12345
이에 대해 Enforcement가 eBPF → Source Identity / Destination Identity 확인 → Policy 적용으로 진행되기 때문에 이로 인한 오버헤드가 비교적 많이 감소한다.
iptables가 선형적으로 동작하여 O(N)으로 동작한다면, eBPF 기반의 Cilium은 Kernel 내에서 Hash Map 기반으로 동작하기 때문에 사실상 O(1) 시간복잡도로 동작할 수 있다.
(L7) Istio and Envoy Sidecar
지금까지 Cilium을 통한 L3/L4에 대한 Zero Trust Microsegmentation에 대해 설명하였다. L3/L4만 막았다고 해서 L7를 간과하면 안되는 것이, 서비스 구동을 위해 필수적으로 필요한 트래픽은 어쩔 수 없이 허용해야 하고 이를 공격 포인트로 침해될 수 있다.
때문에 앞서 언급하였듯 L7에 mTLS나 JWT Claims 인증 등을 도입하는 것이 적절하고, 이에 대해선 Istio Service Mesh와 Envoy Sidecar 방식을 사용하는 것이 Best Practices이다. (Ambient Mode는 별도로 찾아보길 바란다.)
Cilium CNI도 Node wide Envoy Proxy를 통해 L7 제어 및 정책 적용이 가능하지만, L7 제어는 Cilium의 주된 목적은 아니며 L7 제어 및 정책 적용에 대해선 Istio와 같은 Service Mesh 사용하는 것이 더 적절할 수 있다.
이 포스팅에선 Istio를 활용하여 Service Mesh를 구축하면서 각 마이크로서비스간 mTLS Principal 기반 L7 AuthZ을 구현하고 mTLS를 구성해볼 것이다. 필요시 JWT 또는 OIDC 등을 도입해볼 수 있다.
두 정책은 기본적인 동작이 다른데, CiliumNetworkPolicy는 정책이 적용되는 순간 기본적으로 차단되고 ingressDeny/egressDeny를 통해 명시적으로 차단도 추가할 수 있다. 단, Default Deny + Allow List가 기본적이며 필요한 경우 Deny로 예외를 보강한다.
Istio AuthorizationPolicy는 정책이 없으면 기본 허용이며 Allow 정책은 매칭되는 요청만 통과, Deny 정책은 매칭되는 요청만 차단한다.
즉 Zero Trust 관점에선 Cilium을 통해 연결/요청 경계를 최소화하고 Istio는 명시적 Allow로 요청 범위를 좁히는 방식이 가장 안전하다.
3. Demo Architecture
예시의 아키텍처는 아래와 같이 전형적인 MSA 데모로 여기에 특정 프로토콜(L3/L4)이나 특정 경로(L7)만 허용되도록 해보고 mTLS Principal 기반 AuthorizationPolicy까지 적용해보겠다. 아래는 실습 중심으로, 이론적인 내용을 깊게 다루지 않는 점 참고 바란다.
이때 L3/L4 Cilium에 대한 Observability 도구는 Hubble UI, L7 Istio에 대한 Observability 도구는 Kiali를 사용해보도록 하겠다.
또한 실습을 위해 필요한 부가적인 CLI 도구는 아래와 같다. 이러한 도구 없이 직접 Helm을 통해 설치하거나 Kubectl을 사용해볼 수 있지만 편의성을 위해 사용하겠다.
cilium
hubble
istioctl
셋 모두 같은 이름으로 Homebrew를 통해 설치할 수 있다. MacOS 이외의 운영체제나 아키텍처는 각 문서를 참조하자.
EKS Cluster
예시의 EKS Cluster는 아래와 같다. eksctl이 아닌 Terraform 등으로 프로비저닝해도 무방하다.
다음으로 Cilium에 포함되어 있는 고급 기능인 Gloal NetworkPolicy와 FQDN Policy를 적용해보겠다.
Cilium Global/FQDN NetworkPolicy
Cilium은 클러스터 단위에 NetworkPolicy(CNP)를 적용할 수 있는 리소스인 CiliumClusterwideNetworkPolicy를 지원한다. 아래의 예제는 Istio/Envoy Health Check 포트인 TCP 15021(HTTP)를 클러스터 레벨에서 허용하는 정책의 예제이다.
만약 특정 Pod가 공격자에 의해 탈취되어 네트워크 트래픽이 스니핑될 가능성이 존재할 수 있다. 이때 mTLS는 두 마이크로서비스간 암호화(TLS)가 되므로 이러한 문제를 완화할 수 있다. 추가적으로 이를 통해 아래의 mTLS Principal Authorization까지 가능하므로 mTLS를 도입하였다.
Istio mTLS Principal Authorization
Istio는 SPIFFE Identity 기반의 AuthorizationPolicy를 지원한다. SPIFFE Identity는 mTLS 통신의 핸드셰이크에서 인증서로 전달되는 Principal(주체)에 대한 식별 정보로, 이를 통해 ServiceAccount 기반의 Principal Policy를 구성할 수 있다.
이 포스팅에선 단순한 예시로 하나의 SA를 기반으로 AuthorizationPolicy를 구성한다. 프로덕션에선 이를 권장하지 않으며, 각 마이크로서비스에 대한 별개의 SA를 구성하는 것이 Best Practices이다.