Star Wars 데모 시작하기

Star Wars 데모 시작하기 (Getting Started with the Star Wars Demo)

Cilium의 네트워크 정책이 어떻게 동작하는지 Star Wars 테마의 데모 애플리케이션으로 체험해 보는 튜토리얼이에요. L3/L4 정책부터 HTTP 인식 L7 정책까지 단계별로 만들어 보면서 배워요.

출처: Getting Started with the Star Wars Demo

본문

데모 애플리케이션 배포하기 (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

더 알아보기 (Learn more)