DNS 기반 정책으로 외부 접근 잠그기
DNS 기반 정책으로 외부 접근 잠그기 (Locking Down External Access with DNS-Based Policies)
이 문서는 Kubernetes 파드에 대해 DNS 기반 보안 정책을 적용하도록 Cilium을 사용하는 방법을 소개해요. DNS를 영구적인 서비스 식별자로 삼아, IP 주소가 자주 바뀌는 외부 서비스도 안전하게 접근을 통제할 수 있습니다.
본문
아직 Cilium & Hubble 소개를 읽지 않았다면 먼저 읽어보는 것을 권장해요.
막히는 부분이 있다면 Cilium Slack에 질문을 올리는 것이 가장 좋은 방법이에요. 전 세계에 Cilium 기여자들이 있기 때문에 도움을 줄 사람이 거의 항상 있답니다.
Cilium 설정
아직 Cilium을 설정하지 않았다면 Cilium 빠른 설치 가이드를 따라 Kubernetes 클러스터를 빠르게 부트스트랩하고 Cilium을 설치하세요. 확실하지 않다면 minikube 경로를 선택하면 5분 이내에 준비될 거예요.
데모 애플리케이션 배포
DNS 기반 정책은 Kubernetes 클러스터 밖에서 실행되는 서비스에 대한 접근을 제어하는 데 매우 유용해요. DNS는 AWS, Google, Twilio, Stripe 등이 제공하는 외부 서비스와, Kubernetes 밖의 사설 서브넷에서 실행되는 데이터베이스 클러스터 같은 내부 서비스 모두에 대해 영구적인 서비스 식별자 역할을 합니다. CIDR이나 IP 기반 정책은 외부 서비스와 연결된 IP가 자주 변경될 수 있어 다루기 어렵고 유지보수가 힘듭니다. Cilium DNS 기반 정책은 접근 제어를 지정하기 쉬운 메커니즘을 제공하면서, Cilium이 DNS-to-IP 매핑 추적이라는 더 어려운 부분을 관리해요.
이 가이드에서는 다음을 배울 수 있어요:
- DNS 기반 정책을 사용해 클러스터 밖 서비스로의 egress 접근 제어
- 패턴(또는 와일드카드)을 사용해 DNS 도메인 하위 집합 화이트리스트 지정
- 외부 서비스 접근을 제한하기 위해 DNS, 포트, L7 규칙 결합
Star Wars 테마의 예제에 맞춰, 제국의 mediabot 파드가 제국의 git 저장소를 관리하기 위해 GitHub에 접근해야 하는 간단한 시나리오를 사용할 거예요. 이 파드들은 다른 외부 서비스에는 접근할 수 없어야 합니다.
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes-dns/dns-sw-app.yaml
$ kubectl wait pod/mediabot --for=condition=Ready
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
pod/mediabot 1/1 Running 0 14s
DNS Egress 정책 적용
다음 Cilium 네트워크 정책은 mediabot 파드가 api.github.com에만 접근하도록 허용해요.
Generic
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: "*"
OpenShift
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": openshift-dns
toPorts:
- ports:
- port: "5353"
protocol: ANY
rules:
dns:
- matchPattern: "*"
Note
OpenShift 사용자는 정책을
openshift-dns네임스페이스(kube-system대신)에 맞게 수정하고,k8s:k8s-app=kube-dns라벨에 대한 매치를 제거하며, 포트를 5353으로 변경해야 해요.
정책을 자세히 살펴봅시다:
- 첫 번째 egress 섹션은
toFQDNs: matchName명세를 사용해api.github.com으로의 egress를 허용해요. 대상 DNS는 규칙에 지정된 이름과 정확히 일치해야 합니다.endpointSelector는 라벨class: mediabot, org:empire가 있는 파드만 egress 접근을 갖도록 허용합니다. - 두 번째 egress 섹션(
toEndpoints)은mediabot파드가kube-dns서비스에 접근하도록 허용해요.rules: dns는 Cilium이 지정된 패턴과 일치하는 DNS 조회를 검사·허용하도록 지시합니다. 이 경우 모든 DNS 쿼리를 검사·허용해요.
이 정책으로 mediabot은 kube-dns 외에는 어떤 내부 클러스터 서비스에도 접근할 수 없다는 점을 기억하세요. 내부 클러스터 서비스 접근을 제어하는 정책에 대한 자세한 내용은 네트워크 정책 개요를 참고하세요.
정책을 적용해 봅시다:
kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes-dns/dns-matchname.yaml
정책을 테스트하면 mediabot은 api.github.com에 접근할 수 있지만 support.github.com 같은 다른 외부 서비스에는 접근할 수 없어요.
$ 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
curl: (28) Connection timed out after 5000 milliseconds
command terminated with exit code 28
패턴을 사용하는 DNS 정책
위 정책은 DNS 도메인 이름의 정확한 일치를 기반으로 DNS 접근을 제어했어요. 종종 도메인 하위 집합에 대한 접근을 허용해야 할 때가 있습니다. 위 예제에서 mediabot 파드가 *.github.com 패턴처럼 모든 GitHub 서브 도메인에 접근해야 한다고 가정해 봅시다. toFQDN 규칙을 matchName 대신 matchPattern으로 바꾸면 쉽게 구현할 수 있어요.
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "fqdn"
spec:
endpointSelector:
matchLabels:
org: empire
class: mediabot
egress:
- toFQDNs:
- matchPattern: "*.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.20.2/examples/kubernetes-dns/dns-pattern.yaml
DNS가 *.github.com 패턴과 일치하는 여러 GitHub 서비스에 mediabot이 접근할 수 있는지 테스트하세요. 패턴의 *.이 DNS 이름에 서브 도메인이 하나 이상 있어야 하므로, 이 정책이 github.com에 대한 접근은 허용하지 않는다는 점을 테스트를 통해 명심하세요. matchName과 matchPattern 절을 더 추가하면 간단히 접근을 확장할 수 있어요. (패턴과 이름으로 DNS 규칙을 지정하는 방법은 DNS 기반 정책을 참고하세요.)
$ kubectl exec mediabot -- curl -I -s https://support.github.com | head -1
HTTP/1.1 200 OK
$ kubectl exec mediabot -- curl -I -s https://gist.github.com | head -1
HTTP/1.1 302 Found
$ kubectl exec mediabot -- curl -I -s --max-time 5 https://github.com | head -1
curl: (28) Connection timed out after 5000 milliseconds
command terminated with exit code 28
DNS, 포트, L7 규칙 결합
DNS 기반 정책은 포트(L4)와 API(L7) 규칙과 결합해 접근을 더욱 제한할 수 있어요. 우리 예제에서는 mediabot 파드가 443 포트에서만 GitHub 서비스에 접근하도록 제한할 거예요. 아래 정책의 toPorts 섹션이 DNS 기반 정책과 함께 포트 기반 제한을 구현합니다.
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.20.2/examples/kubernetes-dns/dns-port.yaml
테스트해 보면, 443 포트의 https://support.github.com 접근은 성공하지만 80 포트의 http://support.github.com 접근은 거부됩니다.
$ kubectl exec mediabot -- curl -I -s https://support.github.com | head -1
HTTP/1.1 200 OK
$ kubectl exec mediabot -- curl -I -s --max-time 5 http://support.github.com | head -1
curl: (28) Connection timed out after 5001 milliseconds
command terminated with exit code 28
Cilium L4 및 L7 네트워크 정책에 대한 자세한 내용은 레이어 4 정책과 레이어 7 정책을 참고하세요.
정리 (Clean-up)
kubectl delete -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes-dns/dns-sw-app.yaml
kubectl delete cnp fqdn
제한 사항 (Limitations)
기본적으로 Cilium은 DNS 정책 적용을 위해 에이전트 내장 DNS 프록시를 사용해요. 고가용성을 원한다면 Standalone DNS Proxy (alpha) 사용을 고려하세요 (alpha 기능).