
오늘은 중요하지만 맨날 까먹는 X.509와 mTLS에 대해서 정리하려고 한다.
사실 X.509와 TLS, mTLS는 엔지니어로 일하다보면, 상당히 많이 듣게 되는데 정작 인증서 발급 과정을 자세히 살펴보지 않거나, 이미 구현되어 있는 경우 어떻게 동작하는지 배우더라도 금방 잊어버리기 쉽다.
필자 또한 마찬가지이며, 이번엔 Kubernetes와 연동해 기억해보려고 한다.
Kubernetes Control Plane의 컴포넌트들은 API Server, Schduler, Controller Manager, ETCD가 존재한다.
이 컴포넌트들은 API Server를 중심으로 상호 작용하며 Kubernetes 클러스터를 유지시킨다.
각 컴포넌트들은 클러스터의 핵심 부분이기 때문에 신뢰하지 못할만한 소스로부터 오는 요청을 막아야 할 필요가 있다.
여기에 X.509 인증서를 통한 인증 방식이 사용되고 있다.
X.509는 공개 키 인증서(Public Key Certificate)의 표준 형식을 정의하는 국제 표준(ITU-T X.509)이고, X.509 형식의 인증서를 제시함으로써 내 신원을 검증받는 것이다.
X.509 인증서를 발급받기 위한 요청자는 신뢰할만한 인증 단체(CA)로부터 인증서를 발급받기 위해 자신의 신원 정보와 공개 키를 포함한 인증서 서명 요청(CSR)을 생성해 CA에 요청하면, CA의 개인키로 서명을 받고 인증서를 발급 받는다.
해당 인증서를 발급받고 통신에 사용할 때엔, 인증서를 발급한 CA의 인증서가 있어야 CA의 공개 키를 추출해 해당 인증서가 CA의 개인 키로 서명을 받았다는 것을 검증할 수 있다.
주로 SSL/TLS 보안 통신에 X.509 기반 인증서가 사용되며, Kubernetes Control Plane 내 컴포넌트 간의 mTLS 통신에 사용되고 있다.
TLS란 클라이언트와 서버 간 통신 데이터를 암호화해주는 보안 프로토콜인데, 일반적으로 서버 측에서 X.509 인증서를 발급받고 해당 인증서로 TLS 통신을 구현해 클라이언트와의 통신이 안전함을 증명한다.
mTLS란 서버뿐만이 아닌, 클라이언트도 인증서를 통해 자신이 안전한 사용자임을 증명하고, 서버 측에서는 서버 측에서 알고 있는 인증서를 가진 요청만 허용해주어 더 안전한 통신을 가능하게 해주는 상호 인증 방식이다.
Kubernetes Control Plane의 컴포넌트들은 X.509 인증서로 상호 인증(mTLS)된 클라이언트-서버 간 통신만 허용하고 있다.
클라이언트는 서버 측에 요청을 보낼 때 검증된 인증서를 같이 보내고, 서버에서는 이를 검증하여 통과된 사용자에게 응답을 주는 방식이고, 이 때 서버의 인증서도 클라이언트 측에서 검증된다.
컨트롤 플레인 내 통신에 사용되는 모든 인증서들은 /etc/kubernetes와 /etc/kubernetes/pki 경로에 있다.
Control Plane 컴포넌트들은 /etc/kubernetes/manifest 경로에 정의된대로 Static Pod로 실행되는데, kube-apiserver.yaml에는 다른 컴포넌트와의 통신에 필요한 인증서와 키를 명시할 수 있다.
...
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=172.30.1.90
- --allow-privileged=true
- --authorization-mode=Node,RBAC
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --enable-admission-plugins=NodeRestriction
- --enable-bootstrap-token-auth=true
- **--etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key**
- --etcd-servers=https://127.0.0.1:2379
- --**kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key**
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
- --requestheader-allowed-names=front-proxy-client
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- --requestheader-extra-headers-prefix=X-Remote-Extra-
- --requestheader-group-headers=X-Remote-Group
- --requestheader-username-headers=X-Remote-User
- --secure-port=6443
- --service-account-issuer=https://kubernetes.default.svc.cluster.local
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
- --service-cluster-ip-range=10.96.0.0/12
- **--tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key**
...
API Server - ETCD 간의 사용되는 인증서는 /etc/kubernetes/pki/apiserver-etcd-client.crt이고,
API Server - Kubelet 간의 사용되는 인증서는 /etc/kubernetes/pki/apiserver-kubelet-client.crt임을 알 수 있다.
--tls-cert-file 옵션과 --tls-private-key-file 옵션을 통해 API Server의 인증서와 개인 키를 지정해주는데, /etc/kubernetes/pki/apiserver.crt 인증서를 자세히 살펴보자.
apiserver.crt 파일은 cat 으로 조회 시 기본적으로 암호화되어있는데, openssl 로 인증서의 내용을 조회할 수 있다.
[dgyoon@kube-master pki]$ openssl x509 -in apiserver.crt -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 992286967592286795 (0xdc54fbd5a967a4b)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=kubernetes
Validity
Not Before: Dec 26 05:51:12 2024 GMT
Not After : Dec 26 05:56:12 2025 GMT
Subject: CN=kube-apiserver
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:bb:36:ab:9f:4a:df:de:ba:f0:ec:82:19:62:f0:
d2:2f:a4:76:1e:e1:26:d4:79:15:2b:1f:0d:32:1e:
6f:fe:e5:94:40:d5:9f:78:a2:e6:56:6d:21:f7:56:
89:44:8f:e2:35:94:d8:1b:ff:70:cc:91:0c:1c:55:
70:53:1a:05:59:a6:b4:50:93:0c:d0:91:07:b1:d2:
91:d6:ef:50:d0:cf:32:89:66:df:92:49:9e:7e:3e:
78:74:3e:a3:70:23:30:03:c3:6c:b4:41:6b:ca:5f:
47:cf:89:70:84:bc:52:40:55:38:53:b2:f8:51:21:
c0:3a:86:f9:b4:e8:68:23:e3:af:cb:57:94:94:18:
33:2c:80:4c:c7:9e:74:93:99:f3:1c:4a:3c:7d:a5:
a9:99:7c:d6:a4:55:62:a8:ec:57:89:f9:57:0c:cd:
66:b1:fa:d7:6b:49:1a:8c:1c:34:55:eb:54:82:7c:
a8:bd:c7:e2:7b:8b:a6:09:69:78:14:2b:d3:6c:3a:
2c:61:ce:31:e4:a6:e5:35:4b:c3:92:01:23:1c:11:
81:4d:33:7a:17:87:57:73:78:44:52:f4:29:bf:d3:
87:55:c8:7b:41:07:7a:2f:72:9b:7a:fc:a9:94:00:
3e:02:5b:3c:29:51:ca:1b:6b:b4:f9:a1:ce:af:0a:
a1:6f
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Authority Key Identifier:
36:0C:69:00:36:E7:78:B0:45:BD:86:8F:C7:0F:83:96:C5:35:EA:90
X509v3 Subject Alternative Name:
DNS:kube-master, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP Address:10.96.0.1, IP Address:172.30.1.90
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
03:ce:9d:0f:d4:37:57:a7:08:4f:49:69:48:a6:ae:37:4a:5b:
6a:4b:64:5a:39:0f:66:b9:8f:0b:5d:dd:bb:aa:51:64:9a:c1:
71:90:c6:9f:c9:bf:8b:6e:8e:60:8b:f6:44:7c:41:3d:7d:bd:
2e:af:d2:4e:a1:72:86:67:46:7c:66:e8:e8:3e:1c:ef:71:26:
f4:89:d4:20:ca:b8:a5:d7:fb:0a:3e:ae:32:24:bf:a5:78:52:
a2:ce:74:ba:07:ac:dc:c5:59:2d:b0:18:ce:23:0a:9b:06:0b:
8a:0f:b7:b0:a6:ec:2d:69:24:1c:98:0b:ef:4d:de:ca:27:e5:
16:a9:5d:65:cc:34:1d:75:d1:5e:67:a2:a1:88:8d:37:f3:e8:
15:4e:b0:76:69:ff:14:83:15:19:9a:e9:b8:2f:80:ad:16:0d:
f8:e3:04:2e:74:7c:1c:f3:5b:18:88:f8:91:07:16:b1:8c:9a:
c5:cd:91:7a:d9:63:4b:23:75:d0:eb:f3:20:9c:40:31:f8:53:
47:41:0c:b9:f6:73:43:58:a7:36:5c:18:ba:85:c2:f8:e3:fe:
e7:4e:ab:af:bb:77:da:e2:a3:5e:f9:b3:4d:24:90:ab:eb:f8:
99:30:a5:0a:8a:59:32:3e:f1:82:73:09:c8:cf:c6:81:ee:e6:
87:11:5e:cd
위 인증서는 kubernetes CA의 서명을 받아 발급받은 인증서이며, 발급자, 발급날짜, API Server의 공개키와 CA의 서명이 담긴 모습을 확인할 수 있다.
ETCD에서 사용하는 인증서는 /etc/kubernetes/pki/etcd 경로에 위치해있는데, 이는 ETCD 서버의 manifest 파일에 명시되어있다.
...
spec:
containers:
- command:
- etcd
- --advertise-client-urls=https://172.30.1.90:2379
- **--cert-file=/etc/kubernetes/pki/etcd/server.crt**
- --client-cert-auth=true
- --data-dir=/var/lib/etcd
- --experimental-initial-corrupt-check=true
- --experimental-watch-progress-notify-interval=5s
- --initial-advertise-peer-urls=https://172.30.1.90:2380
- --initial-cluster=kube-master=https://172.30.1.90:2380
- **--key-file=/etc/kubernetes/pki/etcd/server.key**
- --listen-client-urls=https://127.0.0.1:2379,https://172.30.1.90:2379
- --listen-metrics-urls=http://127.0.0.1:2381
- --listen-peer-urls=https://172.30.1.90:2380
- --name=kube-master
- --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
- --peer-client-cert-auth=true
- --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
- --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --snapshot-count=10000
- **--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt**
...
API Server가 ETCD로 요청을 보낼 때에는 API Server가 클라이언트가 되는데, 이 때 apiserver-etcd-client.crt 인증서로 ETCD에 인증된 사용자임을 증명한다.
그리고 ETCD는 --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt 옵션으로 명시된 인증서를 통해 해당 클라이언트(API Server)의 인증서가 ETCD가 신뢰할 수 있는 CA 인증서로부터 발급된 인증서인지를 검증한다.
/etc/kubernetes/pki/etcd/ca.crt 인증서의 내용을 살펴보면 CA와 인증서 신청자가 etcd-ca로 동일한 것을 볼 수 있다.
[dgyoon@kube-master etcd]# openssl x509 -in ca.crt -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 7686667323274830162 (0x6aac87346e662152)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=etcd-ca
Validity
Not Before: Dec 23 10:21:09 2024 GMT
Not After : Dec 21 10:26:09 2034 GMT
Subject: CN=etcd-ca
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:c1:f0:0f:41:d3:16:ef:77:50:c1:22:ba:c6:b3:
ac:04:8c:13:dd:81:97:60:93:10:ab:fb:51:0d:87:
56:df:3e:0b:b5:0c:ee:fb:bf:cd:f3:85:0a:bc:93:
24:7f:b2:bd:8a:b9:ac:ac:42:1e:ce:f0:03:7c:c8:
72:aa:81:2b:d4:14:d2:7e:96:7a:b2:40:c2:b3:b3:
b7:39:76:b4:0e:71:e9:a0:b7:01:7b:d8:38:e1:9a:
fe:76:97:52:62:5d:ac:cb:3f:82:8b:a2:b0:3e:aa:
b2:d6:b6:c9:e6:d7:b6:ce:35:81:84:5b:67:a3:e1:
32:4c:26:84:1f:c4:93:cc:6c:8f:b2:3a:a4:bc:45:
ca:9d:e2:81:52:9c:fe:c0:5d:6e:aa:35:9b:8d:9e:
18:39:77:f8:6d:71:30:a8:49:e5:9f:bf:a0:62:58:
9f:6f:d8:67:6c:20:52:e2:1e:48:d6:ff:3c:1e:ab:
1c:e7:c3:b7:d5:0a:cb:3f:f5:f4:e6:ac:e6:ed:ed:
f4:c5:b0:6e:2c:05:a7:00:0c:aa:df:90:1c:ed:5d:
6f:1b:87:4c:f9:7f:e3:31:5a:7e:b7:b5:87:40:b9:
71:4d:0a:20:fb:06:b2:ee:e5:e4:1a:03:08:7c:7f:
a5:ec:97:b9:c2:1a:64:53:4e:9b:7f:5c:af:d6:ae:
a2:85
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment, Certificate Sign
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Subject Key Identifier:
3F:FE:12:38:2E:34:DC:F6:72:F9:AE:9B:C7:FF:FC:0C:02:06:4B:96
X509v3 Subject Alternative Name:
DNS:etcd-ca
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
a5:f2:2f:cf:fd:c2:40:e0:d9:78:3e:d9:e1:26:1a:6d:95:ee:
7d:44:9a:8a:e3:1f:20:7d:09:3e:1d:0a:c2:df:19:e2:e3:e4:
52:93:ee:3b:8a:06:1a:ac:ba:41:8d:6a:01:a5:3f:e9:20:75:
a6:65:04:29:9c:e8:26:af:d3:4d:b2:79:13:55:13:16:8f:12:
cc:3a:c8:20:50:2d:a8:10:6a:d7:c3:a1:a1:48:26:e9:60:95:
fb:20:f7:9a:19:2c:bf:b6:0c:fb:9a:79:f1:23:77:25:48:24:
45:d6:4a:e3:00:7d:7d:d6:e7:4a:97:ef:46:17:0d:e4:a3:28:
6c:d3:b6:85:e9:f3:5c:a6:6c:1d:41:32:1e:10:31:f9:b4:40:
7e:ee:c8:ee:8b:cf:6d:6b:31:2a:85:11:69:00:84:24:8d:c1:
6c:b0:c1:57:42:c3:4a:67:e8:7c:97:0e:48:37:39:ce:3a:f7:
82:8d:a9:26:66:b5:89:65:cd:d1:af:4e:1e:eb:48:54:b6:ac:
2d:3e:00:30:f6:3b:b5:0d:9f:0a:99:d5:22:8b:bc:91:88:77:
c2:ab:cb:72:ff:65:b4:ad:c8:9e:9e:9d:32:2a:3e:78:63:9a:
30:26:5d:2f:9d:33:b2:6f:c9:15:b6:fe:4c:ac:6b:1e:38:39:
16:d8:da:2f
이는 CA의 Self Signed 인증서이기 때문인데, 최상위 CA의 경우 자신의 인증서를 검증받을만한 상위의 CA가 없기 때문에 자체 서명된 인증서를 사용한다.
ETCD로 들어오는 요청의 인증서가 위 CA 인증서 담긴 CA의 공개키로 복호화가 된다면, 해당 인증서가 CA로부터 발급받은 인증서가 맞다는 것이 증명되므로, 신뢰할 수 있는 요청임을 알 수 있다.
이제 API Server가 ETCD로 요청을 보낼 때 사용하는 인증서( /etc/kubernetes/pki/apiserver-etcd-client.crt)를 살펴보자
[dgyoon@kube-master pki]# openssl x509 -in apiserver-etcd-client.crt -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3793171968123556004 (0x34a40dced3f950a4)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=etcd-ca
Validity
Not Before: Dec 26 05:51:12 2024 GMT
Not After : Dec 26 05:56:12 2025 GMT
Subject: CN=kube-apiserver-etcd-client
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:a9:79:33:66:55:ce:dc:90:9a:07:96:f4:cd:f6:
e6:bc:f8:76:b2:79:bf:ed:10:10:96:66:e9:eb:55:
c2:7d:29:32:b6:04:f2:ae:b6:01:f6:77:fb:ee:88:
b3:ef:61:5e:67:06:ea:59:20:70:3a:b4:c4:b8:50:
24:8b:60:71:4a:1d:8b:c4:0a:ac:f6:f9:90:ed:55:
96:07:57:af:6d:59:d0:1f:ef:9d:fd:f8:13:46:01:
26:c8:d1:76:f9:fa:94:5f:54:9b:2b:9b:31:97:72:
d1:c1:72:8e:bb:b0:3f:6c:91:53:f1:3a:ab:09:a4:
d1:71:19:7a:5d:e1:d0:55:a8:65:94:f3:f4:e7:af:
07:51:17:22:0e:eb:9b:59:27:bf:99:38:07:d8:59:
0b:03:a8:00:aa:d7:3f:83:7b:61:f8:bb:2c:50:c7:
43:a5:1d:c0:3a:10:53:92:9d:3c:6f:e2:da:46:3d:
8b:e9:42:98:a0:e1:53:80:2a:79:d6:76:c2:0c:00:
07:5b:3a:7c:8f:2c:b8:15:70:14:2d:3d:d9:c8:02:
69:e1:f6:97:1e:cd:a3:30:fe:de:a9:8f:53:f2:a1:
35:a5:1d:34:91:80:9d:4f:49:36:a3:92:05:9d:21:
8d:34:a7:52:7f:09:ed:64:57:77:41:81:40:51:6c:
0f:25
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Authority Key Identifier:
3F:FE:12:38:2E:34:DC:F6:72:F9:AE:9B:C7:FF:FC:0C:02:06:4B:96
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
86:58:f3:22:5b:9d:df:a9:40:24:33:7c:12:3f:99:a1:15:27:
32:df:b8:a4:34:8d:82:e2:bd:d6:90:4f:33:9d:2a:01:d5:cb:
a8:c9:93:36:38:9f:c4:ef:ac:ef:17:71:8a:8d:81:85:fd:d6:
68:67:b0:71:4a:dc:b1:ac:fd:db:38:09:bd:84:15:65:6a:86:
4b:ec:bb:c2:c0:81:1b:6c:7c:59:c9:d9:78:c5:05:90:6f:da:
2a:9c:fd:b3:f1:83:b0:b9:8d:1f:26:8b:52:ff:4e:96:e9:ec:
7f:ff:16:62:ff:69:59:dc:db:2c:f0:bf:9c:54:c9:dc:63:fa:
5b:84:2d:b5:bb:ff:5f:23:76:ac:ce:48:29:40:3b:f2:df:60:
c2:07:4d:db:e9:b4:38:11:6e:bd:02:35:c9:53:02:e0:8d:1d:
cf:b6:a6:d2:4f:da:42:f8:ea:8b:0b:cf:66:04:97:ea:19:2a:
1e:65:7f:57:39:dc:9d:ac:85:63:18:1c:e6:04:c8:eb:d4:e5:
5a:c5:78:da:89:47:33:d6:1a:c7:82:d8:dc:0a:8e:53:f0:b4:
9e:d1:8b:b1:02:dc:e8:93:8c:72:96:8e:89:a7:30:95:d3:2a:
07:a2:77:7d:51:c5:89:c8:f7:02:10:66:63:a7:d9:63:61:dd:
d7:48:5a:36
해당 인증서의 발급자가 etcd-ca인 것을 알 수 있고, 최하단에 서명받았다는 것을 알 수 있다.
이제 CA의 공개키로 해당 인증서가 진짜로 etcd-ca로부터 서명을 받았는지 확인해보자.
[dgyoon@kube-master pki]# openssl verify -CAfile etcd/ca.crt apiserver-etcd-client.crt
apiserver-etcd-client.crt: OK
OK 가 출력되었고 이는 ETCD 서버에서 신뢰할 수 있는 인증서라는 의미가 된다.
Static Pod로 실행되는 Controller Manager의 Manifest 파일을 살펴보자.
[dgyoon@kube-master manifests]# cat kube-controller-manager.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
component: kube-controller-manager
tier: control-plane
name: kube-controller-manager
namespace: kube-system
spec:
containers:
- command:
- kube-controller-manager
- --allocate-node-cidrs=true
- --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
- --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
- --bind-address=127.0.0.1
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --cluster-cidr=10.244.0.0/16
- --cluster-name=kubernetes
- --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt
- --cluster-signing-key-file=/etc/kubernetes/pki/ca.key
- --controllers=*,bootstrapsigner,tokencleaner
- --kubeconfig=/etc/kubernetes/controller-manager.conf
- --leader-elect=true
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- --root-ca-file=/etc/kubernetes/pki/ca.crt
- --service-account-private-key-file=/etc/kubernetes/pki/sa.key
- --service-cluster-ip-range=10.96.0.0/12
- --use-service-account-credentials=true
image: registry.k8s.io/kube-controller-manager:v1.31.4
name: kube-controller-manager
...
--client-ca-file=/etc/kubernetes/pki/ca.crt 를 통해 클라이언트 측 인증서를 검증할 수 있는 CA 인증서를 명시해주고 있다.
--cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt,--cluster-signing-key-file=/etc/kubernetes/pki/ca.key옵션들은 Kubernetes 내에서도 인증서 서명 요청(Certificate Signing Request) 리소스를 생성해 인증서를 발급할 수 있는데, 그 때 CSR Controller가 서명하기 위해 사용하는 인증서와 키라고 한다.
그리고 Controller Manager가 클라이언트일 때의 요청에 사용되는 인증서는 /etc/kubernetes/controller-manager.conf 파일 내부에 존재한다.
[dgyoon@kube-master kubernetes]# cat controller-manager.conf
apiVersion: v1
kind: Config
...
users:
- name: system:kube-controller-manager
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0...
...
...
users.user.client-certificate-data를 base64로 디코딩해보면 인증서인 것을 확인할 수 있다.
[root@kube-master kubernetes]# echo 'LS0tLS1CRUdJTiBDRVJUSUZJQ0...' | base64 -d
-----BEGIN CERTIFICATE-----
MIIDFjCCAf6gAwIBAgIITi9orqx8XD0wDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNDEyMjYwNTUxMTJaFw0yNTEyMjYwNTU2MTJaMCkx
...
-----END CERTIFICATE---
그리고 이 인증서를 API Server 측의 CA 인증서로 검증해보면 OK 로 표시되는 것을 확인할 수 있다.
[dgyoon@kube-master kubernetes]# echo '-----BEGIN CERTIFICATE-----
MIIDFjCCAf6gAwIBAgIITi9orqx8XD0wDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNDEyMjYwNTUxMTJaFw0yNTEyMjYwNTU2MTJaMCkx
...
-----END CERTIFICATE-----' > controller.crt
[dgyoon@kube-master kubernetes]# openssl verify -CAfile pki/ca.crt controller.crt
controller.crt: OK
이와 동일하게 Scheduler의 인증서도 /etc/kubernetes/scheduler.conf에 존재하고 동일한 과정을 통해 확인해볼 수 있다.
API Server가 Controller Manager와 Scheduler에 요청을 보내는 클라이언트 입장일 때 사용되는 인증서가 따로 지정되지 않는 이유는 API Server는 두 컴포넌트에게 요청을 받기만 할 뿐 보내지는 않기 때문이다.
systemctl로 Kubelet의 실행 상태를 확인해볼 수 있다.
[dgyoon@kube-master kubernetes]# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; preset: disabled)
Drop-In: /usr/lib/systemd/system/kubelet.service.d
└─10-kubeadm.conf
Active: active (running) since Thu 2024-12-26 14:58:21 KST; 3 months 3 days ago
Docs: https://kubernetes.io/docs/
Main PID: 2559479 (kubelet)
Tasks: 31 (limit: 408096)
Memory: 138.4M
CPU: 1d 2h 163ms
CGroup: /system.slice/kubelet.service
└─2559479 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock --pod-infra-container-ima>
--kubeconfig=/etc/kubernetes/kubelet.conf , --config=/var/lib/kubelet/config.yaml 옵션을 통해 Kubelet 설정 파일의 위치를 확인할 수 있다.
[root@kube-master kubernetes]# cat /etc/kubernetes/kubelet.conf
apiVersion: v1
kind: Config
users:
- name: system:node:kube-master
user:
client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
...
확인해보니 Kubelet의 인증서와 키는 /var/lib/kubelet/pki/kubelet-client-current.pem 경로에 있음을 알 수 있다.
[dgyoon@kube-master kubernetes]# cat /var/lib/kubelet/pki/kubelet-client-current.pem
-----BEGIN CERTIFICATE-----
MIIDJjCCAg6gAwIBAgIIG5KYerIJhJEwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNDEyMjMxMDIxMDlaFw0yNTEyMjMxMDI2MTBaMDkx
...
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAtPgxFtXGqkdo8tBkDyBLTV75UGHCwOfE9K294rS+2eYsqI3V
KPq2MEVA0F+mirhjGl57F/LueNwW1XyiUVhqNbQZ1Ab/EOsO1RS2Exi4op/nnUEm
...
-----END RSA PRIVATE KEY-----
위 인증서는 Kubelet이 API Server에 클라이언트로서 요청을 보낼 때 사용하는 인증서이며, pem 파일에서 인증서만 추출해 API Server의 CA 인증서로 검증해보면 OK 가 나오는 것을 확인해볼 수 있다.
[dgyoon@kube-master kubernetes]# echo '-----BEGIN CERTIFICATE-----
MIIDJjCCAg6gAwIBAgIIG5KYerIJhJEwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNDEyMjMxMDIxMDlaFw0yNTEyMjMxMDI2MTBaMDkx
...
-----END CERTIFICATE-----' > kubelet.crt
[dgyoon@kube-master kubernetes]# openssl verify -CAfile pki/ca.crt kubelet.crt
kubelet.crt: OK
반대로 API Server가 클라이언트일 때는 /etc/kubernetes/pki/apiserver-kubelet-client.crt 에 위치한 인증서가 사용되며(API Server Manifest 파일에서 확인), Kubelet이 클라이언트 요청에 검증하는 인증서는 /var/lib/kubelet/config.yaml 에서 확인 결과 /etc/kubernetes/pki/ca.crt 파일이다.
[dgyoon@kube-master pki]# cat /var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
...
그리고 openssl로 확인해보면 유효한 인증서임을 확인할 수 있다.
[dgyoon@kube-master pki]# openssl verify -CAfile /etc/kubernetes/pki/ca.crt /etc/kubernetes/pki/apiserver-kubelet-client.crt
/etc/kubernetes/pki/apiserver-kubelet-client.crt: OK
관리자가 사용하는 kubectl명령어는 API Server로 쉽게 요청을 보내주는 클라이언트 측 명령어에 불과하다.
즉, kubectl 명령어를 사용하는 클라이언트 역시 자신이 신뢰할 수 있는 사용자라는 것을 인증서를 통해 증명해야한다.
주로 사용자의 홈 디렉토리의 .kube/config 에 위치한다.
[dgyoon@kube-master .kube]# cat config
apiVersion: v1
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJTjZRTHJHY1VsSjh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRFeU1qTXhNREl4TURsYUZ3MHlOVEV5TWpNeE1ESTJNRGxhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXQ5c0dBOWczanpZYlYzYmQKY0MyTVNON3NicUZJR0U5SEx2bjllT1Uyd0JDT3UxeTFDdTkwNUc2SmpTbnV5N0d6bGlnQTMrcndZVnI1dzZOcgowc0NpalVFOVV4NkJkNExWbVJkb3hUUng4Mlg1NnVLQllzdXR0OUpFZTk0NkswYm9yVmdKODZKVWhZNllnU2NWCmxaakpCb0pWU2hJaVUyYkxqaGhvMERUM2hwb3llT3ViaFBqdFphREpFZk1FWGNoOGhWM1VDVzU0ME10Wk9JUVQKd0pUQ2RzZ1VmSUhJNTQ1dllmcGFnUlRxeFdUdVBsRnI3bmVXS1IvSFJGNDVJUW9KS0l2cmNMSTJ1elRWM240MQp4L2tCbXE3bWJEeVR5QjkvMUgyQy9qdk9pL1pnTVlZQ0tUdFZWWTM4UnlkMmRISWhYNUlmNnJhbjg4dG1jMHBqCi9NT3RTd0lEQVFBQm8xWXdWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVHREFXZ0JRMkRHa0FOdWQ0c0VXOWhvL0hENE9XeFRYcQprREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBWDRBVEpFSy9XNUo4YWcxdUhPdUg4K0psT2JCUDVPdWcwSGN6CmNNNFNkbXY3a0ZtUTFmVWwvWnhrMm5aZ1Nva2hJOVNJbHNmSmc2bHBmS0RDaHlhZXZGRm15MThpV29hYWpGdXkKeTNCV09FN2pGMmVnY3NhMnFXU1lSNUNVWEsyMEJVL2VFRUoyNHpBa2h5ZWtCVjI2M3RtR2xmNExwR0c0Y0RncgpSSHoxRHhLSytIQjB5NWpuZ2pFdkhvRkdXRFVBcENjTFRKWjFlenowbjBCVlphbXVrWTZWNjlIZjF3R0ZzVHlyCklKVGRYY1lSYTRFNkVHczRSSEllNHRYcGh2RW1VaFdRUFdIcHphNjlDOE8vVTQrTXMrdUQ3MmdVdFB0Ym15VlgKS2h6SW5YNS84bjR0ejh0azhzRVFsYVl1eTVvSUorMUFwVElLUFFtRDhYWGpWNmZhdlE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
...
client-certificate-data 데이터를 디코딩하고 이를 API Server의 CA 인증서로 검증해보면 OK가 나오는 것을 확인할 수 있다.
[dgyoon@kube-master .kube]# echo 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t...' | base64 -d > kubectl.crt
[dgyoon@kube-master .kube]# openssl verify -CAfile /etc/kubernetes/pki/ca.crt kubectl.crt
kubectl.crt: OK
이번 글에서는 Kubernetes Control Plane 컴포넌트 간의 통신이 mTLS로 상호 인증을 통해 보호되며, X.509 인증서를 사용한다는 것을 정리했다.
그리고 그 과정에 사용되는 인증서들을 하나씩 검증해보았고 모두 검증이 되는 것을 확인했다.
인증서 관련 트러블슈팅을 위한 기초 지식을 다질 수 있었고, 매번 잊어버렸던 X.509 인증서와 mTLS 또한 상기해볼 수 있는 시간이었다.