상호 인증 예제

상호 인증 예제 (Mutual Authentication Example)

이 예제는 두 Pod 사이에서 상호 인증을 강제하는 방법을 보여드려요. 클라이언트(pod-worker)와 서버(echo)를 배포하고, SPIRE 상태 확인 → SPIFFE 아이덴티티 확인 → 상호 인증 정책 적용 및 검증 순서로 진행해요.

출처: Mutual Authentication Example

본문

이 예제는 두 Pod 사이에서 상호 인증을 강제하는 방법을 보여드려요.

다음 매니페스트를 사용해 클라이언트(pod-worker)와 서버(echo)를 배포해볼게요:

$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes/servicemesh/mutual-auth-example.yaml
$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes/servicemesh/cnp-without-mutual-auth.yaml
service/echo created
deployment.apps/echo created
pod/pod-worker created
ciliumnetworkpolicy.cilium.io/no-mutual-auth-echo created

Pod들이 성공적으로 배포됐는지 확인해볼게요:

$ kubectl get svc echo
NAME   TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
echo   ClusterIP   10.96.16.90   <none>        3000/TCP   42m
$ kubectl get pod pod-worker
NAME         READY   STATUS    RESTARTS   AGE
pod-worker   1/1     Running   0          40m

네트워크 정책이 성공적으로 배포됐고 기대대로 트래픽을 필터링하는지 확인해볼게요.

다음 명령들을 실행해볼게요:

$ kubectl exec -it pod-worker -- curl -s -o /dev/null -w "%{http_code}" http://echo:3000/headers
200
$ kubectl exec -it pod-worker -- curl http://echo:3000/headers-1
Access denied

첫 번째 요청은 성공해야 해요 (pod-worker Pod가 특정 HTTP 경로를 통해 echo Service에 연결할 수 있고 HTTP 상태 코드는 200이에요). 두 번째 요청은 거부되어야 해요 (pod-worker Pod가 /headers가 아닌 특정 HTTP 경로를 통해 echo Service에 연결할 수 없어요).

pod-worker와 echo 사이의 상호 인증을 활성화하기 전에 SPIRE 서버가 정상인지 확인해볼게요.

설치 지침을 따랐고 Cilium을 제공하는 SPIRE 서버가 있다고 가정하면, 상호 인증을 추가하는 것은 네트워크 정책의 ingress/egress 블록에 authentication.mode: "required"를 추가하기만 하면 돼요.

SPIRE 상태 확인 (Verify SPIRE Health)

참고

이 예제는 기본 SPIRE 설치를 가정해요.

먼저 자동으로 배포된 SPIRE 서버와 agent가 기대대로 동작하는지 확인해볼게요.

SPIRE 서버는 StatefulSet으로, SPIRE agent는 DaemonSet으로 배포돼요 (따라서 노드마다 SPIRE agent가 하나 있어야 해요).

$ kubectl get all -n cilium-spire
NAME                    READY   STATUS    RESTARTS   AGE
pod/spire-agent-27jd7   1/1     Running   0          144m
pod/spire-agent-qkc8l   1/1     Running   0          144m
pod/spire-server-0      2/2     Running   0          144m

NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/spire-server   ClusterIP   10.96.124.177   <none>        8081/TCP   144m

NAME                         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/spire-agent   2         2         2       2            2           <none>          144m

NAME                            READY   AGE
statefulset.apps/spire-server   1/1     144m

SPIRE 서버에 헬스체크를 실행해볼게요.

$ kubectl exec -n cilium-spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server healthcheck
Server is healthy.

증명된(attested) agent 목록을 확인해볼게요:

$ kubectl exec -n cilium-spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server agent list
Found 2 attested agents:

SPIFFE ID         : spiffe://spiffe.cilium/spire/agent/k8s_psat/default/64745bf2-bd9d-4e42-bb2b-e095a6b65121
Attestation type  : k8s_psat
Expiration time   : 2023-07-04 18:39:50 +0000 UTC
Serial number     : 110848236251310359782141595494072495768

SPIFFE ID         : spiffe://spiffe.cilium/spire/agent/k8s_psat/default/d4a8a6da-d808-4993-b67a-bed250bbc53e
Attestation type  : k8s_psat
Expiration time   : 2023-07-04 18:39:55 +0000 UTC
Serial number     : 7806033782886940845084156064765627978

SPIRE Server가 Kubernetes Projected Service Account Token(PSAT)을 사용해 Kubernetes 클러스터에서 실행되는 SPIRE Agent의 아이덴티티를 검증한다는 점에 주목해주세요. Projected Service Account Token은 기존 Kubernetes Service Account Token보다 추가 보안 보장을 제공하며, Kubernetes 클러스터가 지원할 때 PSAT는 권장되는 증명 전략이에요.

SPIFFE 아이덴티티 확인 (Verify SPIFFE Identities)

이제 SPIRE 서비스가 정상이라는 걸 알았으니, Cilium과 SPIRE 통합이 성공했는지 확인해볼게요:

  • Cilium agent와 operator는 SPIRE Server에 등록된 delegate Identity가 있어야 해요.
  • Cilium operator는 워크로드(Kubernetes Pod)를 대신해 SPIRE Server에 Identities를 등록했어야 해요.

Cilium agent와 operator가 SPIRE 서버에 Identities를 가지고 있는지 확인해볼게요:

$ kubectl exec -n cilium-spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server entry show -parentID spiffe://spiffe.cilium/ns/cilium-spire/sa/spire-agent
Found 2 entries
Entry ID         : b6424c87-4323-4d64-98dd-cd5b51a1fcbb
SPIFFE ID        : spiffe://spiffe.cilium/cilium-agent
Parent ID        : spiffe://spiffe.cilium/ns/cilium-spire/sa/spire-agent
Revision         : 0
X509-SVID TTL    : default
JWT-SVID TTL     : default
Selector         : k8s:ns:kube-system
Selector         : k8s:sa:cilium

Entry ID         : 8aa91d65-16c4-48a0-bc1f-c9bf26e6a25f
SPIFFE ID        : spiffe://spiffe.cilium/cilium-operator
Parent ID        : spiffe://spiffe.cilium/ns/cilium-spire/sa/spire-agent
Revision         : 0
X509-SVID TTL    : default
JWT-SVID TTL     : default
Selector         : k8s:ns:kube-system
Selector         : k8s:sa:cilium-operator

다음으로 echo Pod가 SPIRE 서버에 등록된 Identity를 가지고 있는지 확인해볼게요.

이렇게 하려면 먼저 Pod의 SPIFFE ID를 구성해야 해요. 워크로드의 SPIFFE ID는 spiffe://spiffe.cilium/identity/$IDENTITY_ID 형식을 기반으로 하는데, 여기서 $IDENTITY_ID는 워크로드의 Cilium Identity예요.

echo Pod의 Cilium Identity를 가져와볼게요:

$ IDENTITY_ID=$(kubectl get cep -l app=echo -o=jsonpath='{.items[0].status.identity.id}')
$ echo $IDENTITY_ID
17947

echo pod의 Cilium Identity를 사용해 SPIFFE ID를 구성하고 SPIRE 서버에 등록됐는지 확인해볼게요:

$ kubectl exec -n cilium-spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server entry show -spiffeID spiffe://spiffe.cilium/identity/$IDENTITY_ID
Found 1 entry
Entry ID         : 9fc13971-fb19-4814-b9f0-737b30e336c6
SPIFFE ID        : spiffe://spiffe.cilium/identity/17947
Parent ID        : spiffe://spiffe.cilium/cilium-operator
Revision         : 0
X509-SVID TTL    : default
JWT-SVID TTL     : default
Selector         : cilium:mutual-auth

cilium-operator가 Parent ID에 나열된 걸 볼 수 있어요. 이는 Cilium operator가 Cilium Identities가 생성될 때마다 그에 대한 SPIRE 엔트리를 만들기 때문이에요.

등록된 모든 엔트리를 얻으려면 다음 명령을 실행해주세요:

kubectl exec -n cilium-spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server entry show -selector cilium:mutual-auth

아이덴티티 수만큼 엔트리가 있어요. 다음 명령을 실행해 일치하는지 확인해볼게요:

kubectl get ciliumidentities

NAME 아래 나열된 identity ID가 앞선 명령에서 실행한 SPIFFE ID의 끝에 있는 숫자와 일치해야 해요.

상호 인증 강제하기 (Enforce Mutual Authentication)

Cilium으로 상호 인증을 롤아웃하는 것은 기존 또는 새 CiliumNetworkPolicy egress 또는 ingress 규칙에 다음 블록을 추가하는 것만큼 간단해요:

authentication:
    mode: "required"

상호 인증된 워크로드만 echo에 접근하도록 ingress 접근을 허용하도록 기존 규칙을 업데이트해볼게요:

$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.20.2/examples/kubernetes/servicemesh/cnp-with-mutual-auth.yaml

상호 인증 검증 (Verify Mutual Authentication)

먼저 debug 레벨을 활성화할게요:

cilium config set debug true

연결성 테스트를 다시 시도해보세요. 이전과 비슷한 결과를 줘야 해요:

$ kubectl exec -it pod-worker -- curl -s -o /dev/null -w "%{http_code}" http://echo:3000/headers
200
$ kubectl exec -it pod-worker -- curl http://echo:3000/headers-1
Access denied

agent의 로그에 접근해서 상호 인증이 발생했는지 확인해볼게요.

echo Pod와 같은 노드에 있는 Cilium agent의 로그를 살펴볼게요. 간결하게 하기 위해 레이블로 특정 로그 메시지를 검색할 수 있어요:

$ kubectl -n kube-system -c cilium-agent logs -l k8s-app=cilium --timestamps=true | grep "Policy is requiring authentication\|Validating Server SNI\|Validated certificate\|Successfully authenticated"
2023-07-04T17:58:28.795760597Z level=debug msg="Policy is requiring authentication" key="localIdentity=17947, remoteIdentity=39239, remoteNodeID=54264, authType=spire" subsys=auth
2023-07-04T17:58:28.800509503Z level=debug msg="Validating Server SNI" SNI ID=39239 subsys=auth
2023-07-04T17:58:28.800525190Z level=debug msg="Validated certificate" subsys=auth uri-san="[spiffe://spiffe.cilium/identity/39239]"
2023-07-04T17:58:28.801441968Z level=debug msg="Successfully authenticated" key="localIdentity=17947, remoteIdentity=39239, remoteNodeID=54264, authType=spire" remote_node_ip=10.0.1.175 subsys=auth

상호 인증 정책을 적용하면 agent가 소스 Pod의 아이덴티티를 가져와서, 목적지 Pod가 실행 중인 노드에 연결하고 mutual TLS 핸드셰이크를 수행해요 (위 로그는 mutual TLS 핸드셰이크의 한쪽을 보여줘요). 핸드셰이크가 성공했으므로 연결이 인증됐고, 정책으로 보호되는 트래픽이 진행될 수 있었어요.

네트워크 정책이 제거되거나 엔트리가 만료될 때까지 두 Pod 사이의 패킷은 흐를 수 있어요.

더 알아보기 (Learn more)