1. 쿠버네티스에서의 User 개념
쿠버네티스에서는 User(human)와 ServiceAccount(machine) 두 종류의 사용자가 존재합니다. User에는 클러스터 관리자 및 사용자(일반 개발자)가 있으며, ServiceAccount는 Prometheus, Jenkins 등의 Pod 단위로 동작하는 컨테이너들이 API를 사용하기 위해 권한을 부여받은 machine을 뜻합니다. 쿠버네티스에는 우리가 일반적으로 이해하고 있는 User Account과 매칭되는 개념의 객체가 존재하지 않습니다. 즉, 일반적인 방식인 API Call을 통해서는 클러스터에 '지금 사용자가입하니깐 DB 같은데 등록해놔라~'라고 등록할 수 없다는 말입니다.
대신, 클러스터의 인증 기관(CA)이 서명한 유효한 인증서를 제시하는 객체를 인증된 "User"로 간주합니다. 아래부터 사용하는 사용자는 이 문장에서 쓰인 맥락으로 통일하도록 하겠습니다. 이러한 방식에서 쿠버네티스는 인증서의 '제목'의 CommonName 필드에서 사용자 이름을 명시해둡니다(ex. /CN=Bob). CN으로 기록된 사용자는 역할 기반 액세스 제어(RBAC)를 통해 쿠버네티스 리소스(Pod, Secret, Deployment 등)에 대해 특정 작업(create, get, list, delete 등)을 수행할 권한이 있는지 여부를 결정합니다.
User와 달리 ServiceAccount(SA)는 쿠버네티스 API에 의해 관리되는데요, SA는 특정 namespace에 종속되고 api-server에 의해 자동으로 또는 관리자/개발자의 API Call을 통해 수동으로 생성됩니다. 또한, Secret API Object에 저장된 자격 증명(certificates) 세트에 연결되어 클러스터 내 프로세스가 쿠버네티스 API와 소통이 가능하게끔 Pod에 마운트됩니다.
API 요청은 User, SA, 또는 Anonymous로부터 받을 수 있습니다. kubectl 명령어를 타이핑하는 입력하는 인간 사용자, Node의 kubelet, Control-plane의 컴포넌트 등 클러스터 내부/외부의 모든 프로세스가 API 서버에 요청할 때마다 특정 권한을 받기 위한 인가(Authentication) 이전에 인증(Authorization)을 받거나 그렇지 못하여 Anonymous한 사용자로 남아 API 요청이 거부됩니다.
Client가 Server로 API 요청 Call을 날릴 때 적절한 Bearer Token을 header에 추가해주지 않으면 반환되는 status가 401 Unauthorizaed 에러입니다.
그렇다면 인증은 어떠한 방식으로 이뤄질까요? User 및 ServiceAccount로부터 받은 API는 아래 다섯 가지 방식으로 인증할 수 있습니다.
1. Service Account Token
2. 정적 Token
3. Password file
4. X.509 Client 인증서
5. OpenID Connect
위 가운데서 4번인 X.509 Client 인증서 방식은 비대칭 암호화 기술을 이용한 공개키 기반의 인증 표준으로서, 아래 사진처럼 Alice가 Bob에게 데이터를 전송할 때 Alice는 자신의 private key로 암호화를 하고 Bob은 Alice로부터 건네 받은 public key로 복호화를 하는 방식입니다. 오늘은 이 비대칭 암호화 기술을 중심으로 설명해보고자 합니다.
2. Authorization(인증) / Authentication(인가)
쿠버네티스 클러스터에서 인증을 얘기하기 전에 빼놓을 수 없는 개념이 CertificateSigningRequest(CSR)입니다. 인증서 발급을 위해 필요한 정보를 저장하고 있는 인증서로서, 신청 형식 데이터 공개 키와 인증서 적용되는 도메인에 대한 정보 등이 포함됩니다. 쿠버네티스에서는 CSR을 API Object로 관리할 수 있고, 이를 통해서 Cluster가 이용중인 Private Root CA으로부터 인증서를 발급받는 기능을 제공합니다. Kubernetes CSR 인증서를 발급받는 과정은 다음과 같습니다.
1. 인증서의 Private Key 생성
2. 생성한 인증서의 Private Key를 이용하여 csr 파일 생성
3. 생성한 CSR 파일의 내용을 base64로 Encoding한 문자열을 이용하여 Kubernetes의 CertificateSigningRequest Manifest 작성 및 인증서 발급 요청
4. 인증서 발급 요청 수락
(1) 쿠버네티스 관리자 입장의 CSR 발급
클러스터 내 인증 작업을 진행하려면 우선 CSR(CertificateSigningRequests)를 생성/승인/사인할 관리자 ClusterRole이 필요합니다. 아래 csr-creator, csr-approver, csr-signer로 명명한 세 ClusterRole의 .yaml 파일을 살펴보면 됩니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-creator
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- create
- get
- list
- watch
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-approver
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- get
- list
- watch
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/approval
verbs:
- update
- apiGroups:
- certificates.k8s.io
resources:
- signers
resourceNames:
- example.com/my-signer-name
# example.com/* can be used to authorize for all signers in the 'example.com' domain
verbs:
- approve
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-signer
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- get
- list
- watch
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/status
verbs:
- update
- apiGroups:
- certificates.k8s.io
resources:
- signers
resourceNames:
- example.com/my-signer-name
# example.com/* can be used to authorize for all signers in the 'example.com' domain
verbs:
- sign
(2) 쿠버네티스 사용자(개발자) 입장의 CSR 및 Role 생성
A. 개인 키(Private key) 생성
marcel 이라는 이름을 지닌 user의 .key와 .csr 파일을 만들어 줍니다. 이때 openssl genrsa 명령어 마지막 인자인 2048은 개인키의 사이즈이며, 반드시 명령어 마지막에 위치합니다. (default: 512)
openssl genrsa -out marcel.key 2048
openssl req -new -key marcel.key -out marcel.csr -subj "/CN=marcel"
B. CertificateSigningRequest (API 오브젝트) 생성
단계 A에서 생성한 .csr 파일을 base64 인코딩한 문자열을 이용하여 Manifest .yaml을 작성하고, 인증서 발급을 요청합니다. 그리고 kubectl apply 명령어를 통해 CSR API 오브젝트를 만들어 줍니다. 이때, spec.request에는 base64 인코딩 문자열을 넣어줍니다.
cat marcel.csr | base64 | tr -d "\n"
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: myuser
spec:
request: [CSR base64 Decoded String]
signerName: kubernetes.io/kube-apiserver-client
expirationSeconds: 86400 # one day
usages:
- client auth
EOF
C. CSR 승인 (인증서 발급 승인)
단계 B에서 만든 CSR를 조회하면, 아직 인증서가 승인되어 있지 않아 Pending 상태로 나타납니다. 인증서 발급을 승인해줍니다. (Pending -> Approved,Issued)
kubectl get csr
NAME AGE SIGNERNAME REQUESTOR REQUESTEDDURATION CONDITION
marcel 44s kubernetes.io/kube-apiserver-client kubernetes-admin <none> Pending
kubectl certificate approve marcel
certificatesigningrequest.certificates.k8s.io/marcel approved
kubectl get csr
NAME AGE SIGNERNAME REQUESTOR REQUESTEDDURATION CONDITION
marcel 108s kubernetes.io/kube-apiserver-client kubernetes-admin <none> Approved,Issued
D-1. crt 파일 및 Role / RoleBinding 생성
.kube/config 파일에 권한 자격을 가진 User를 만들기 위해서는 제일 처음에 생성한 marcel.key와 marcel.crt 파일이 필요합니다. 따라서 CSR API 오브젝트를 정의한 json에서 certificate를 base64 디코딩하여 저장하는 방식으로 marcel.crt 파일을 만들어줍니다.
kubectl get csr marcel -o jsonpath='{.status.certificate}'| base64 -d > marcel.crt
kubectl config set-credentials marcel \
--client-key=marcel.key \
--client-certificate=marcel.crt \
--embed-certs=true
User "app-manager" set.
위처럼 set-credentials 명령어로 자격 증명을 생성하면, .kube/config 파일에는 users 항목 아래 user인 marcel이 추가됩니다.
kubectl config view
...
users:
- name: marcel
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
- name: hk8s-admin
...
생성된 user(marcel)과 cluster로 context(kubernetes; 기본)를 설정해줍니다. (set-context)
kubectl config set-context app-manager --cluster=kubernetes --user=marcel
Context "marcel" created.
kubectl config view
...
contexts:
- context:
cluster: kubernetes
user: marcel
name: marcel
- context:
...
D-2. (Cluster)Role / RoleBinding 생성
D-1 단계에서 set-credentials, set-context로 .kube/config 파일을 수정하는 것과 병렬적으로, C 단계에서 만들어놓은 CSR API 오브젝트에 Role을 부여하는 RoleBinding 단계는 D-1 이전에 수행해도 무방합니다. 아래 예제는 ClusterRole과 ClusterRoleBinding을 만드는 작업인데요, Role / RoleBinding과의 차이는 namespace의 여부입니다. ClusterRole은 namespace에 상관 없이 전체 클러스터 내 리소스에 대한 접근 권한을 정의할 수 있습니다.
먼저 marcel-role이라는 이름의 ClusterRole을 만들어 봅시다.
kubectl create clusterrole marcel-role \
--verb=create,list,get,update,delete \
--resource=deployment,pod,service
clusterrole.rbac.authorization.k8s.io/marcel-role created
kubectl get clusterrole marcel-role
NAME CREATED AT
marcel-role 2022-07-11T05:01:47Z
kubectl describe clusterrole marcel-role
Name: marcel-role
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
pods [] [] [create list get update delete]
services [] [] [create list get update delete]
deployments.apps [] [] [create list get update delete]
이제, ClusterRole(marcel-role)을 CSR(API 오브젝트)에 부여하기 위한 ClusterRoleBinding(marcel-role-binding)을 생성하여 실습을 마무리합니다.
kubectl create clusterrolebinding marcel-role-binding \
--clusterrole=marcel-role \
--user=marcel
clusterrolebinding.rbac.authorization.k8s.io/marcel-role-binding created
kubectl get clusterrolebindings marcel-role-binding
NAME ROLE AGE
marcel-role-binding ClusterRole/app-access 28s
kubectl describe clusterrolebindings marcel-role-binding
Name: marcel-role-binding
Labels: <none>
Annotations: <none>
Role:
Kind: ClusterRole
Name: marcel-role
Subjects:
Kind Name Namespace
---- ---- ---------
User marcel
댓글