Star Wars 데모 시작하기
Star Wars 데모 시작하기 (Getting Started with the Star Wars Demo)
Cilium의 네트워크 정책이 어떻게 동작하는지 Star Wars 테마의 데모 애플리케이션으로 체험해 보는 튜토리얼이에요. L3/L4 정책부터 HTTP 인식 L7 정책까지 단계별로 만들어 보면서 배워요.
본문
데모 애플리케이션 배포하기 (Deploy the Demo Application)
Cilium이 배포되고 kube-dns가 올바르게 동작하면 데모 애플리케이션을 배포할 수 있어요.
Star Wars에서 영감을 받은 이 예시에는 deathstar, tiefighter, xwing이라는 세 개의 마이크로서비스 애플리케이션이 있어요. deathstar는 80 포트에서 HTTP 웹서비스를 실행하며, 이 서비스는 두 개의 파드 레플리카에 걸쳐 deathstar로 요청을 로드밸런싱하는 Kubernetes Service로 노출돼요. deathstar 서비스는 제국의 우주선이 착륙 포트를 요청할 수 있도록 착륙 서비스를 제공해요. tiefighter 파드는 전형적인 제국선의 착륙 요청 클라이언트 서비스를 나타내고, xwing은 연합선의 유사한 서비스를 나타내요. 이 파드들은 deathstar 착륙 서비스에 대한 접근 제어를 위한 다양한 보안 정책을 테스트하기 위해 존재해요.
Cilium과 Kubernetes를 위한 애플리케이션 토폴로지
http-sw-app.yaml 파일에는 세 서비스 각각에 대한 Kubernetes Deployment가 들어 있어요. 각 배포는 Kubernetes 라벨 (org=empire, class=deathstar), (org=empire, class=tiefighter), (org=alliance, class=xwing)로 식별돼요. 또한 org=empire, class=deathstar 라벨이 있는 모든 파드에 트래픽을 로드밸런싱하는 deathstar-service도 포함돼요.
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/minikube/http-sw-app.yaml
service/deathstar created
deployment.apps/deathstar created
pod/tiefighter created
pod/xwing created
Kubernetes는 백그라운드에서 파드와 서비스를 배포해요. kubectl get pods,svc를 실행하면 작업 진행 상황을 알 수 있어요. 각 파드는 파드가 준비된 시점인 Running 상태에 도달할 때까지 여러 상태를 거쳐요.
$ kubectl get pods,svc
NAME READY STATUS RESTARTS AGE
pod/deathstar-6fb5694d48-5hmds 1/1 Running 0 107s
pod/deathstar-6fb5694d48-fhf65 1/1 Running 0 107s
pod/tiefighter 1/1 Running 0 107s
pod/xwing 1/1 Running 0 107s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/deathstar ClusterIP 10.96.110.8 <none> 80/TCP 107s
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3m53s
각 파드는 Cilium에서 로컬 cilium 에이전트의 Endpoint(엔드포인트)로 표현돼요. Cilium 파드 안에서 cilium-dbg 도구를 호출해 그것들을 나열할 수 있어요. (단일 노드 설치에서는 kubectl -n kube-system exec ds/cilium -- cilium-dbg endpoint list가 모두 나열하지만, 멀티 노드 설치에서는 같은 노드에서 실행 중인 것만 나열돼요.)
$ kubectl -n kube-system get pods -l k8s-app=cilium
NAME READY STATUS RESTARTS AGE
cilium-5ngzd 1/1 Running 0 3m19s
$ kubectl -n kube-system exec cilium-5ngzd -- cilium-dbg endpoint list
ENDPOINT POLICY (ingress) POLICY (egress) IDENTITY LABELS (source:key[=value]) IPv6 IPv4 STATUS
ENFORCEMENT ENFORCEMENT
232 Disabled Disabled 16530 k8s:class=deathstar 10.0.0.147 ready
k8s:io.cilium.k8s.policy.cluster=default
k8s:io.cilium.k8s.policy.serviceaccount=default
k8s:io.kubernetes.pod.namespace=default
k8s:org=empire
726 Disabled Disabled 1 reserved:host ready
883 Disabled Disabled 4 reserved:health 10.0.0.244 ready
1634 Disabled Disabled 51373 k8s:io.cilium.k8s.policy.cluster=default 10.0.0.118 ready
k8s:io.cilium.k8s.policy.serviceaccount=coredns
k8s:io.kubernetes.pod.namespace=kube-system
k8s:k8s-app=kube-dns
1673 Disabled Disabled 31028 k8s:class=tiefighter 10.0.0.112 ready
k8s:io.cilium.k8s.policy.cluster=default
k8s:io.cilium.k8s.policy.serviceaccount=default
k8s:io.kubernetes.pod.namespace=default
k8s:org=empire
2811 Disabled Disabled 51373 k8s:io.cilium.k8s.policy.cluster=default 10.0.0.47 ready
k8s:io.cilium.k8s.policy.serviceaccount=coredns
k8s:io.kubernetes.pod.namespace=kube-system
k8s:k8s-app=kube-dns
2843 Disabled Disabled 16530 k8s:class=deathstar 10.0.0.89 ready
k8s:io.cilium.k8s.policy.cluster=default
k8s:io.cilium.k8s.policy.serviceaccount=default
k8s:io.kubernetes.pod.namespace=default
k8s:org=empire
3184 Disabled Disabled 22654 k8s:class=xwing 10.0.0.30 ready
k8s:io.cilium.k8s.policy.cluster=default
k8s:io.cilium.k8s.policy.serviceaccount=default
k8s:io.kubernetes.pod.namespace=default
k8s:org=alliance
이 모든 파드에서 ingress와 egress 정책 강제가 여전히 비활성화돼 있는 이유는, 아직 이 파드들 중 어떤 것도 선택하는 네트워크 정책이 가져와지지 않았기 때문이에요.
현재 접근 확인 (Check Current Access)
deathstar 서비스 관점에서, org=empire 라벨이 있는 배만 연결하고 착륙을 요청할 수 있어야 해요. 강제되는 규칙이 없으므로 xwing과 tiefighter 모두 착륙을 요청할 수 있어요. 이를 테스트하려면 아래 명령을 사용하세요.
$ kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
L3/L4 정책 적용하기
Cilium을 사용할 때 보안 정책을 정의하는 데는 엔드포인트 IP 주소가 무관해요. 대신 파드에 할당된 라벨을 사용해 보안 정책을 정의할 수 있어요. 정책은 클러스터 내 어디서 언제 실행되든 상관없이 라벨에 기반해 올바른 파드에 적용돼요.
deathstar 착륙 요청을 org=empire 라벨이 있는 배로만 제한하는 기본 정책부터 시작할게요. 이렇게 하면 org=empire 라벨이 없는 배는 deathstar 서비스에 연결조차 할 수 없어요. 이는 IP 프로토콜(네트워크 계층3)과 TCP 프로토콜(네트워크 계층4)만 필터링하는 단순한 정책이라, 흔히 L3/L4 네트워크 보안 정책이라고 불러요.
참고: Cilium은 상태 저장 연결 추적(stateful connection tracking)을 수행해요. 즉, 정책이 프론트엔드가 백엔드에 도달하는 것을 허용한다면, 같은 TCP/UDP 연결 맥락 안에서 백엔드가 프론트엔드에 답하는 데 필요한 모든 응답 패킷을 자동으로 허용해요.
Cilium과 Kubernetes를 위한 L4 정책
다음 CiliumNetworkPolicy로 이를 달성할 수 있어요.
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "rule1"
spec:
description: "L3-L4 policy to restrict deathstar access to empire ships only"
endpointSelector:
matchLabels:
org: empire
class: deathstar
ingress:
- fromEndpoints:
- matchLabels:
org: empire
toPorts:
- ports:
- port: "80"
protocol: TCP
CiliumNetworkPolicy는 "endpointSelector"를 사용해 파드 라벨을 매칭해서 정책이 적용되는 소스와 목적지를 식별해요. 위 정책은 org=empire 라벨이 있는 모든 파드에서 TCP 80 포트의 org=empire, class=deathstar 라벨이 있는 deathstar 파드로 보내는 트래픽을 화이트리스트 처리해요.
이 L3/L4 정책을 적용하려면 다음을 실행하세요.
$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/minikube/sw_l3_l4_policy.yaml
ciliumnetworkpolicy.cilium.io/rule1 created
이제 다시 착륙 요청을 실행하면 org=empire 라벨이 있는 tiefighter 파드만 성공할 거예요. xwing 파드는 차단될 거예요!
$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
예상대로 동작해요. 이제 xwing 파드에서 같은 요청을 실행하면 실패할 거예요.
$ kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
이 요청은 멈춰 있으므로 Control-C를 눌러 curl 요청을 종료하거나 타임아웃을 기다려야 해요.
정책 검사하기 (Inspecting the Policy)
cilium-dbg endpoint list를 다시 실행하면 org=empire와 class=deathstar 라벨이 있는 파드에 위 정책에 따라 ingress 정책 강제가 활성화된 것을 볼 수 있어요.
$ kubectl -n kube-system exec cilium-1c2cz -- cilium-dbg endpoint list
ENDPOINT POLICY (ingress) POLICY (egress) IDENTITY LABELS (source:key[=value]) IPv6 IPv4 STATUS
ENFORCEMENT ENFORCEMENT
232 Enabled Disabled 16530 k8s:class=deathstar 10.0.0.147 ready
k8s:io.cilium.k8s.policy.cluster=default
k8s:io.cilium.k8s.policy.serviceaccount=default
k8s:io.kubernetes.pod.namespace=default
k8s:org=empire
726 Disabled Disabled 1 reserved:host ready
883 Disabled Disabled 4 reserved:health 10.0.0.244 ready
1634 Disabled Disabled 51373 k8s:io.cilium.k8s.policy.cluster=default 10.0.0.118 ready
k8s:io.cilium.k8s.policy.serviceaccount=coredns
k8s:io.kubernetes.pod.namespace=kube-system
k8s:k8s-app=kube-dns
1673 Disabled Disabled 31028 k8s:class=tiefighter 10.0.0.112 ready
k8s:io.cilium.k8s.policy.cluster=default
k8s:io.cilium.k8s.policy.serviceaccount=default
k8s:io.kubernetes.pod.namespace=default
k8s:org=empire
2811 Disabled Disabled 51373 k8s:io.cilium.k8s.policy.cluster=default 10.0.0.47 ready
k8s:io.cilium.k8s.policy.serviceaccount=coredns
k8s:io.kubernetes.pod.namespace=kube-system
k8s:k8s-app=kube-dns
2843 Enabled Disabled 16530 k8s:class=deathstar 10.0.0.89 ready
k8s:io.cilium.k8s.policy.cluster=default
k8s:io.cilium.k8s.policy.serviceaccount=default
k8s:io.kubernetes.pod.namespace=default
k8s:org=empire
3184 Disabled Disabled 22654 k8s:class=xwing 10.0.0.30 ready
k8s:io.cilium.k8s.policy.cluster=default
k8s:io.cilium.k8s.policy.serviceaccount=default
k8s:io.kubernetes.pod.namespace=default
k8s:org=alliance
kubectl로 정책 세부 사항을 검사할 수도 있어요.
$ kubectl get cnp
NAME AGE
rule1 2m
$ kubectl describe cnp rule1
Name: rule1
Namespace: default
Labels: <none>
Annotations: <none>
API Version: cilium.io/v2
Description: L3-L4 policy to restrict deathstar access to empire ships only
Kind: CiliumNetworkPolicy
Metadata:
Creation Timestamp: 2020-06-15T14:06:48Z
Generation: 1
Managed Fields:
API Version: cilium.io/v2
Fields Type: FieldsV1
fieldsV1:
f:description:
f:spec:
.:
f:endpointSelector:
.:
f:matchLabels:
.:
f:class:
f:org:
f:ingress:
Manager: kubectl
Operation: Update
Time: 2020-06-15T14:06:48Z
Resource Version: 2914
Self Link: /apis/cilium.io/v2/namespaces/default/ciliumnetworkpolicies/rule1
UID: eb3a688b-b3aa-495c-b20a-d4f79e7c088d
Spec:
Endpoint Selector:
Match Labels:
Class: deathstar
Org: empire
Ingress:
From Endpoints:
Match Labels:
Org: empire
To Ports:
Ports:
Port: 80
Protocol: TCP
Events: <none>
HTTP 인식 L7 정책 적용 및 테스트하기
위의 단순 시나리오에서는 tiefighter/xwing에게 deathstar의 API에 대한 전체 접근을 주거나 전혀 주지 않는 것으로 충분했어요. 하지만 마이크로서비스 간에 가장 강력한 보안(즉, 최소 권한 격리 적용)을 제공하려면, deathstar의 API를 호출하는 각 서비스는 정당한 운영에 필요한 HTTP 요청 집합만 하도록 제한되어야 해요.
예를 들어 deathstar 서비스가 일반 제국선은 호출해서는 안 되는 일부 유지보수 API를 노출한다고 생각해 보세요. 이를 보려면 다음을 실행해요.
$ kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Panic: deathstar exploded
goroutine 1 [running]:
main.HandleGarbage(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
/code/src/github.com/empire/deathstar/
temp/main.go:9 +0x64
main.main()
/code/src/github.com/empire/deathstar/
temp/main.go:5 +0x85
이것은 예시이지만, 위와 같은 무단 접근은 보안상 악영향을 미칠 수 있어요.
Cilium과 Kubernetes를 위한 L7 정책
Cilium은 tiefighter가 도달할 수 있는 URL을 제한하기 위해 HTTP 계층(즉, L7) 정책을 강제할 수 있어요. 다음은 tiefighter를 POST /v1/request-landing API 호출만 하도록 제한하되, 다른 모든 호출(PUT /v1/exhaust-port 포함)을 금지함으로써 원래 정책을 확장한 예시 정책 파일이에요.
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
name: "rule1"
spec:
description: "L7 policy to restrict access to specific HTTP call"
endpointSelector:
matchLabels:
org: empire
class: deathstar
ingress:
- fromEndpoints:
- matchLabels:
org: empire
toPorts:
- ports:
- port: "80"
protocol: TCP
rules:
http:
- method: "POST"
path: "/v1/request-landing"
L7 인식 정책을 적용해 deathstar를 보호하려면 기존 규칙을 갱신하세요.
$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/minikube/sw_l3_l4_l7_policy.yaml
ciliumnetworkpolicy.cilium.io/rule1 configured
이제 위와 같은 테스트를 다시 실행할 수 있는데, 다른 결과가 나올 거예요.
$ kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
그리고
$ kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Access denied
이 규칙은 신원 인식 규칙 위에 구축되므로, org=empire 라벨이 없는 파드의 트래픽은 계속 드롭되어 연결이 타임아웃돼요.
$ kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
보시다시피, Cilium L7 보안 정책을 사용하면 tiefighter가 deathstar의 필요한 API 리소스에만 접근하도록 허용할 수 있어요. 이를 통해 마이크로서비스 간 통신에 "최소 권한(least privilege)" 보안 접근 방식을 구현할 수 있어요. path는 정확한 URL과 일치한다는 점을 참고하세요. 예를 들어 /v1/ 아래의 모든 것을 허용하려면 정규식을 사용해야 해요.
path: "/v1/.*"
kubectl로 L7 정책을 관찰할 수 있어요.
$ kubectl describe ciliumnetworkpolicies
Name: rule1
Namespace: default
Labels: <none>
Annotations: API Version: cilium.io/v2
Description: L7 policy to restrict access to specific HTTP call
Kind: CiliumNetworkPolicy
Metadata:
Creation Timestamp: 2020-06-15T14:06:48Z
Generation: 2
Managed Fields:
API Version: cilium.io/v2
Fields Type: FieldsV1
fieldsV1:
f:description:
f:metadata:
f:annotations:
.:
f:kubectl.kubernetes.io/last-applied-configuration:
f:spec:
.:
f:endpointSelector:
.:
f:matchLabels:
.:
f:class:
f:org:
f:ingress:
Manager: kubectl
Operation: Update
Time: 2020-06-15T14:10:46Z
Resource Version: 3445
Self Link: /apis/cilium.io/v2/namespaces/default/ciliumnetworkpolicies/rule1
UID: eb3a688b-b3aa-495c-b20a-d4f79e7c088d
Spec:
Endpoint Selector:
Match Labels:
Class: deathstar
Org: empire
Ingress:
From Endpoints:
Match Labels:
Org: empire
To Ports:
Ports:
Port: 80
Protocol: TCP
Rules:
Http:
Method: POST
Path: /v1/request-landing
Events: <none>
그리고 cilium-dbg CLI로도 확인할 수 있어요.
$ kubectl -n kube-system exec cilium-qh5l2 -- cilium-dbg policy get
[
{
"endpointSelector": {
"matchLabels": {
"any:class": "deathstar",
"any:org": "empire",
"k8s:io.kubernetes.pod.namespace": "default"
}
},
"ingress": [
{
"fromEndpoints": [
{
"matchLabels": {
"any:org": "empire",
"k8s:io.kubernetes.pod.namespace": "default"
}
}
],
"toPorts": [
{
"ports": [
{
"port": "80",
"protocol": "TCP"
}
],
"rules": {
"http": [
{
"path": "/v1/request-landing",
"method": "POST"
}
]
}
}
]
}
],
"labels": [
{
"key": "io.cilium.k8s.policy.derived-from",
"value": "CiliumNetworkPolicy",
"source": "k8s"
},
{
"key": "io.cilium.k8s.policy.name",
"value": "rule1",
"source": "k8s"
},
{
"key": "io.cilium.k8s.policy.namespace",
"value": "default",
"source": "k8s"
},
{
"key": "io.cilium.k8s.policy.uid",
"value": "eb3a688b-b3aa-495c-b20a-d4f79e7c088d",
"source": "k8s"
}
]
}
]
Revision: 11
cilium-dbg monitor로 HTTP 요청을 실시간으로 모니터링하는 것도 가능해요.
$ kubectl exec -it -n kube-system cilium-kzgdx -- cilium-dbg monitor -v --type l7
<- Response http to 0 ([k8s:class=tiefighter k8s:io.cilium.k8s.policy.cluster=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default k8s:org=empire]) from 2756 ([k8s:io.cilium.k8s.policy.cluster=default k8s:class=deathstar k8s:org=empire k8s:io.kubernetes.pod.namespace=default k8s:io.cilium.k8s.policy.serviceaccount=default]), identity 8876->43854, verdict Forwarded POST http://deathstar.default.svc.cluster.local/v1/request-landing => 200
<- Request http from 0 ([k8s:class=tiefighter k8s:io.cilium.k8s.policy.cluster=default k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.kubernetes.pod.namespace=default k8s:org=empire]) to 2756 ([k8s:io.cilium.k8s.policy.cluster=default k8s:class=deathstar k8s:org=empire k8s:io.kubernetes.pod.namespace=default k8s:io.cilium.k8s.policy.serviceaccount=default]), identity 8876->43854, verdict Denied PUT http://deathstar.default.svc.cluster.local/v1/request-landing => 403
위 출력은 POST 요청에 대한 성공 응답과, L7 정책에 의해 거부된 PUT 요청을 보여줘요.
튜토리얼을 즐겼길 바랍니다. 설정을 가지고 더 놀아보고, 나머지 문서를 읽어보고, 궁금한 점이 있으면 Cilium Slack으로 연락하세요!
정리 (Clean-up)
$ kubectl delete -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/minikube/http-sw-app.yaml