Overview
오늘은 Kubernetes Service Account에 대해서 공부해보려고 한다.
먼저 간단하게 User Account와 Service Account에 대해 설명해보자면,
사용자 어카운트는 사람을 위한 것이다. 서비스 어카운트는 파드에서 실행되는 프로세스를 위한 것이다.
Service Account란?
Kubernetes Service Account는 쿠버네티스 클러스터 내에서 실행되는 팟(Pod)이 API 서버와 상호 작용할 수 있도록 권한을 부여하는 데 사용되는 자격증명이다. 서비스 어카운트는 특정 네임스페이스(namespace)에 속하며, 자동으로 생성되거나 사용자가 직접 생성할 수 있다. 그리고 네임스페이스 생성시 디폴트 서비스 어카운트가 생성된다.
쿠버네티스 1.24 이전까지는 서비스 어카운트를 처음 생성하면 자동으로 서비스 어카운트 토큰이 생성된다.
$ kubectl get serviceaccount -n somaz
NAME SECRETS AGE
default 1 27d
$ kubectl get secret -n somaz
NAME TYPE DATA AGE
default-token-7c6dm kubernetes.io/service-account-token 3 27d
쿠버네티스 1.24 이후에는 보안 강화로 인해 서비스 어카운트를 생성하여도 서비스 어카운트 토큰이 생성되지 않는다.
따라서 아래의 방법으로 서비스 어카운트 토큰까지 생성해야 한다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: servicenow-discovery
namespace: default
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: servicenow-discovery-token
namespace: default
annotations:
kubernetes.io/service-account.name: "servicenow-discovery"
---
apiVersion: v1
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
# "namespace" omitted since ClusterRoles are not namespaced
name: read-only
rules:
- apiGroups:
- apps
- extensions
- "*"
- ""
resources: ["*"]
verbs: ["get", "watch", "list"]
---
apiVersion: v1
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: discovery-global
subjects:
- kind: ServiceAccount
name: servicenow-discovery
namespace: default
- kind: User
name: servicenow-discovery
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: read-only
apiGroup: rbac.authorization.k8s.io
RBAC에 대한 내용은 아래의 게시물을 보면된다.
2023.04.19 - [Container Orchestration/Kubernetes] - Kubernetes API Server, Group / RBAC란?
Cluster Role Binding
Role Binding
Service Account 주요 요소
- ServiceAccount admission 컨트롤러
- ServiceAccount Token 컨트롤러
- ServiceAccount 컨트롤러
서비스 어카운트 주요 요소는 클러스터 내의 인증 및 권한 부여 프로세스에서 필수적인 역할을 한다.
ServiceAccount admission 컨트롤러
ServiceAccount admission 컨트롤러는 default Service account를 지정하지 않은 Pod에 할당하는 역할을 한다.
그리고 K8S-api-server에 포함된다.
새로운 파드가 생성되면 포드 사양에 ServiceAccount가 지정되어 있는지 확인한다. 그렇지 않은 경우 Pod가 생성되는 네임스페이스에 대한 기본 ServiceAccount를 자동으로 할당한다. 이렇게 하면 클러스터의 모든 Pod에 Kubernetes API 서버에 액세스할 때 인증에 사용할 수 있는 연결된 ServiceAccount가 존재한다.
ServiceAccount Token 컨트롤러
Token 컨트롤러는 클러스터의 각 ServiceAccount에 대한 토큰 생성 및 관리를 담당한다.
그리고 kube-controller-manager에 포함된다.
새로운 ServiceAccount가 생성되면 토큰 컨트롤러는 JWT 토큰, 네임스페이스 및 인증서를 포함하는 해당 암호를 자동으로 생성한다 ca.crt 토큰은 ServiceAccount를 대신하여 Kubernetes API 서버에 대한 요청을 인증하는 데 사용할 수 있다. 토큰 컨트롤러는 또한 회전 및 삭제를 포함하여 토큰의 수명 주기를 관리한다.
ServiceAccount 컨트롤러
ServiceAccount 컨트롤러는 ServiceAccount 및 관련 Secret 생성 및 삭제를 관리한다.
그리고 kube-controller-manager에 포함된다.
새로운 네임스페이스가 생성되면 ServiceAccount 컨트롤러는 토큰 컨트롤러에서 생성된 해당 암호와 함께 해당 네임스페이스에 대한 기본 ServiceAccount를 자동으로 생성한다. ServiceAccount가 삭제되면 ServiceAccount 컨트롤러는 연결된 암호도 클러스터에서 제거한다.
Service Account Secret 주요 요소
- Token
- ca.crt
- Namespace
서비스 어카운트가 생성되면 Kubernetes는 다음 세 가지 구성요소를 포함하는 보안 Secret을 자동으로 생성한다.
Token
토큰은 서비스 계정을 대신하여 Kubernetes API 서버에 대한 요청을 인증하는 데 사용할 수 있는 JWT(JSON 웹 토큰)이다. 이 토큰은 Kubernetes API 서버의 개인 키로 서명되며
해당 공개 키(ca.crt에 있는)를 사용하여 확인할 수 있다. 토큰에는 해당 이름 및 속한 네임스페이스와 같은 서비스 계정에 대한 정보가 포함된다.
ca.crt
ca.crt 파일에는 Kubernetes 클러스터에 대한 인증 기관(CA) 인증서가 포함되어 있다. 요청을 할 때 클라이언트와 API 서버 간의 신뢰를 설정하는 데 사용된다.
클라이언트는 이 CA 인증서를 사용하여 API 서버의 인증서가 유효하고 동일한 CA에서 서명했는지 확인할 수 있다. 이렇게 하면 클라이언트가 악의적인 행위자가 아닌 인증된 API 서버와 통신하고 있는지 확인할 수 있다.
Namespace
네임스페이스는 Kubernetes의 다중 테넌트 아키텍처의 핵심 구성 요소이다. 네임스페이스는 클러스터 내의 리소스를 논리적으로 분리하는 데 사용되므로 여러 팀이나 프로젝트가 서로 간섭하지 않고 동일한 클러스터를 공유할 수 있다.
서비스 계정이 생성되면 특정 네임스페이스와 연결된다. 서비스 계정에 ClusterRoleBindings을 통해 클러스터 전체 권한이 부여되지 않은 경우 RoleBindings를 통해 해당 네임스페이스의 권한을 부여 받은 서비스 계정의 토큰은 해당 네임스페이스 내의 리소스에 액세스 하는 용도로만 사용할 수 있다.
Service Account 실습
기존에 생성되어 있는 서비스 어카운트와 서비스 어카운트 토큰으로 진행해 보려고 한다.
$ k get serviceaccounts -n somaz
NAME SECRETS AGE
default 1 27d
$ k get secret -n somaz
NAME TYPE DATA AGE
default-token-7c6dm kubernetes.io/service-account-token 3 27d
아래와 같이 파드에 서비스 어카운트가 마운트 되어 있다.
k describe po -n somaz somaz-db-d7d785ff4-mwgfm
Mounts:
/etc/mysql/conf.d from config-volume (rw)
/var/lib/mysql from mysql-persistent-storage-dev2 (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-9dnzq (ro)
파드를 확인해보면 마운트 되어있는 장소에 token이 있다.
따라서 해당 token을 가지고 쿠버네티스 API 서버와 통신할 수 있다.
$ k exec -ti -n somaz somaz-db-d7d785ff4-mwgfm -- ls -l /var/run/secrets/kubernetes.io/serviceaccount/
total 0
lrwxrwxrwx 1 root root 13 Apr 12 10:00 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Apr 12 10:00 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Apr 12 10:00 token -> ..data/token
token 파일을 확인해보면 값이 들어가 있다.
k exec -ti -n somaz somaz-db-d7d785ff4-mwgfm -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOi...
그리고 Role을 생성 후 Rolebinding을 해준다.
간단하게 설명하자면, Role은 특정 namespace에서만 동작하는 역할이고
ClusterRole은 클러스터 전체에서 동작하는 역할이다.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: service-account-somaz-role
namespace: somaz
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: service-account-somaz-role-binding
namespace: somaz
subjects:
- kind: ServiceAccount
name: default
namespace: somaz
roleRef:
kind: Role
name: service-account-somaz-role
apiGroup: rbac.authorization.k8s.io
아래와 같이 통신을 확인해본다.
k exec -ti -n somaz somaz-db-d7d785ff4-mwgfm -- bash
bash-4.4# cd /var/run/secrets/kubernetes.io/serviceaccount/
bash-4.4# TOKEN=$(cat token)
bash-4.4# curl -X GET https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/somaz/pods --header "Authorization: Bearer $TOKEN" --insecure
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "79523984"
},
"items": [
{
"metadata": {
"name": "somaz-db-d7d785ff4-mwgfm",
"generateName": "somaz-db-d7d785ff4-",
"namespace": "somaz",
"uid": "2f44c054-937a-478c-b719-20298846f236",
...
해당 실습을 하면서 두가지 궁금증이 생겼다.
- $KUBERNETES_SERVICE_HOST 변수의 IP는 어떤의미일까?
- 파드의 토큰 값과 클러스터의 서비스 어카운트 토큰 값이 다른데 어떻게 통신이 되는걸까?
$KUBERNETES_SERVICE_HOST 변수의 IP는 어떤의미일까?
바로 쿠버네티스 API 서버의 IP 주소를 나타낸다.
Pod 환경에 자동으로 주입되며 실행중인 Pod가 API 서버와 통신할 수 있게 해준다.
echo $KUBERNETES_SERVICE_HOST
10.233.0.1
k get service -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 372d
파드의 토큰 값과 클러스터의 서비스 어카운트 토큰 값이 다른데 어떻게 통신이 되는걸까?
먼저 파드의 토큰 값을 확인해본다.
k exec -ti -n somaz somaz-db-d7d785ff4-mwgfm -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZC...
그리고 클러스터의 서비스 어카운트 토큰 값을 확인한다.
$ k get secrets -n somaz
NAME TYPE DATA AGE
default-token-7c6dm kubernetes.io/service-account-token 3 27d
$ k get secrets -n somaz default-token-7c6dm -o yaml | k neat
apiVersion: v1
data:
ca.crt:...
namespace: c29tYXoK # echo "somaz" | base64 -> c29tYXoK
token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNkltVlNSalpvYnpaR09UZER...
분명히 두개의 토큰 값은 다르다. 그러면 어떻게 통신하는 걸까?
이유는 다음과 같다.
실제로 두 토큰이 같아야 할 것처럼 보이지만, 쿠버네티스는 보안상의 이유로 서비스 계정 토큰을 자동으로 회전(rotate)한다. 서비스 계정 토큰이 탈취당하는 경우나 누출되는 경우를 대비하여 보안을 강화하기 위한 조치이다.
서비스 계정 토큰의 회전 주기는 쿠버네티스 클러스터 설정에 따라 다르다. 따라서, 생성 시점에 따라 파드의 토큰과 kubectl get secret 명령어로 조회하는 토큰이 다를 수 있다. 이것이 두 토큰이 다른 이유이다.
Reference
https://kingofbackend.tistory.com/237
https://kubernetes.io/ko/docs/reference/access-authn-authz/service-accounts-admin/
https://www.bogotobogo.com/DevOps/Docker/Docker-Kubernetes-Service-Account.php
https://ssup2.github.io/theory_analysis/Kubernetes_Authentication_Service_Account/
'Container Orchestration > Kubernetes' 카테고리의 다른 글
Helm Chart 작성방법 (0) | 2023.05.18 |
---|---|
Helm 이란? (Kubernetes Package manager) (2) | 2023.05.16 |
Kubernetes Secret이란? (0) | 2023.05.09 |
MetalLB란? (2) | 2023.05.03 |
Kubernetes Resources(쿠버네티스 리소스) (0) | 2023.05.02 |