본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

Istio로 Calico 네트워크 정책 적용하기

원문 보기 위키 갱신

Istio로 Calico 네트워크 정책 적용하기 (튜토리얼)

Istio와 Calico를 함께 쓰면 네트워크 계층과 서비스 메시 내부 양쪽에서 세밀한 접근 제어를 적용할 수 있어요. 이 튜토리얼은 마이크로서비스 애플리케이션을 배포한 뒤, Calico 애플리케이션 계층 정책으로 흔한 위협을 어떻게 완화하는지 보여 줘요. 정책이 왜 필요한지부터 단계별로 확인해 봐요.

출처: 문서

본문

이 튜토리얼은 마이크로서비스 애플리케이션을 구성한 다음, Calico 애플리케이션 계층 정책을 사용해 몇 가지 흔한 위협을 완화하는 방법을 보여 줘요.

참고: 이 튜토리얼은 Istio v1.10.2로 검증되었어요. 최신 Istio 버전에는 일부 내용이 적용되지 않을 수 있어요.

사전 준비

  1. Kubernetes 클러스터를 만들어요.

  2. Kubernetes에 Calico를 설치해요.

    • Kubernetes에 Calico가 설치되어 있지 않다면 Kubernetes의 Calico를 참고하세요.
    • Kubernetes에 Calico가 이미 설치되어 있다면 Calico 네트워킹(또는 non-Calico CNI)과 Calico 네트워크 정책이 설치되어 있는지 확인해요.
  3. calicoctl 명령줄 도구를 설치해요. 참고: calicoctl이 데이터스토어에 연결되도록 구성되어 있어야 해요.

  4. MutatingAdmissionWebhook을 활성화해요.

  5. 애플리케이션 계층 정책을 활성화해요. 참고: Istio 사이드카 주입을 위해 기본 네임스페이스에 레이블을 붙이세요(istio-injection=enabled). kubectl label namespace default istio-injection=enabled

데모 애플리케이션 설치하기

Calico 애플리케이션 계층 정책을 시연하기 위해 간단한 마이크로서비스 애플리케이션을 사용해요. YAO Bank 애플리케이션은 고객용 웹 애플리케이션, 계정 요약을 제공하는 마이크로서비스, etcd 데이터스토어로 이루어져 있어요.

kubectl apply -f https://docs.tigera.io/files/10-yaobank.yaml

참고: 브라우저에서 매니페스트를 확인할 수도 있어요.

애플리케이션 파드가 만들어지고 준비되었는지 확인해요.

kubectl get pods

데모 애플리케이션이 뜨면 파드 세 개가 보일 거예요.

NAME                        READY     STATUS    RESTARTS   AGE
customer-2809159614-qqfnx   3/3       Running   0          21h
database-1601951801-m4w70   3/3       Running   0          21h
summary-2817688950-g1b3n    3/3       Running   0          21h

인그레스 IP와 포트 알아내기

YAO Bank 애플리케이션에 접근할 때는 istio-ingressgateway 서비스를 사용해요. Istio 안내에 따라 인그레스 호스트와 포트를 확인해요. INGRESS_HOST와 INGRESS_PORT 변수를 설정했다면 다음과 같이 GATEWAY_URL을 지정할 수 있어요.

export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT

브라우저에서 http://$GATEWAY_URL/에 접속해 YAO Bank 애플리케이션이 정상 동작하는지 확인해요. 모든 서비스가 뜨고 응답하기까지 몇 분 걸릴 수 있고, 그 사이 404나 500 오류가 보일 수 있어요.

정책이 필요한 이유

Calico와 Istio가 클러스터에서 실행 중이지만, 아직 인증 정책은 하나도 정의하지 않았어요. Istio는 애플리케이션의 파드 사이 트래픽을 상호 인증(mTLS)하도록 구성되어 있어서 Istio가 발급한 인증서를 가진 연결만 허용되고, 모든 파드 간 트래픽은 TLS로 암호화돼요. 이미 크게 올바른 방향으로 나아간 거예요.

하지만 이 보안 아키텍처에는 몇 가지 부족한 점이 있어요.

  • Istio 메시 안의 워크로드에서 오는 모든 인바운드 연결을 똑같이 신뢰해요.
  • 키와 인증서 쌍을 가졌다는 사실이 고려되는 유일한 접근 자격 증명이에요.

이것이 왜 문제가 될 수 있는지 하나씩 살펴봐요.

워크로드 신뢰

Istio 메시 안의 모든 워크로드 연결을 신뢰하는 것은 좋지 않은 보안 아키텍처예요. Kubernetes처럼 Istio도 여러 애플리케이션을 함께 운영하도록 설계되었기 때문이에요. 그중 일부 애플리케이션은 다른 애플리케이션만큼 신뢰할 수 없을 수 있어요. 보안 요구 사항이 크게 다른 사용자나 팀이 운영할 수도 있고요. 다른 개발자가 대충 만드는 프로토타입에서 우리의 금융 애플리케이션 마이크로서비스에 접근할 수 있게 두고 싶지는 않겠죠.

같은 애플리케이션 내부에서도 최선의 방법은 접근을 최대한 제한하는 거예요. 서비스에 접근이 필요한 파드만 접근하게 해야 해요. YAO Bank 애플리케이션을 생각해 봐요. 고객용 웹 서비스는 백엔드 데이터베이스에 직접 접근할 필요가 없고, 접근해서도 안 돼요. 고객용 웹 서비스는 클러스터 외부의 클라이언트와 직접 통신해야 하고, 그중 악의적인 클라이언트도 있을 수 있어요. 안타깝게도 웹 애플리케이션의 취약점은 흔한 일이에요. 예를 들어 Apache Struts의 패치되지 않은 취약점이 공격자들에게 Equifax 네트워크에 첫발을 내딛는 통로를 열어 주었고, 공격자들은 거기서 수백만 명의 금융 정보를 훔치는 파괴적인 공격을 시작했어요.

만약 공격자가 우리 애플리케이션의 customer web 파드를 장악하면 어떻게 될지 상상해 볼까요? 그 파드 안에서 원격 셸을 실행해 이 상황을 재현해 볼게요.

kubectl exec -ti customer-<fill in pod ID> -c customer -- bash

여기서 백엔드 데이터베이스에 직접 접근할 수 있다는 점에 주목하세요. 예를 들어 다음처럼 데이터베이스의 모든 항목을 나열할 수 있어요.

curl http://database:2379/v2/keys?recursive=true | python -m json.tool

(python -m json.tool로 파이핑하면 출력이 보기 좋게 정렬돼요.)

단일 요소 인증

키와 인증서 쌍을 가졌다는 것은 연결이 진짜라는 아주 강한 근거가 돼요. 거의 위조가 불가능하다고 여겨지는 암호학적 증명에 기반하기 때문이에요. 이 방식으로 연결을 인증하면 상대편이 대응하는 키를 가지고 있다는 사실을 매우 높은 확신으로 말할 수 있어요. 하지만 이것은 우리가 실제로 확신하고 싶은 것, 즉 상대편이 정말로 우리가 통신하려는 인가된 워크로드라는 사실의 대리 증거일 뿐이에요. 개인 키를 비밀로 유지하는 것이 이 확신의 핵심인데, 공격자가 가끔 애플리케이션을 속여서 가 져서는 안 되는 비밀을 넘겨 버리는 방법을 찾아내기도 해요. 예를 들어 OpenSSL의 Heartbleed 취약점은 공격자가 영향을 받는 애플리케이션을 속여 메모리의 일부를 읽어 내게 하고, 개인 키를 탈취하게 만들었어요.

네트워크 정책

위 두 가지 부족한 점은 모두 Calico 정책으로 완화할 수 있어요.

wget https://docs.tigera.io/files/30-policy.yaml
calicoctl create -f 30-policy.yaml

참고: 브라우저에서 매니페스트를 확인할 수도 있어요.

이 정책을 조각조각 살펴봐요. 정책은 마이크로서비스마다 하나씩, 세 개의 정책 객체로 이루어져 있어요.

apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: customer
spec:
  selector: app == 'customer'
  ingress:
    - action: Allow
      http:
        methods: ['GET']
  egress:
    - action: Allow

이 정책은 customer 웹 앱을 보호해요. 이 애플리케이션은 고객을 대상으로 하기 때문에 무엇이 통신할 수 있는지 제한하지 않아요. 다만 이 앱의 통신을 HTTP GET 요청으로 제한해요.

apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: summary
spec:
  selector: app == 'summary'
  ingress:
    - action: Allow
      source:
        serviceAccounts:
          names: ['customer']
  egress:
    - action: Allow

두 번째 정책은 계정 요약(account summary) 마이크로서비스를 보호해요. 이 서비스를 사용하는 것은 customer 웹 앱뿐이므로, 인바운드 연결의 출처를 customer 웹 앱의 서비스 어카운트로 제한해요.

apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: database
spec:
  selector: app == 'database'
  ingress:
    - action: Allow
      source:
        serviceAccounts:
          names: ["summary"]
  egress:
    - action: Allow

세 번째 정책은 데이터베이스를 보호해요. summary 마이크로서비스만 데이터베이스에 직접 접근할 수 있어야 하거든요.

정책이 의도대로 동작하는지 확인해 봐요. 먼저 브라우저로 돌아가 새로고침해서 정책 적용이 애플리케이션을 망가뜨리지 않았는지 확인해요.

다음으로 customer 웹 앱으로 돌아가요. 앞서 그 파드 안에서 원격 셸을 실행해 공격자가 파드를 장악한 상황을 재현했었죠.

kubectl exec -ti customer-<fill in pod ID> -c customer bash

데이터베이스 접근 시도를 반복해 봐요.

curl -I http://database:2379/v2/keys?recursive=true

유효한 JSON 응답을 기대할 수 없으니 JSON 포매팅은 생략했어요. 이번에는 403 Forbidden 응답을 받아야 해요. 정책상 데이터베이스 접근 권한은 계정 요약 마이크로서비스에만 있거든요.

추가 리소스

더 알아보기 (Learn more)

Istio의 mTLS만으로는 "인증서를 가진 누구나"를 막을 수 없다는 점이 핵심 교훈이에요. Calico 정책으로 서비스 어카운트와 HTTP 메서드 수준의 제한을 더하면 훨씬 단단한 접근 제어가 완성돼요.