목차
참고
앞서 말했듯이, 쿠버네티스는 사용자 정보를 저장하지 않습니다. 사용자를 관리하고 싶으면, 쿠버네티스에서 제공하는 OIDC 를 통해 별도의 인증 서버와 연계를 하면 사용자를 관리할 수 있습니다.
인증서버로 유명한 Keycloak 을 이용해 사용자를 관리하고 쿠버네티스에서 인증을 진행할 수 있도록 설정을 진행하려고 합니다.
✅ 1. Keycloak Client 추가 쿠버네티스에서 Keycloak 을 이용해 인증을 진행하기 위해서는 Client 생성(등록) 이 필요합니다. Client 단위로 Keycloak 에서는 쿠버네티스 사용자들을 관리하고 권한을 매핑해줄 수 있습니다.
1-1. Client 생성 kubernetes-client
이름으로 새로운 client 를 생성합니다.
Client authentication 을 활성화 해줍니다.
현재는 OIDC 를 이용하기 위한 URL 이 없으므로 빈칸으로 넘어갑니다.
1-2. Role 생성 새로운 Role 을 추가해줍니다.
1-3. User Client Role 생성
1-4. User 추가 쿠버네티스 사용자 인증을 위해 test
로 계정을 하나 생성합니다.
계정을 활성화 하기 위해서는 password 를 세팅해야 합니다.
1-5. Group 생성
1-6. User 에 Group 매핑
✅ 2. Kubernetes API Server에 OIDC 옵션 추가 2-1. kube-apiserver 매니페스트 변경 쿠버네티스에서 요청을 받은 Kube API Server 가 Keycloak 을 통해 인증의 유효성을 확인할 수 있도록 kube-apiserver.yaml
파일에 설정을 추가해줘야 합니다.
kubeadm으로 설치했을 경우 /etc/kubernetes/manifests/kube-apiserver.yaml
에 파일이 존재합니다.
spec: containers: - command: - kube-apiserver - --oidc-issuer-url=https://<Keycloak-호스트>/auth/realms/<Realm-이름> - --oidc-client-id=kubernetes - --oidc-username-claim=preferred_username - --oidc-username-prefix=- - --oidc-groups-claim=groups - --oidc-groups-prefix="kubernetes:"
2-2. 쿠버네티스 접속을 위한 Keycloak 토큰 생성 테스트 위에서 생성한 Keycloak 에서 사용하기 위한 Client 와 User 정보를 이용해 Token 생성 요청이 정상적으로 이뤄지는지 확인합니다.
curl --location 'https://<Keycloak-호스트>/realms/<Realm-이름>/protocol/openid-connect/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ -d 'grant_type=password' \ -d 'client_id=kubernetes-client' \ -d 'username=test' \ -d 'password=<비밀번호>' \ -d 'scope=openid' \ -d 'client_secret=<secrete 정보>'
정상적인 응답이 왔을 경우 Keycloak 에서는 아래와 같은 토큰 정보를 반환해줍니다.
{ "access_token" : "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJzOEdsMWxzdUNLcFQ4R0RUcDVycF8waEsxN200TmJEOHFqcWp6dnFkMTFjIn0.eyJleHAiOjE3NDM4NjE0NDYsImlhdCI6MTc0Mzg2MTE0NiwianRpIjoiNzZkZTYwYWMtZGMxYy00YjM1LWFiODUtOWM0ZDNjMDc3M2VlIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmRkYW56aXRzLmNvbS9yZWFsbXMvZGRhbnppdCIsInN1YiI6IjI1MzJiNjYxLTA5ODUtNDI1YS1iYWU2LTNjMmQ4YmQ0ZGEyMyIsInR5cCI6IkJlYXJlciIsImF6cCI6Imt1YmVybmV0ZXMtY2xpZW50Iiwic2lkIjoiMzc5ZDYzZWMtNzUwOS00NzUyLWI3YTQtOTM1NDE4YTllOWNkIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIvKiJdLCJyZXNvdXJjZV9hY2Nlc3MiOnsia3ViZXJuZXRlcy1jbGllbnQiOnsicm9sZXMiOlsiYWRtaW4iXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJncm91cHMiOlsia3ViZXJuZXRlczphZG1pbiJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0In0.K8A_nTdNhP7HR4dR_hvuqGaWspnwnsQgJ5VPiC2UceO2l5tPi4Bxmh_TmeuIjcIOlv-d_CjgyZ4_kuKrobMpinReuAuFLD3IMuzqvXUMTyWkBv-tAxfcHqk4jceUSQEANdrywfzA7Xb2h38aSytm9TglZa7_P9mbNT34r3R_qnjwSxlnlPegjFfYvJFP107Tt5w6GIbIf3XhoRI_TYzDBSVTIvL_Y7lkvvoNB0woF0gBX914qW8_koD3NE0eNpXINhAWUB7LUycvBMYE-edgWj8menY3XwHrcNiO0FHCgTJ2nfA0KgJAspiWD725UnCW5v7Y7wD5rijNYZ38LiVkIQ" , "expires_in" : 300 , "refresh_expires_in" : 1800 , "refresh_token" : "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0OGE4YzYzMy0zODk3LTQ3ZjMtYTk3Ni05OTdhNmJkYjQ1MzcifQ.eyJleHAiOjE3NDM4NjI5NDYsImlhdCI6MTc0Mzg2MTE0NiwianRpIjoiZmQ1M2UzNjgtMjM4NC00N2FlLTlhNWYtODEyNjIyNzg0ZmE1IiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmRkYW56aXRzLmNvbS9yZWFsbXMvZGRhbnppdCIsImF1ZCI6Imh0dHBzOi8vYXV0aC5kZGFueml0cy5jb20vcmVhbG1zL2RkYW56aXQiLCJzdWIiOiIyNTMyYjY2MS0wOTg1LTQyNWEtYmFlNi0zYzJkOGJkNGRhMjMiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoia3ViZXJuZXRlcy1jbGllbnQiLCJzaWQiOiIzNzlkNjNlYy03NTA5LTQ3NTItYjdhNC05MzU0MThhOWU5Y2QiLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGJhc2ljIHJvbGVzIGFjciB3ZWItb3JpZ2lucyBlbWFpbCJ9.jCsS44jWe8xAUV1BNc4bFaamElEtg7pWHuW-m19azInpg-nziZvUrx1RwMYB_sR6pOWRvSOhoIrZXWLJOml7Vg" , "token_type" : "Bearer" , "id_token" : "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJzOEdsMWxzdUNLcFQ4R0RUcDVycF8waEsxN200TmJEOHFqcWp6dnFkMTFjIn0.eyJleHAiOjE3NDM4NjE0NDYsImlhdCI6MTc0Mzg2MTE0NiwianRpIjoiNWU0NTk4OTktOTk5NC00N2FlLWE2NjYtOWEwMzNkZmQ1MThkIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmRkYW56aXRzLmNvbS9yZWFsbXMvZGRhbnppdCIsImF1ZCI6Imt1YmVybmV0ZXMtY2xpZW50Iiwic3ViIjoiMjUzMmI2NjEtMDk4NS00MjVhLWJhZTYtM2MyZDhiZDRkYTIzIiwidHlwIjoiSUQiLCJhenAiOiJrdWJlcm5ldGVzLWNsaWVudCIsInNpZCI6IjM3OWQ2M2VjLTc1MDktNDc1Mi1iN2E0LTkzNTQxOGE5ZTljZCIsImF0X2hhc2giOiJ2N3VFMzY2RS1OSmpsa21QbFRJX0dnIiwiYWNyIjoiMSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZ3JvdXBzIjpbImt1YmVybmV0ZXM6YWRtaW4iXSwicHJlZmVycmVkX3VzZXJuYW1lIjoidGVzdCJ9.bAUI2zSw8WGfKtJagjMFMMpza2jHHbPwB5dOGlvC0bgqJANjuZTt8VU43yLps_1ksf3EydOjnnKXoQOPSDrJprdEb6AKud-wzs0KjeuwCWrJXnHEp1YxA9fM8-CTwcExoeFKSHQOIBFkoCAY2F8fUdfmdszqMUj13vm_95QHUwhN8jw12zh_KKS_KwEMODs9fLqTxJLgKHSti7uGQi8CI_NBFDcfC-FP1Q44fzJf7m9trD14NW06iu9IGL4d32xwtcjzdh9d4zM4Wc42bYOfA1wI0l5N-k6jmpYevilWvwACwxLmkHnVdjRlHSS6pb3lY8lyvKSwfMdQs_McMQcsIA" , "not-before-policy" : 0 , "session_state" : "379d63ec-7509-4752-b7a4-935418a9e9cd" , "scope" : "openid profile email" }
위 여러 토큰 중에서 쿠버네티스 API Server 에 요청시에는 id_token
값을 사용합니다. 해당 토큰에 어떤 정보가 있는지 디코딩 해보면 아래와 같은 값들을 갖고 있는 것을 확인할 수 있습니다.
2-3. 권한 부여 - 롤 생성 및 롤 바인딩 쿠버네티스는 인증 후 리소스를 사용하기 위해서는 권한이 필요합니다. 모든 Pod 에 대한 조회 권한을 주기 위해 ClusterRole
을 생성합니다. 만약, 특정 리소스에 대한 권한만을 주고 싶다고 하면 Role
리소스를 생성하면 됩니다.
kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: keycloak-role rules: - apiGroups: ["" ] resources: ["namespaces" , "pods" ] verbs: ["get" , "list" , "watch" ]
권한이 생성 됐으면 해당 권한을 사용하기 위한 롤 바인딩 리소스도 생성합니다. 위에서 ClusterRol
을 생성했으므로 ClusterRoleBinding
리소스를 통해 바인딩을 해야 합니다.
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: keycloak-crb-test roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: keycloak-role subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: test
2-4. 토큰을 이용해 쿠버네티스 API Server 요청 토큰 생성이 정상적으로 되는지 확인했고, 이번에는 생성한 토큰을 이용해 쿠버네티스 API Server 에 요청을 보내려고 합니다.
TOKEN=$(curl --location 'https://<Keycloak-호스트>/realms/<Realm-이름>/protocol/openid-connect/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ -d 'grant_type=password' \ -d 'client_id=kubernetes-client' \ -d 'username=test' \ -d 'password=<비밀번호>' \ -d 'scope=openid' \ -d 'client_secret=<secrete 정보>' | jq -r '.id_token' ) curl https://<쿠버네티스 api server 주소>/api/v1/namespaces/default --header "Authorization: Bearer ${TOKEN} " --insecure
토큰에 문제가 없을 경우 아래와 같은 응답이 오는 것을 확인할 수 있습니다.
{ "kind" : "Namespace" , "apiVersion" : "v1" , "metadata" : { "name" : "default" , "uid" : "1c78224d-d264-4c25-873c-4407be8d63e1" , "resourceVersion" : "36" , "creationTimestamp" : "2025-02-20T16:16:48Z" , "labels" : { "kubernetes.io/metadata.name" : "default" } , "managedFields" : [ { "manager" : "kube-apiserver" , "operation" : "Update" , "apiVersion" : "v1" , "time" : "2025-02-20T16:16:48Z" , "fieldsType" : "FieldsV1" , "fieldsV1" : { "f:metadata" : { "f:labels" : { "." : { } , "f:kubernetes.io/metadata.name" : { } } } } } ] } , "spec" : { "finalizers" : [ "kubernetes" ] } , "status" : { "phase" : "Active" } }
인증에 성공한 User 를 이용해 kubectl 명령어를 이용하기 위해 kubeconfig
파일에 토큰 정보를 반영해줍니다.
config apiVersion: v1 clusters: - cluster: certificate-authority-data: LS0tLS1CRUdJ ... DQVRFLS0tLS0K server: <kubernetes api server 주소> name: kubernetes contexts: - context: cluster: kubernetes user: test namespace: default name: kubernetes current-context: kubernetes kind: Config preferences: {}users: - name: test user: token: eyJhbGciO ... GfZXJw
2-5. 인증 및 인가 에러 인증이 정상적으로 이뤄지지 않은 경우에는 다음과 같은 401
에러가 발생하게 됩니다.
{ "kind" : "Status" , "apiVersion" : "v1" , "metadata" : { } , "status" : "Failure" , "message" : "Unauthorized" , "reason" : "Unauthorized" , "code" : 401 } %
권한이 제대로 안들어가 있을 경우 403
에러와 함께 리소스에 대한 접근을 할 수 없다는 오류 메시지를 확인할 수 있습니다.
{ "kind" : "Status" , "apiVersion" : "v1" , "metadata" : { } , "status" : "Failure" , "message" : "namespaces \"default\" is forbidden: User \"test\" cannot get resource \"namespaces\" in API group \"\" in the namespace \"default\"" , "reason" : "Forbidden" , "details" : { "name" : "default" , "kind" : "namespaces" } , "code" : 403 } %
✅ 3. kubelogin - OIDC 를 이용하기 위한 Client 사용 쿠버네티스에서 Keycloak 을 이용해 사용자 인증과정에 대한 설정이 끝났습니다. 하지만, 사용자가 쿠버네티스로의 인증 방식에 대해서는 현재 딱히 정해진게 없습니다.
위 과정에서 쿠버네티스 리소스를 이용하기 전에 토큰을 생성하고, 토큰을 config 파일에 반영하는 과정들이 있습니다.
매번 반복적으로 하기에는 번거로운 이 과정을 단순화 하기 위해 kubelogin 을 사용해 인증을 간편화하려고 합니다.
3-1. kubelogin 설치. kubelogin 공식문서 에서 운영체제 별로 설치하는 방법이 있습니다. 저는 맥환경에서 이용하므로 brew 를 이용한 방법만 정리했습니다.
3-2. kubelogin 설치. kubelogin 에서는 8000
번 포트를 이용해 응답을 대기 합니다. 인증에 성공 후 kubelogin 로 응답을 보내주기 위해 Valid redirect URIs
설정을 추가해줍니다.
3-3. kubeconfig 파일에 정보 추가 쿠버네티스에서 kubelogin 을 이용해 사용자 인증이 진행될 수 있도록 config 파일에 명령어와 옵션들을 추가합니다. 해당 정보들을 통해 kubelogin 에서 Keycloak 을 이용해 인증을 진행하고 인증정보들을 관리해줍니다.
users: - name: keycloak-user user: exec: apiVersion: client.authentication.k8s.io/v1beta1 command: kubectl args: - oidc-login - get-token - --oidc-issuer-url=https://<Keycloak-호스트>/auth/realms/<Realm-이름> - --oidc-client-id=kubernetes-client - --oidc-client-secret=<Keycloak Client Secret>
명령어를 이용해서도 config 파일에 설정정보를 추가할 수 있습니다.
kubectl config set-credentials keycloak-user \ --exec-api-version=client.authentication.k8s.io/v1beta1 \ --exec-command=kubectl \ --exec-arg=oidc-login \ --exec-arg=get-token \ --exec-arg=--oidc-issuer-url=https://<Keycloak-호스트>/auth/realms/<Realm-이름> \ --exec-arg=--oidc-client-id=kubernetes-client \ --exec-arg=--oidc-client-secret=<Keycloak Client Secret>
아래 처럼 User 정보를 명시적으로 지정해서 명령어를 사용할 수 있습니다.
kubectl --user=keycloak-user get nodes
kubectl 명령어를 사용하게 되면 kubelogin 가 Keycloak 을 통해 사용자 인증을 하도록 화면을 띄어줍니다.
인증이 정상적으로 이뤄지면 아래와 같이 성공했다는 메시지가 뜨고 위에서 실행한 결과가 정상적으로 실행됩니다.
3-4. 전체 kubeconfig 파일 이제 새로운 쿠버네티스 운영자가 왔을 경우 해당 config 파일을 건내주면 Keycloak 을 통해 사용자 인증을 받고 쿠버네티스 리소스에 접근할 수 있습니다.
apiVersion: v1 clusters: - cluster: certificate-authority-data: LS0tLS1CRUdJ ... DQVRFLS0tLS0K server: <kubernetes api server 주소> name: kubernetes contexts: - context: cluster: kubernetes user: keycloak-user namespace: default name: kubernetes current-context: kubernetes kind: Config preferences: {}users: - name: keycloak-user user: exec: apiVersion: client.authentication.k8s.io/v1beta1 command: kubectl args: - oidc-login - get-token - --oidc-issuer-url=https://<Keycloak-호스트>/auth/realms/<Realm-이름> - --oidc-client-id=kubernetes-client - --oidc-client-secret=<Keycloak Client Secret>