Citadel 과 GCGS의 관계

김도영
Cloud Villains
Published in
21 min readMay 11, 2022

Why?

이전 포스트 GCGS를 이용한 게임서버 배포 에서 GCGS Multi-Cluster Allocation 이용해 GameServer Allocation을 진행해 보았습니다. GCGS Multi-Cluster Allocation을 진행하면서 Citadel이라는 모듈을 설치하였는데 여기서 Citadel은 무엇이며, 왜 설치를 해야 하는지, 그리고 Citadel의 역할이 무엇인지에 대한 궁금증으로 Citadel을 찾아보게 되었습니다.

​Citadel을 찾아보기 전에 먼저 풀어야할 과제들이 있었습니다. 우선 TLS가 무엇인지, mTLS란 무엇인지에 대해 알아야 했으며 Server의 인증서는 어떤 흐름으로 인증이 되는지, Client 인증서란 무엇인지에 대해서도 알아야 했습니다. 대충만 알고 있던 TLS 와 mTLS 그리고 Server인증서와 Client 인증서에 대해서 이번에 제대로 알아보자! 라는 마음가짐으로 해당 내용들을 조사해 보았습니다.

​조사한 내용 토대로 TLS와 mTLS의 개념 그리고 Client와 Server가 어떤 식으로 서로 간 인증하는지에 대해 알아볼 것이며, 이러한 인증 과정에 쓰이는 인증서를 조금 더 쉽게 발급이 가능한 Citadel 그리고 GCGS에서 Multi-Cluster Allocation을 하기 위해서는 왜 Citadel이 필요한지에 대해서 알아보겠습니다.

TLS 통신이란​

TLS 의미
먼저 TLS에 대해서 알아보겠습니다. TLS란 SSL과 동일한 의미입니다. SSL 이란 Secure Sockets Layer의 약자로 암호화를 기반으로 한 인터넷 보안 프로토콜이며 현재는 TLS라는 이름으로 변경이 되었습니다. 이름은 변경되었지만 SSL, TLS 두 용어 모두 사용되고 있습니다.​

SSL HandShake 과정
그렇다면 어떠한 방식으로 보안 통신이 되는지 알아보겠습니다.
(대칭키와 공개키에 대해서는 링크를 참고 부탁드립니다)​

1. Client Hello 「Client → Server」
먼저 Client가 Server에게 접속을 하면서 시작되는 단계입니다.
Client는 랜덤 숫자를 생성을 하고, 그 값과 자신이 지원하는 암호화 방식 List(Cipher Suite 목록)를 Server 측에 보냅니다.​

2–1. Server Hello 「Server → Client」
Client Hello에 대해 응답하는 단계로 Client와 마찬가지로 랜덤 숫자를 생성하고, 그 값과 Client가 보낸 암호화 List 중에서 한 가지 암호화 방식을 택해 Client에게 보냅니다.​

2–2. Certificate
그리고 자신(Server)의 인증서Root CA 인증서​를 Client에게 보냅니다.

2–3. ServerKeyExchange
Server는 자신의 인증서에 공개키가 포함이 되어있지 않으면 이 메시지를 통해서 공개키를 전달합니다. 인증서에 공개키가 포함되어 있는 경우에는 해당 메시지를 생략합니다.​

2–4. ServerHelloDone
Server는 초기 협상이 끝났다는 메시지를 Client에게 알립니다.​

3–1. ClientKeyExchange 「Client → Server」
Client는 자신이 가지고 있는 Root CA List에서 Server로부터 받은 Root CA가 존재하는지 확인합니다. (브라우저 같은 경우에는 아래와 같이 기본적으로 공용 CA 를 지니고 있습니다)

List에 Server로부터 받은 Root CA가 존재하지 않으면 경고 메시지를 보내고​

List에 Server로부터 받은 Root CA가 존재하면 그 인증서의 공개키를 가지고 Server 인증서의 Hash 부분을 복호화 합니다. 복호화에 성공을 했다면 이는 Root CA에 의해 발급 된 인증서임이 증명된 것이므로 정상으로 판단합니다.​

그러고 나서 1번 과정(Client Hello)에서 Client가 생성한 랜덤 와 2번 과정(Server Hello)에서 Server로부터 받은 랜덤 수를 이용해 Pre Master Secret(PMS)이라는 난수값을 생성하고, Server 인증서에서 공개키를 획득해 암호화한 후 Server에게 보냅니다.​

Server는 자신의 공개키로 암호화된 PMS Key를 받아 자신의 개인키로 복호화를 통해 안전하게 PMS Key를 획득합니다. 그리고 Server와 Client는 각자에게 받은 랜덤 수 + PMS를 가지고 일련의 과정을 걸쳐서 MS(Master Secret) > Session Key를 생성합니다.

3–2. ChangeCipherSpec
위 협상에서 정해진 암호화 알고리즘, 개인키를 실제로 적용을 하고 Server 측에게 보냄으로써 정말로 사용하겠다는 의미를 지닌 메시지를 보냅니다.

3–3. Finished
Client는 Server에게 협상 종료하겠다는 메시지를 보냅니다.

4–1. ChangeCipherSpec 「Server → Client」
위에서 정한 알고리즘 및 개인키를 사용하겠다고 Server 측에서 확답의 메시지를 보냅니다.

4–2. Finished
Server는 Client에게 협상 종료하겠다는 메시지를 보냅니다.

5. Session
Client와 Server는 안전한 방식으로 대칭키를 서로 가지게 되었으며 이제 이 대칭키Session Key를 이용한 암호화 통신​을 진행합니다.

6. Session 종료
데이터의 전송이 끝나면 SSL 통신이 끝났음을 서로에게 알려줍니다. 이때 통신에서 사용한 대칭키​Session Key폐기합니다.

위 과정을 통해 SSL 통신은 처음에는 공개키를 이용해 안전하게 PMS Key를 잔달하며, 안전하게 전달받은 PMS Key를 이용해 각자 Session Key를 만들어 안전하게 서로가 대칭키를 이용해해 암호화된 통신하는 것을 확인할 수 있었습니다.

mTLS 통신이란

mTLS 의미
위에서 암호화를 기반으로 한 인터넷 보안 프로토콜인 TLS에 대해서 알아보았습니다. 그렇다면 mTLS란 무엇일까요?

m 은 Mutual의 약자로 서로의 라는 의미를 가지고 있습니다. 즉, Server만 인증하는 것이 아닌 Server도 Client를 인증하는 통신방법을 의미합니다.

Client-authenticated SSL HandShake 과정
mTLS HandShake 과정은 TLS HandShake 과정에서 Server가 Client의 인증서를 요청하는 과정과 인증하는 과정이 추가됩니다.​

1. Client Hello 「Client → Server」
먼저 Client가 Server에게 접속을 하므로 시작되는 단계입니다. Client는 랜덤 숫자를 생성을 하고 그 값과 자신이 지원하는 암호화 방식 List(Cipher Suite 목록)를 Server 측에 보냅니다.​

2–1. Server Hello 「Server → Client」
Client Hello에 대해 응답하는 단계로 Client와 마찬가지로 랜덤 숫자를 생성하고, 그 값과 Client가 보낸 암호화 List 중에서 한 가지 암호화 방식을 택해 Client에게 보냅니다.​

2–2. Certificate
그리고 자신(Server)의 인증서와 Root CA 인증서​를 Client에게 보냅니다.​

2–3. ServerKeyExchange
Server는 자신의 인증서에 공개키가 포함이 되어있지 않으면 이 메시지를 통해서 공개키를 전달합니다. 인증서에 공개키가 포함되어 있는 경우에는 해당 메시지를 생략합니다.​

※2–4. CertificateRequest (추가된 부분)
Server는 Client에게 인증서를 요구
합니다.​

2–5. ServerHelloDone
Server는 초기 협상이 끝났다는 메시지를 Client에게 알립니다.​

※3–0. Certificate (추가된 부분)
Client가 자신의 인증서를 담아서 Certificate 메시지에 응답합니다
여기서 개인키(Private Key)는 보내지 않습니다.

3–1. ClientKeyExchange 「Client → Server」
Client는 자신이 가지고 있는 Root CA List에서 Server로부터 받은 Root CA가 존재하는지 확인합니다. List에 Server로부터 받은 Root CA가 존재하지 않으면 경고 메시지를 보내고

​List에 Server로부터 받은 Root CA가 존재하면 그 인증서의 공개키를 가지고 Server 인증서의 Hash 부분을 복호화 합니다. 복호화에 성공을 했다면 이는 Root CA에 의해 발급 된 인증서임이 증명된 것이므로 정상으로 판단합니다.

​그러고 나서 1번 과정(Client Hello)에서 Client가 생성한 랜덤 수와 2번 과정(Server Hello)에서 Server로부터 받은 랜덤 수를 이용해 Pre Master Secret이라는 난수값을 생성하고, Server 인증서에서 획득한 공개키를 이용해 암호화한 후 Server에게 보냅니다.​

※3–2. CertificateVerify (추가된 부분)
위 3–1의 메시지를 Client 개인키로 암호화해 다시 Server에게 보냅니다. Server는 Client의 인증서를 받았으므로

공개키를 가지고 있으며, 그 공개키를 이용해서 해당 메시지 복호화를 성공하면, Server는 해당 Client가 개인키를 가지고 있음을 확신할 수 있습니다.

​그러고 나서 Server는 자신의 공개키로 암호화된 PMS Key를 자신의 개인키로 복호화를 통해 안전하게 PMS Key를 획득합니다. 그리고 Server와 Client는 각자에게 받은 랜덤 수 + PMS를 가지고 일련의 과정을 걸쳐서 MS(Master Secret) > Session Key를 생성합니다.


3–3. ChangeCipherSpec
위 협상에서 정해진 암호화 알고리즘, 개인키를 실제로 적용을 하고 Server 측에게 보냄으로써 정말로 사용하겠다는 의미를 지닌 메시지를 보냅니다.


그 이후 과정은 TLS와 동일합니다.
(추가된 부분은 보라색 CertificateRequest / Certificate/ Certificate Verify)

Citadel 이란 무엇인가?
TLS와 mTLS에 의미 및 인증과정에 대해서 알아보았습니다. 이제부터 Citadel에 대해서 본격적으로 알아보겠습니다.

Citadel 이란 무엇일까요?
Citadel 이란 Istio에서 Security 관련 기능을 담당을 하며 Client 인증서를 관리하는 하는 모듈입니다.

GCGS에서 Citadel은?

그렇다면 GCGS에서는 왜 Istio의 Citadel을 필수로 설치해 사용하는 걸까요?

​답을 찾기 전에 먼저 Agones에서 Multi-Cluster Allocation을 사용하기 위해서는 무엇이 필요한지 살펴봐야 합니다.

  1. Server 인증서
    Allocate 또는 Multi-cluster Allocation을 진행하기 위해서는 agones-allocator의 External IP로 Server 인증서 발급이 필요합니다. External IP로 인증서 발급은 아래 명령어를 통해서 할 수 있습니다.

조금더 정확하게 말하면 초기 helm을 통해 agones를 설치하면 agones-allocator pod가 생성되면서 아래와 같이 인증서가 발급됩니다.

============================

allocator-client-ca는 클라이언트 인증서로 Server 측에서 사용하는 Client Whitelist.

allocator-tls는 agones-allocator의 Server 인증서와 개인키.

allocator-tls-ca는 Server Root CA 인증서.

============================

하지만 agones-allocator Service의 External IP 가 발행 전에 발급된 인증서이므로 External IP로 다시 인증서 갱신하는 작업이 필요한데, 그 작업을 위 명령어를 통해서 진행이 필요합니다.

2. Client 인증서
mTLS 통신이므로 Server뿐만 아니라 Client도 인증서가 필요합니다.
(Self Sign Client 인증서 생성하는 방법은 링크 참고)

​위 링크와 같이 Openssl을 이용해 Client 인증서, Client 개인키, Client Root CA를 생성하고 해당 정보를 담고있는 Secret이 필요합니다.

3. GameServerAllocationPolicy라는 CRD Object 생성이 필요GameServerAllocationPolicy란 Multi-Cluster Allocation을 진행시 Allocate할 Cluster의 정보를 담고 있는Object로써 agones-allocator의 External IP 정보, GameServer의 NameSpace 이름, Client 인증서 정보가 담긴 Secret (2번에서 생성한 Secret) 그리고 Multi-Cluster Allocation 진행시 다수의 GameServer
Allocation Policy 중 선택의 기준이 되는 Priority와 Weight 값을 지니고 있는 Object입니다.

이 정보들을 포함하고 있는 GameServerAllocationPolicy를 Cluster 별로 생성이 필요합니다.
​​

4. allocator-client-ca에 Client 인증서 추가
Server 측에서 Client 인증서에 대한 WhileList를 담당하는 Secret으로써 2번에서 생성한 Client 인증서를 allocator-client-ca Secret에 추가가 필요합니다.

이렇게 4개의 작업인 Server인증서 생성, Client 인증서 생성, Cluster마다 GameServerAllocationPolicy 생성, Client 인증서 WhiteList 처리 작업들을 Cluster가 추가될때마다 수동으로 해야 한다면 매우 번거로운 일이 될 것입니다. 하지만 CitadelGCGS를 사용하게 되면 쉽게 생성 및 관리를 할 수 있습니다.

Citadel

OpenSSl 명령어로 생성해야 했던 Client 인증서를 Citadel이 대체 가능합니다.

아래 명령어를 통해서 Citadel을 생성을 하면 Citadel은 현재 존재하고 있는 각각의 Service Account마다 Client 인증서를 발급해 주게 됩니다. 또한 Citadel을 설치 후에 생성하는 Service Account에 대해서도 Client 인증서를 발급해줍니다.

생성된 Secret 중 대표적으로 default 네임스페이스에 위치한 istio.default secret을 확인해 보면 아래와 같습니다.

「cert-chain.pem」 은 Client 인증서이고 「key.pem」 은 Client 개인키 ,
그리고 마지막 「root-cert.pem」 은 Self Signed Client Root CA입니다.

Citadel이 생성한 어떠한 Secret 내용을 살펴보아도 default 형태와 동일하게 Client 인증서, Client 개인키, Client Root CA를 가지고 있는 것을 확인 할 수 있습니다. 그리고 istio.default, istio.agones-sdk secret 두 개의 Secret만 비교해 봐도 알 수 있지만 Client Root CA는 동일한 걸 알 수 있습니다.

istio.default secret 내용
istio.agones-sdk secret 내용

GCGS

GCGS는 아래와 같이 간단하게 Realm에 Cluster1과 Cluster2를 추가만 해주면 Cluster 마다 생성이 필요한 GameServerAllocationPolicy 생성 작업 및 allocator-client-ca에 Client 인증서 추가를 자동으로 해줍니다.

위와 같이 Realm에 Cluster를 추가 해주면, 먼저 Google API는 Citadel이 생성한 default 네임스페이스에 있는 istio.default 이름의 Secret의 값을 참조해서 또 다른 Secret을 생성합니다. (Secret 이름은 Realm에 Cluster 포함할 때 사용자가 지정했던 이름이 포함되어 있습니다)

kubectl get secret

생성된 Secret을 확인해 보면 istio.default의 Client 인증서와 개인키 값을 가지며 Server Root CA를 가지고 있습니다. 여기서 왜 Client Root CA가 아닌 Server Root CA를 담고 있을까요?

kubectl get secret allocator-secret-cluster1-… -o yaml

그 이유는 Citadel은 Self Signed 인증서이기 때문입니다. 즉 공인 CA가 아니면 Client가 Request를 보낼 때 자신(Server)의 CA를 같이 보내야만 합니다. 그래서 Client Root CA가 아닌 Server Root CA의 정보를 담고 있는 것입니다.

​​그리고 Google API는 Cluster1에는 Cluster1과 Cluster2의 GameServer
AllocationPolicy생성해주고 Cluster2도 마찬가지로 Cluster1과 Cluster2의 GameServerAllocationPolicy를 생성해줍니다.

kubectl get gameserverallocationpolicy

그리고 GameServerAllocationPolicy정보를 확인해보면 위에서 생성한 Google API가 따로 생성한 Secret을 지니고 있음을 확인할 수 있습니다.

kubectl get gameserverallocationpolicy allocation-cluster1-… -o yaml

마지막으로 allocator-client-ca에 허용해 줄 Client 인증서를 추가해 줍니다. 여기서 allocator-client-ca Secret의 정보를 확인해 보면

kubectl get secret allocator-client-ca -n agones-system -o yaml

Client 인증서 값이 아닌 Client Root CA 값이 추가되어 있는 것을 확인할 수 있습니다. 당연히 특정 Client 인증서만 추가해 특정 Client 만 허용할 수도 있습니다. 하지만 Client Root CA를 추가하게 되면 Citadel이 Service Account마다 생성한 Client 인증서를 모두 허용 되게 됩니다. 즉 Google API는 특정 Client 인증서뿐만 아니라 Citadel이 생성한 모든 Client 인증서로 접속이 가능하게 하기 위해 Client Root CA를 추가해 줍니다.​

그럼으로써 위에서 만든 Client 인증서로 Server에 Multi-cluster Request를 보낼 수 있는 환경이 갖춰졌습니다.

​Multi-cluster Allocation 흐름

이제 Multi-cluster Allocation이 되는 흐름을 살펴보도록 하겠습니다.

매치메이커가 Cluster1에게 Multi-Cluster Allocation Request를 보내게 보내면 우선 Priority가 낮게 설정된 GameServerAllocationPolicy를 선택하게 될 것입니다. ( Priority와 Weigh이 동일한 값이면 랜덤으로 GameServerAllocation
Policy가 선택이 될 것입니다) 여기서는 여러 가지 요인으로 인해서 Cluster2의 Game ServerAllocationPolicy가 선택이 되었다고 가정하겠습니다.


그렇다면 매치메이커로 부터 Multi-ClusterAllocation Request를 받은Cluster1의 aognes-allocator는 Cluster2의 GameServerAllocationPolicy를 확인해 Cluster2의 agones-allocator External IP를 획득 후 해당 IP로 Allocate Request를 보낼 것입니다. Request를 받은 Cluster2는 여러가지 과정을 걸친 후(HandShake) Client 인증서를 요구할 것입니다. 그러면 Cluster1은 Game
ServerAllocationPolicy에 포함되어 있는 Secret의 Client 인증서를(Client 개인키를 제외한) Cluster2에게 보낼 것이며 그리고 PMS Key를 생성해 Cluster2의 공개키로 암호화해 보낼 것입니다.

그리고 이 메시지를 Client 개인키로 암호화해서 한 번 더 보내고 Server의 Root CA도 보낼 것입니다. (위 mTLS 통신과정 3–0에서 3–2까지)

​그러면 Cluster2 측은 allocator-client-ca에 해당 Client 인증서가 있는지 확인을 해 볼 것이고 Client 개인키로 암호화된 메시지는 Client 인증서에 있는 공개키로 복호화를 진행할 것입니다. 만약 복호화가 성공하였다면 이는 Cluster1이 해당 Client 인증서의 개인키를 지니고 있다고 증명된 것이기에 정상으로 판단합니다. 그리고 Client인증서는 공용 CA 사용이 아님으로 Cluster1이 보낸 CA가 자신(server)의 Root CA 인지도 확인할 것입니다. 이 모든 것이 정상적이면 해당 Client의 Allocate Request는 받아들여지게 될 것입니다.

​이런 식으로 Client와 Server 간에 서로를 인증하는 mTLS 통신이 이루어집니다.

GCGS에서 꼭 Citadel을 사용하는 이유가 있는가?

그렇다면 왜 하필 Citadel을 사용하는 것일까요?

정확한 이유는 알 수가 없었습니다. 하지만 Realm에 Cluster를 추가하게 되면 Google API는 istio.default 이름의 secret을 참조해 사용한다는 것은 확인할 수 있었습니다.

​이를 확인하기 위해 Citadel 설치 없이 수동으로 Self signed 인증서를 발급 하고 인증서, 개인키, Root CA 값을 지닌 istio.default secret 생성후에 Realm에 Cluster를 추가해보았습니다

​Google API는 istio.default란 이름만 보고 istio.default안의 값을 참조해 자동으로 Secret 생성했고 , GameServer AllocationPolicy 생성했으며 allocator-client-ca에 인증서 추가해 주는 것을 확인할 수가 있었습니다.

​Google API가 장확하게 어떻게 동작하는지에 대해서는 Code Open이 안되어있기 때문에 확인할 수 없었지만 테스트를 통해서 istio.default라는 이름의 Secret을 참조해 자동으로 GameServerAllocationPolicy등의 작업을 수행한다는 것을 알 수 있었습니다.

정리

GCGS에서의 Citadel은 Service Account마다 인증서를 발급해 주는 것이 전부입니다. 나머지는 Citadel이 생성한 istio.default의 이름을 지닌 Secret를 이용해 Google API에서 새로운 Secret을 생성하고 이를 포함하는 GameServer
AllocationPolicy를 생성하며 allocator-client-ca에 Citidel이 생성한 Client Root-CA를 추가 해줍니다.​

그리고 allocator-client-ca에 Client 인증서가 아닌 Client Root-CA를 추가해주는데 그 이유는 istio.default가 지닌 인증서 뿐만 아니라 Citadel이 생성한 모든 ServiceAccount의 Client 인증서를 Allow 하기 위함이였습니다.

또한 Multi Cluster Allocation 요청을 보낼 때에는 Client 인증서, Client 개인키, 공용 CA로 Client 인증서가 서명되지 않은 경우에는 Server Root CA가 필요합니다.

그리고 allocator-tls Secret에 Root CA가 포함되어 있지 않은 이유는 Client에서 Server Root CA 사용 시 Server의 개인키를 노출하지 않기 위함입니다.

--

--