Kubernetes

AEWS [3기] 6주차 - K8S API Server Client Certificate 인증

yu3papa 2025. 3. 15. 22:03

쿠버네티스는 API Sever가 제공하는 리소스(Deployment, Service, Pod, ...)를 생성, 수정, 삭제하면서 어플리케이션을 운영하게 됩니다.

이때 kubectl 도구를 많이 이용하는데, 이는 K8S API Server에게 REST Call을 하는 클라이언트 프로그램입니다.

node 목록을 조회하는 아래 명령을 수행할때 verbose 레벨을 6으로 조정하면 호출되는 REST API 엔드포인트를 확인 할 수 있습니다.

[root@k8s-cp ~]# kubectl get no --v 6
I0315 21:54:38.318179  440925 loader.go:395] Config loaded from file:  /root/.kube/config
I0315 21:54:38.340374  440925 round_trippers.go:553] GET https://192.168.10.50:6443/api/v1/nodes?limit=500 200 OK in 13 milliseconds
NAME     STATUS   ROLES           AGE   VERSION
k8s-cp   Ready    control-plane   94d   v1.31.3
k8s-w1   Ready    <none>          94d   v1.31.3
k8s-w2   Ready    <none>          94d   v1.31.3

 

위의 엔드포인트를 curl을 이용하여 호출해보면 아래와 같은 응답을 받게 됩니다.

[root@k8s-cp ~]# curl -k https://192.168.10.50:6443/api/v1/nodes
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "nodes is forbidden: User \"system:anonymous\" cannot list resource \"nodes\" in API group \"\" at the cluster scope",
  "reason": "Forbidden",
  "details": {
    "kind": "nodes"
  },
  "code": 403
}

API Server 입장에서 노드 목록을 조회하려는 사람이 누구인지 인증(Authentication) 하여야 하고, 인증이 되었다면 노드 목록을 조회할 수 있는 권한이 있는지 인가(Authorization)를 통과해야 정상적인 노드 목록에 대한 응답을 반환할 것입니다.

https://hanee24.github.io/2018/04/21/authentication-authorization/

 

이때 kubectl 명령은 OS 계정의 홈경로밑에 ~/.kube/config 파일을 이용하여 Authentication과 Authorization을 위한 정보를 API Sever에게 제공합니다.

~/.kube/config 파일 내용

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJU3ZkcHFYeWMwMGd3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRFeU1URXdNVFV3TkRKYUZ3MHpOREV5TURrd01UVTFOREphTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUM1VXhocmRCNTFwY2JvUGRSamd1NGttUXQveVN3RjFtRDRVYllESXVsSXcvYUoybVRzclZCa1gwcE8KaEJ1a0N0TWh0ajRxOGVkb2dFdzI5ZVRKeTVvNEZwL1lHODVUdlY0bjJlWkE1YWsvOFFrOGNVQ0YzemdYZWFJbQpreUhMSE90bGxVTEdaRlI4UWMrcU5ROGJFeHd0d0MwM0JaTzVnWTd4VXo3ZzZEQk1pM3JRYUd6Z0ZjcG1yZTdwClliTnhFZ0l0b1AxSlhIbTJqMFRTRVQ2VUNZaDk0eE9KZXJsbTQyUHNIMTU3OHJ2WGExeDZoeU5XdzVGQkRsYXMKZXFTaGRPWXA0aGNmMlM4Q3prWWtTWCtCME4wUVROYmJCdktEbEE4MkhvaXk4dTR3NkRibmZTNWw1WEZxdVczOQpsSWVhZnNJMHFSeTZzV05qZy9BYUxtVkJscW9iQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUQXVGL1dYQmNJQnFOSE9YbVBic1VPYVRsZjN6QVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQm9VOFhhWjhZQgpDYnV4RmtJUXhPc3paemxLT2Jxam5pZERTQmxlUUsxMVdWeDNiMTljYjlWUXpTeFlaS0JqWkdaRHVsazhFVEM3CnhxVUcyWHlaamd5aUgwYi9KQzR5dE9BL2VVUFVQQ3NtRTdjSVR3amY3dzc5N1hQVjh4Z2NSMHJqT1g1ZlFxZHQKYVhjRnVwZmtLQjNmTkMzVEF3eURnWEtQbW01ZUVCenRrT09YRldMM1NrcExBRDVnZ3JpSXVNMUVqTjZMc285Zwo0dFhGTWhIVjVTZ05zcmxRd3QxWWkrSDBkVmFQRDhPMGR6TTFSTU9URXBnVDZPYjJycjBaRTVLN0VoZHhjREE5CllyZXNZSDJFbExCU0FLU2dvdmcyY2Fmb0tkcm1jZE44RTdHMWNheXZsWkR5V1I2d2Y4UlBJQ2pmS3hFeE1CalAKWFNRWGpEbDEzT3BuCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    server: https://192.168.10.50:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJT0hvUHl2T2ozOEl3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRFeU1URXdNVFV3TkRKYUZ3MHlOVEV5TVRFd01UVTFOREphTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDaWQ2Y1EKbU1PajdmTW9VV0Vac1RuTU83UUE1S0RuMlVQKzJFQ2JOTSt0bmN1WTJ1dVQ1dTYyUE9iUDRQV1N4KzBHak5xcQptMkxkcW90OVVSSzJLczZSQWg2QmZUVFNmQktZTDFOaU9MRjdnMC9jaVQzRHk1bm52TFRZdW9yR25FcElseE1aCnk5TVR2WmNFbktha0ovTUJpbGF3MlczdjlucC9JemxaR2kyaUdmaG9LTmZXRk5KWUE2Mm84Z3IrQThzb29PKzYKb2tFRUpyMEZEYzlIYjRyNHdTRUZUU1N0dnJVOC9JTjZEV2VsOXY5dFd6dUt0b211aUducFI0cW9YLzZLLzJsMQp4Qks0enJ3TGFxaHVZYUU1TWl2VU00a0tNa0JBRWROYkxIME5QaW55Z01DZC9JVG1OajBESE1QcW1QM04rQnV1CjRBRUJwcC90UHRqNHNVQkhBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRk1DNFg5WmNGd2dHbzBjNQplWTl1eFE1cE9WL2ZNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUNDZWRXTTNEeHNhR1dISm1sUmtndFVxRTl3CnNwT1FxOUJVdEplWXE0WXVEUlVRNTV5UFdHSmNJMUoxeEVOOFQ5ZEwzdENrQW91ZG9zWFdoUWVxTzNKeXUxV0gKR0hTajMzendUMkZXODVMdWFiS2NtTXpDcEhSVDhNVmgvZVBhSjVPbHA3Z1I4RHpXTGNtUzJUNTdYbm0rZVQ0NApTTjFHSWhnems3QzQ0MHFiU2YzRHlRRURFRFlOWGUyeVZOOXJFTkNTYWtFZWJrNm9EQ2U4STBTVTZPcDBYT0JpCis4a1MzTTlRN2hQcUI4ZFhuOE1VY1JCUFNtRXkzdDBBbjFDRnZvSU42aXdCTFM0Yit1UGNPRnZ5Q2UvYm5NZmUKa1NsajBRT0JDUHBhekdMNW4yQitOUUI2UzFVamxub0U4ZHhXVW5TYXJ1NjNLTUdNUTNzTGEwdGxBUGw2Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBb25lbkVKakRvKzN6S0ZGaEdiRTV6RHUwQU9TZzU5bEQvdGhBbXpUUHJaM0xtTnJyCmsrYnV0anpteitEMWtzZnRCb3phcXB0aTNhcUxmVkVTdGlyT2tRSWVnWDAwMG53U21DOVRZaml4ZTROUDNJazkKdzh1WjU3eTAyTHFLeHB4S1NKY1RHY3ZURTcyWEJKeW1wQ2Z6QVlwV3NObHQ3L1o2ZnlNNVdSb3RvaG40YUNqWAoxaFRTV0FPdHFQSUsvZ1BMS0tEdnVxSkJCQ2E5QlEzUFIyK0srTUVoQlUwa3JiNjFQUHlEZWcxbnBmYi9iVnM3CmlyYUpyb2hwNlVlS3FGLytpdjlwZGNRU3VNNjhDMnFvYm1HaE9USXIxRE9KQ2pKQVFCSFRXeXg5RFQ0cDhvREEKbmZ5RTVqWTlBeHpENnBqOXpmZ2JydUFCQWFhZjdUN1krTEZBUndJREFRQUJBb0lCQUJvdHJYMi9IOGNLVUdQdQp1UGFxMStWUXVYQnJab0gxUUQrUUdOanBhWTFRSDc1QmlEUU13ZW9iWmhXWTZmajh2ZlNRUGF1LzNlNVp6S0FrCnNCOHFScDZXK05Oa21WNVJINjNsOGttaFdQZk44TkJZVE50L3N3emtHei9Rend1eG8wYVBFVVRXMHV3YllIb2QKdzd3dnM3aEwwaXdzM01vTnJTOVBQYTVkeVh5VThPL096ekN4MGE5VThuUVJOdzRvNm1SSVgyWVpRdEdua1RwUAo3QWlzdGNVR1VvQ2lEUlc5SDZIcDE5UjF4b2prb296b1NsZHNINFJqaXZzay9lejNxOTRoRlhGclhmd0doakdZCkFOWENhS2FNVENCTVpNQ3VRTDQrdlAyNzVqM3RuTHlCbThmanE5UUtRenYvOW5aR1ZtYllTQWQ3aU1ZeUF3MFYKK052aS9nRUNnWUVBMHJSMFQxWXdkVklCOCtQNHJQcWovUm5xMzJid2Y0SDFUSUpPOEUzREZweFVuN1NPR2tZWgozZ2VZczlKNUpuQUFBeE5vZGhRUHNoWFRNb000V2hqL0FDdmpaalgySS8xUEw2YmNPdHNvanJFTUF4Zy9heFRYClloMVdmd1FrcVltWGpOUTRjd3NkNGx5amV5RkJ4c2ZPUWJ1UFQrR28wSGJFbGR5UXU3a09aSTBDZ1lFQXhXU1YKaHZYSVlOL1ZkdFh6cXRRTndWTk1lTFFROVRnK2pyZyt0NWRVRGs2T0VYQU4xZG8xWTZUZWlGTjNyRXd3NCtDcQoxb21BUmlFcXdrcUZSZGlOWXJFR2w3OENUNFhjbGVTWm5oMDZxWlpoY0pNRkQ2MkdSbzlac3hGaWZsU2Z3dUtXCjEyMmxocytodFZGOHJMR2I0Y096cnhxMGQ5N0tpdHpmbmwzOXhTTUNnWUJ4OGRab1JSTGxqeGl4UE5weEtRWGYKek5GQVNObk1Db3FOK25ZWDYrV1JaNjgzd3B1S1I5aWJRbjVvMkd4ZHhmbXJzQ1l3bkw2MExxdE15THVEdGJySQo2L01JaGl2NjRIU2ZMMXd2ays4RjQyYThrYk5TQkM1Sng5WEwwSURIRWpwYmxUU0Z1a0JKOXBFTitEa3E5MGhxCk1iVFpuUTMrMWd1UGczemRsOERaelFLQmdRQ04vRVVHV0NNemM2NVRsYUI1WDZFcFQ2YXhLQzBPaHMrUXozemwKaUdmQkJzWndTcDdaRmVYVGNCTldFbHdCeHFoZ01nb1EyY1U0TE1GYUVrcDlMRUMvSGNFMVJBTUFlclJwNlk2SwpTbElaREgrNGZ6aGp4NWpQaG11cVA3WU9Vd2E4STBzSFdPcDQ1ejlWY25Ed3M2MENIMGhQaXpFSERHMjZweGYvCkRYTHQ2d0tCZ1FERFByWW1SbnI2dVNjVmtoY1ZkQWZBUFMrTXNhb0tjR25BdUpjZzNlMlpzTi9EZWNMRWpTajMKdEFudXZPNmIxaXZHOEJaSnZnb2thYms3RTVETGwya1NROGJBSjFDeEFDcC90YzU5cnpMMktIQUJ6Z1FLcWcyRgpRVHEvTEc4N2Vra0liL0h5SkpXSks4cmZGcnprdVI3eXZxTy9wM3ZLK2JnS1ZuVXRXcUNqOEE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=

 

K8S 사용자와 그룹

API 서버는 2가지 유형의 클라이언트로 구분합니다.

  • Human User
    • 실제 사람으로 LDAP, SSO 같은 별도의 외부 계정 시스템을 통해 관리
    • 쿠버네티스에는 사용자계정을 나타내는 리소스는 없음
  • Machine User
    • POD 에서는 Service Accounts 라는 메커니즘을 사용하고, ServcieAccount 리소스로 생성되고 저장

그룹

  • 사용자 및 ServiceAccount는 하나 이상의 그룹에 속할 수 있다
  • 인증 플러그인이 사용자 이름/ID와 함께 그룹을 반환한다
  • 그룹에 속한 사용자에게 한 번에 사용 권한을 부여하는데 사용
  • 시스템에서 제공하는 기본 그룹

인증(Authentication) 절차

  • 클러스터의 API에 접근하기 위해서는 우선 유효한 사용자인지 인증(authentication)을 거쳐야 한다
  • API 서버는 하나 이상의 인증 플러그인으로 구성될 수 있음
  • API 서버가 요청을 받으면 인증 플러그인 목록의 각각에게 요청을 전달해 요청을 보낸 사람이 누구인지 확인한다
  • 해당 요청에서 정보 추출이 가능한 첫번째 플러그인은 사용자 이름, ID, 클라이언트가 속한 그룹을 API 서버 코어에 반환한다
  • API 서버는 나머지 인증 플러그인의 호출을 중지하고 승인(Authorization) 단계로 넘어간다
  • 쿠버네티스는 자체적으로 사용자 계정을 관리하고 이를 인증(Authenticate)하는 시스템을 가지고 있지 않다
  • 별도의 외부 계정 시스템을 사용해야 하며, 계정 시스템 연동을 위해서 OAuth(Google Account, ...)나 Webhook, LDAP과 같은 계정 연동 방식을 지원한다
  • API 서버를 시작할 때 명령행 옵션을 통해 인증 플러그인을 활성화할 수 있다

쿠버네티스에서 지원하는 사용자 인증 메커니즘

쿠버네티스 내부 인증서 관리

kubeadm init 명령을 이용하여 바닐라 쿠버네티스를 구성할 때 아래와 같이 여러가지 인증서가 생성됩니다.

기본적인 인증서의 유효기간은 1년이므로, 1년이 지나면 인증서 갱신 작업이 필요합니다.

 

인증서 만료기간은 kubeadm certs 명령을 이용하여 확인이 가능합니다.

[root@k8s-cp ~]# kubeadm certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Jan 13, 2026 09:03 UTC   364d            ca                      no
apiserver                  Jan 13, 2026 09:03 UTC   364d            ca                      no
apiserver-etcd-client      Jan 13, 2026 09:03 UTC   364d            etcd-ca                 no
apiserver-kubelet-client   Jan 13, 2026 09:03 UTC   364d            ca                      no
controller-manager.conf    Jan 13, 2026 09:03 UTC   364d            ca                      no
etcd-healthcheck-client    Jan 13, 2026 09:03 UTC   364d            etcd-ca                 no
etcd-peer                  Jan 13, 2026 09:03 UTC   364d            etcd-ca                 no
etcd-server                Jan 13, 2026 09:03 UTC   364d            etcd-ca                 no
front-proxy-client         Jan 13, 2026 09:03 UTC   364d            front-proxy-ca          no
scheduler.conf             Jan 13, 2026 09:03 UTC   364d            ca                      no
super-admin.conf           Jan 13, 2026 09:03 UTC   364d            ca                      no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Jan 11, 2035 09:03 UTC   9y              no
etcd-ca                 Jan 11, 2035 09:03 UTC   9y              no
front-proxy-ca          Jan 11, 2035 09:03 UTC   9y              no

 

쿠버네티스 내부에서 사용하는 인증서는 kube-system 네임스페이스의 kubeadm-config 라는 컨피그맵에 기록됩니다.

 

위 인증서 파일중에서 Root CA 인증서와 “kube-apiserver” 에서 사용하는 인증서 파일 정보를 확인해 보겠습니다.

 

API Server

Authentication 과 Authorization은 API Server가 담당하며, API 서버는 kubelet이 관리하는 Static POD로 실행됩니다.

  • 인증을 관리하는 API Server도 kubelet이 관리하는 Static POD
  • /etc/kubernetes/manifests/kube-apiserver.yaml 에 POD 매니페스트가 정의 되어 있음

인증서(Certificate)와 Private Key

  • 암호화 통신을 위해 필요한 2가지 파일
  • 인증서
    • SSL 암호화 통신에 사용할 공개키(Public Key)를 포함
    • 무엇을 인증하고 있는지에 대한 정보 포함
  • Private key
    • Public-Key를 암호화한 데이터를 복호화할 수 있는 유일한 수단

 

Client Certificate Authentication

~/.kube/config 파일에는 API 서버 인증을 위한 정보가 아래와 같이 포함되어 있습니다.

위에 언급한 내용중 "Client Certifacate" 내용을 확인해보면 Organization 정보와, CN(Common Name)을 확인할 수 있는데 이부분이 API Server 가 인식하는 GROUP명과, USER 명에 해당되며, 이 정보를 이용하여 RBAC 제어를 하게 됩니다.

 

쿠버네티스 Client Certificate 인증 실습

실습 개요

  • 일반유저(honggildong) 에게 자신만의 쿠버네티스 네임스페이스에서만 애플리케이션 배포 및 테스트 할 수 있도록 환경 구축
  • 그 외의 네임스페이스는 요청 거부

# “honggildong” 개발자의 작업영역인 namespace 생성
[root@k8s-cp ~]# kubectl create ns honggildong
namespace/honggildong created

# “honggildong” 리눅스 계정 생성하고 암호는 “jadecross”로 설정
[root@k8s-cp ~]# useradd -G wheel honggildong
[root@k8s-cp ~]# passwd honggildong
Changing password for user honggildong.
New password: jadecross
Retype new password: jadecross
passwd: all authentication tokens updated successfully.

 

일반유저용 CSR 생성

# “honggildong” 리눅스 계정으로 쉘로그인후, RSA 알고리즘을 사용하는 Private Key 파일 생성
[honggildong@k8s-cp ~]$ openssl genrsa -out honggildong.key 2048
[honggildong@k8s-cp ~]$ cat honggildong.key
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzJ9wqd654PSnS
~~~
-----END PRIVATE KEY-----

# 생성된 PrivateKey를 이용하여 CSR(Certificat Signing Request) 파일 생성
[honggildong@k8s-cp ~]$ openssl req -new \
-key honggildong.key \
-subj "/O=DEV/CN=honggildong" \
-out honggildong.csr
[honggildong@k8s-cp ~]$ cat honggildong.csr
-----BEGIN CERTIFICATE REQUEST-----
MIICaTCCAVECAQAwJDEMMAoGA1UECgwDREVWMRQwEgYDVQQDDAtob25nZ2lsZG9u
~~~
-----END CERTIFICATE REQUEST-----

 

CSR 파일으 O, CN 정보가 올바른지 확인합니다.

CertificateSigningRequest

 

개별사용자의 CSR  서명은 쿠버네티스 클러스터의 Root CA 를 이용하여 서명해야 합니다.

Root CA를 통하여 인증서 생성을 위한 CSR 리소스를 아래와 같이 생성합니다.

클러스터 관리자는 개별 사용자용 CSR 리소스를 생성하고 승인하여 클라이언트 인증서를 생성합니다.

[root@k8s-cp ~]# kubectl get csr
No resources found

[root@k8s-cp ~]# kubectl apply -f ~honggildong/honggildong-csr.yaml
certificatesigningrequest.certificates.k8s.io/honggildong-csr created

[root@k8s-cp ~]# kubectl get csr
NAME              AGE   SIGNERNAME                            REQUESTOR          REQUESTEDDURATION   CONDITION
honggildong-csr   11s   kubernetes.io/kube-apiserver-client   kubernetes-admin   <none>              Pending

[root@k8s-cp ~]# kubectl get csr honggildong-csr -o yaml
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"certificates.k8s.io/v1","kind":"CertificateSigningRequest","metadata":{"annotations":{},"name":"honggildong-csr"},"spec":{"groups":["system:authenticated"],"request":"LS0tLS~~DATA+OMITTED~~LQo=","signerName":"kubernetes.io/kube-apiserver-client","usages":["digital signature","key encipherment","client auth"]}}
  creationTimestamp: "2025-03-02T06:19:36Z"
  name: honggildong-csr
  resourceVersion: "20943"
  uid: f667e749-5d73-4f76-a69f-d15db2f1f299
spec:
  groups:
  - kubeadm:cluster-admins
  - system:authenticated
  request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ2FUQ0NBVkVDQVFBd0pERU1NQW~~DATA+OMITTED~~Qo=
  signerName: kubernetes.io/kube-apiserver-client
  usages:
  - digital signature
  - key encipherment
  - client auth
  username: kubernetes-admin
status: {}

 

클러스터 관리자는 제출된 CSR 을 Approve 하여, 디지털 서명 후 클라이언트 인증서를 생성합니다.

[root@k8s-cp ~]# kubectl certificate approve honggildong-csr
certificatesigningrequest.certificates.k8s.io/honggildong-csr approved

[root@k8s-cp ~]# kubectl get csr
NAME              AGE     SIGNERNAME                            REQUESTOR          REQUESTEDDURATION   CONDITION
honggildong-csr   6m20s   kubernetes.io/kube-apiserver-client   kubernetes-admin   <none>              Approved,Issued

# certificate 필드에 있는 부분이 생성된 인증서 정보입니다.
[root@k8s-cp ~]# kubectl get csr honggildong-csr -o yaml
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
~~~
spec:
  groups:
  - kubeadm:cluster-admins
  - system:authenticated
  request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ2FUQ0NBVkVDQVFBd0pERU1NQW9HQTFVRUNnd0RSRVZ~~DATA+OMITTED~~XTVJRdLS0tLQo=
  signerName: kubernetes.io/kube-apiserver-client
  usages:
  - digital signature
  - key encipherment
  - client auth
  username: kubernetes-admin
status:
  certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURHVENDQWdHZ0F3SUJBZ0lRZHJKeTFOeTVuKzFKNFNNVklZRZ~~DATA+OMITTED~~JQ0FURS0tLS0tCg==
  conditions:
  - lastTransitionTime: "2025-03-02T06:23:03Z"
    lastUpdateTime: "2025-03-02T06:23:03Z"
    message: This CSR was approved by kubectl certificate approve.
    reason: KubectlApprove
    status: "True"
    type: Approved

디지털 서명된 CSR로부터 클라이언트 인증서 추출 후 인증서 정보 확인합니다.

[root@k8s-cp ~]# k get csr honggildong-csr -o jsonpath='{.status.certificate}' | base64 -d > ~honggildong/honggildong.crt
[root@k8s-cp ~]# openssl x509 -text -noout -in ~honggildong/honggildong.crt
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            8a:de:cb:e1:29:2d:a5:e3:d4:bb:02:64:8d:7a:2a:a0
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = kubernetes
        Validity
            Not Before: Mar 10 01:20:02 2025 GMT
            Not After : Mar 10 01:20:02 2025 GMT
        Subject: O = DEV, CN = honggildong
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:cf:36:87:05:d7:5e:b5:7e:a8:e2:dc:0f:2c:97:
                    ~~~
                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:
                0B:34:FC:FB:AD:98:38:6E:E8:A4:78:5A:B4:5D:13:4A:E1:2D:DD:14
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        24:44:5b:ea:80:d6:62:db:c6:ea:eb:fb:5c:90:9c:99:a9:0c:
        ~~~

 

일반 사용자를 위한 Kube Config 작업 - Set a cluster entry in kubeconfig

# “hongildong” 사용자를 위한 Kubernetes Configuation File 이 생성되었는지 확인
[honggildong@k8s-cp ~]$ cat ~/.kube/config
cat: /home/honggildong/.kube/config: No such file or directory

# “hongildong” 사용자를 위한 K8S Cluster 추가하고 목록 확인
[honggildong@k8s-cp ~]$ kubectl config set-cluster edu-k8s \
--server=https://172.31.1.10:6443 \
--certificate-authority=/etc/kubernetes/pki/ca.crt
Cluster "edu-k8s" set.

[honggildong@k8s-cp ~]$ kubectl config get-clusters
NAME
edu-k8s

# Kube Config 파일에 등록된 클러스터 확인
[honggildong@k8s-cp ~]$ cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /etc/kubernetes/pki/ca.crt
    server: https://172.31.1.10:6443
  name: edu-k8s
contexts: null
current-context: ""
kind: Config
preferences: {}
users: null

# “honggildong” 사용자를 위한 Credential 등록
[honggildong@k8s-cp ~]$ kubectl config get-users
NAME
[honggildong@k8s-cp ~]$ kubectl config set-credentials honggildong \
 --client-certificate=/home/honggildong/honggildong.crt \
 --client-key=/home/honggildong/honggildong.key
User "honggildong" set.

[honggildong@k8s-cp ~]$ kubectl config get-users
NAME
honggildong

# Kube Config 파일에 등록된 사용자 확인
[honggildong@k8s-cp ~]$ cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /etc/kubernetes/pki/ca.crt
    server: https://172.31.1.10:6443
  name: edu-k8s
contexts: null
current-context: ""
kind: Config
preferences: {}
users:
- name: honggildong
  user:
    client-certificate: /home/honggildong/honggildong.crt
    client-key: /home/honggildong/honggildong.key

# “honggildong” 사용자를 위한 Context 등록
[honggildong@k8s-cp ~]$ kubectl config get-contexts
CURRENT   NAME   CLUSTER   AUTHINFO   NAMESPACE

[honggildong@k8s-cp ~]$ kubectl config set-context honggildong-context --cluster=edu-k8s --user=honggildong
Context "honggildong-context" created.

[honggildong@k8s-cp ~]$ kubectl config get-contexts
CURRENT   NAME                  CLUSTER   AUTHINFO      NAMESPACE
          honggildong-context   edu-k8s   honggildong

# Kube Config 파일에 등록된 Context 확인
[honggildong@k8s-cp ~]$ cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /etc/kubernetes/pki/ca.crt
    server: https://172.31.1.10:6443
  name: edu-k8s
contexts:
- context:
    cluster: edu-k8s
    user: honggildong
  name: honggildong-context
current-context: ""
kind: Config
preferences: {}
users:
- name: honggildong
  user:
    client-certificate: /home/honggildong/honggildong.crt
    client-key: /home/honggildong/honggildong.key

# “honggildong” 사용자를 위한 Current Context 설정
[honggildong@k8s-cp ~]$ kubectl config get-contexts
CURRENT   NAME                  CLUSTER   AUTHINFO      NAMESPACE
          honggildong-context   edu-k8s   honggildong

[honggildong@k8s-cp ~]$ kubectl config use-context honggildong-context
Switched to context "honggildong-context".

[honggildong@k8s-cp ~]$ kubectl config get-contexts
CURRENT   NAME                  CLUSTER   AUTHINFO      NAMESPACE
*         honggildong-context   edu-k8s   honggildong

# Kube Config 파일에 등록된 Current Context 확인
[honggildong@k8s-cp ~]$ cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /etc/kubernetes/pki/ca.crt
    server: https://172.31.1.10:6443
  name: edu-k8s
contexts:
- context:
    cluster: edu-k8s
    user: honggildong
  name: honggildong-context
current-context: honggildong-context
kind: Config
preferences: {}
users:
- name: honggildong
  user:
    client-certificate: /home/honggildong/honggildong.crt
    client-key: /home/honggildong/honggildong.key

 

이제 honggildong 사용자는 Client Certificate 를 통해 API Server에 인증을 통과할 수 있는 상태입니다.

# “honggildong” 사용자로 pod 목록조회 명령을 실행하고 실패하는 원인을 파악
[honggildong@k8s-cp ~]$ kubectl get po --v=6
I0302 16:19:49.146930  113364 loader.go:395] Config loaded from file:  /home/honggildong/.kube/config
I0302 16:19:49.151587  113364 cert_rotation.go:137] Starting client certificate rotation controller
I0302 16:19:49.171599  113364 round_trippers.go:553] GET https://172.31.1.10:6443/api/v1/namespaces/default/pods?limit=500 403 Forbidden in 7 milliseconds
I0302 16:19:49.172043  113364 helpers.go:246] server response object: 
[{
    "kind": "Status",
    "apiVersion": "v1",
    "metadata": {},
    "status": "Failure",
    "message": "pods is forbidden: User \"honggildong\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
    "reason": "Forbidden",
    "details": {
      "kind": "pods"
    },
    "code": 403
}]
Error from server (Forbidden): pods is forbidden: User "honggildong" cannot list resource "pods" in API group "" in the namespace "default"

 

root 계정(클러스터 관리자)으로 “honggildong” 사용자에게 “Deployment, ReplicaSet, Pod” 리소스에 대한 RBAC 권한 부여하겠습니다.

[root@k8s-cp ~]# kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: honggildong
  name: honggildong-role
rules:
- apiGroups: ["", "extensions", "apps"]
  resources: ["deployments", "replicasets", "pods"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: honggildong
  name: honggildong-rolebinding
roleRef:
  kind: Role
  name: honggildong-role
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: User
  name: honggildong
EOF
role.rbac.authorization.k8s.io/honggildong-role created
rolebinding.rbac.authorization.k8s.io/honggildong-rolebinding created

# “honggildong” 사용자에게 부여된 RBAC 리소스 정보 확인
[root@k8s-cp ~]# kubectl -n honggildong describe rolebindings honggildong-rolebinding
Name:         honggildong-rolebinding
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  Role
  Name:  honggildong-role
Subjects:
  Kind  Name         Namespace
  ----  ----         ---------
  User  honggildong

[root@k8s-cp ~]# kubectl -n honggildong describe role honggildong-role
Name:         honggildong-role
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources               Non-Resource URLs  Resource Names  Verbs
  ---------               -----------------  --------------  -----
  deployments             []                 []              [get list watch create update patch delete]
  pods                    []                 []              [get list watch create update patch delete]
  replicasets             []                 []              [get list watch create update patch delete]
  deployments.apps        []                 []              [get list watch create update patch delete]
  pods.apps               []                 []              [get list watch create update patch delete]
  replicasets.apps        []                 []              [get list watch create update patch delete]
  deployments.extensions  []                 []              [get list watch create update patch delete]
  pods.extensions         []                 []              [get list watch create update patch delete]
  replicasets.extensions  []                 []              [get list watch create update patch delete]

 

이제 RBAC 설정을 마쳤으므로 “honggildong” 사용자 쉘에서 nginx Deployment 리소스 생성이 가능합니다.

[honggildong@k8s-cp ~]$ kubectl -n honggildong create deploy nginx --image=nginx
deployment.apps/nginx created

[honggildong@k8s-cp ~]$ kubectl -n honggildong  get all
NAME                         READY   STATUS    RESTARTS   AGE
pod/nginx-7854ff8877-cjd9v   1/1     Running   0          2m25s

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx   1/1     1            1           2m25s

NAME                               DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-7854ff8877   1         1         1       2m25s
Error from server (Forbidden): replicationcontrollers is forbidden: User "honggildong" cannot list resource "replicationcontrollers" in API group "" in the namespace "honggildong"
Error from server (Forbidden): services is forbidden: User "honggildong" cannot list resource "services" in API group "" in the namespace "honggildong"
Error from server (Forbidden): daemonsets.apps is forbidden: User "honggildong" cannot list resource "daemonsets" in API group "apps" in the namespace "honggildong"
Error from server (Forbidden): statefulsets.apps is forbidden: User "honggildong" cannot list resource "statefulsets" in API group "apps" in the namespace "honggildong"
Error from server (Forbidden): horizontalpodautoscalers.autoscaling is forbidden: User "honggildong" cannot list resource "horizontalpodautoscalers" in API group "autoscaling" in the namespace "honggildong"
Error from server (Forbidden): cronjobs.batch is forbidden: User "honggildong" cannot list resource "cronjobs" in API group "batch" in the namespace "honggildong"
Error from server (Forbidden): jobs.batch is forbidden: User "honggildong" cannot list resource "jobs" in API group "batch" in the namespace "honggildong“