시작하며

쿠버네티스 클러스터를 운영할 때 모든 사용자나 애플리케이션이 동일한 권한을 갖는 것은 보안상 위험하다. 쿠버네티스는 ServiceAccountRBAC(Role Based Access Control)을 통해 세밀한 권한 제어를 지원한다.

ServiceAccount와 RBAC

kubectl 명령어가 동작하는 과정

1) kubectl 명령어는 쿠버네티스 API 서버의 HTTP 핸들러에 요청을 전송한다.
2) API 서버는 해당 클라이언트가 쿠버네티스 사용자가 맞는지 인증(Authentication),
   해당 기능이 인가(Authorization)되어있는지 확인한다.
   -> kubectl 명령어는 config 파일 user 항목에 client-certification, keydata가 있는데
      이는 k8s에서 최고권한(cluster-admin)을 갖는다.
3) Admission Controller라는 별도의 과정을 거친 뒤 요청받은 기능을 수행한다.

ServiceAccount, Role, Cluster-Role

Role 생성 및 바인딩

# default 서비스어카운트가 디폴트로 존재
kubectl get sa
 
# 새로운 서비스어카운트 생성
kubectl create sa lsy1206
 
# 에러 발생. 아직 서비스 목록을 조회할 권한이 없음
kubectl get services --as system:serviceaccount:default:lsy1206

Role(네임스페이스에 속한 오브젝트들에 대한 권한을 정의)Cluster-Role(클러스터 단위의 권한을 정의)은 부여할 권한이 무엇인지를 나타내는 쿠버네티스 오브젝트이다.

# Role 생성
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
    namespace: default
    name: service-reader
rules:
  - apiGroups: [""]             # 1. 대상이 될 오브젝트 API 그룹
    resources: ["services"]     # 2. 대상이 될 오브젝트의 이름
    verbs: ["get", "list"]      # 3. 허용하고자 하는 동작
# RoleBinding 생성
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
    name: service-reader-rolebinding
    namespace: default
subjects:
- kind: ServiceAccount        # 권한을 부여할 대상이 ServiceAccount
  name: lsy1206               # 권한을 부여할 서비스어카운트 이름
  namespace: default 
roleRef:
    kind: Role                # Role에 정의된 권한을 부여한다.
    name: service-reader      # service-reader라는 이름의 Role을 대상(Subject)에 연결한다.
    apiGroup: rbac.authorization.k8s.io
kubectl apply -f {yaml-file-name}
 
# 권한을 부여받았으므로 정상 조회
kubectl get services --as system:serviceaccount:default:lsy1206

Role vs Cluster-Role

ClusterRole은 클러스터 단위의 리소스에 대한 권한을 정의하기 위해 사용한다. kind가 Role → ClusterRole로 바뀐 점을 제외하면 Role 정의부와 대부분이 동일하다.

# Cluster-Role 목록 확인
kubectl get clusterrole
kubectl apply -f {cluster-yaml-file-name}
kubectl describe clusterrole nodes-reader

Cluster-Role Aggregation

자주 사용되는 ClusterRole이 있다면 다른 ClusterRole에 포함시켜 재사용할 수 있다. 상속 구조로 작성하여, 부모의 권한을 자식 롤에서 물려받아 사용할 수 있다. aggregationRule.clusterRoleSelectors 옵션을 사용한다.

Kubernetes API 서버 접근

ServiceAccount Secret을 이용한 접근

ServiceAccount에 연결된 Secret에는 세 가지 데이터가 저장된다.

1) ca.crt: 쿠버네티스 공개 인증서
2) namespace: 서비스 어카운트가 존재하는 네임스페이스
3) token: 쿠버네티스 API 서버와의 JWT 인증에 사용 -> API 서버에 HTTP REST API 방식으로 접근할 때 신분을 증명

클러스터 내부 kubernetes Service를 이용한 접근

클러스터 내부에서 API 서버에 접근할 수 있는 서비스 리소스가 바로 kubernetes 서비스이다. 내부 포드는 kubernetes.default.svc라는 DNS 이름을 통해 쿠버네티스 API를 사용할 수 있다.

쿠버네티스는 자동으로 서비스 어카운트의 시크릿을 포드 내부에 마운트하기 때문에, 포드 내부에서 API 서버에 접근하기 위해 시크릿 데이터를 별도로 가져올 필요는 없다.

Kubernetes SDK를 이용한 포드 내부 접근

쿠버네티스 SDK(특정 언어로 바인딩된)를 사용하면 다음과 같이 동작한다. 포드를 생성하는 YAML에서 ServiceAccountName을 명시해두면, 시크릿은 포드 내부에 마운트되어 포드에서 해당 SDK로 쿠버네티스 API 서버에 접근할 수 있다.

인증 설정 요약

ServiceAccount에 secret 설정하기

imagePullSecrets 항목을 사용한다.

Kubeconfig에 인증정보 설정하기

kubeconfig 파일은 크게 세 가지로 구성된다.

  1. clusters: kubectl이 사용할 쿠버네티스 API 서버의 접속 정보 목록
  2. users: 쿠버네티스 API 서버에 접속하기 위한 사용자 인증정보 목록
  3. contexts: clusters 항목과 users 항목에 정의된 값을 조합해 최종적으로 사용할 쿠버네티스 클러스터의 정보를 설정

User와 Group

쿠버네티스에서는 유저(User)나 그룹(Group)이라는 오브젝트는 없지만, 서비스 어카운트는 개념상으로 유저의 한 종류이다. system:serviceaccount:<네임스페이스>:<서비스어카운트명>은 서비스 어카운트를 지칭하는 고유한 유저 이름이다. system:serviceaccount가 그룹 이름이며, system: 접두어는 쿠버네티스에 의해 미리 정의된 유저나 그룹에 사용된다.

x509 인증서를 통한 인증

쿠버네티스에서 기본 인증 방식은 x509라는 self-signed 루트 인증서를 사용한다. 이 인증서는 k8s를 설치할 때 자동으로 생성된다. 최상위 인증서는 ca.crt이며 ca.key가 이에 대응하는 비밀키 값이다.

Warning

x509 하위 인증서는 인증서가 유출되었을 때 파기하는 기능을 제공하지 않으므로, 실제 운영 환경에서는 Dex, Guard 등의 서드파티 솔루션으로 인증 정보를 관리하는 것이 더 효율적이다.

정리하며

쿠버네티스 보안의 핵심은 최소 권한 원칙이다. ServiceAccount를 세분화하고, Role/ClusterRole로 필요한 권한만 부여한 뒤, RoleBinding/ClusterRoleBinding으로 연결하는 RBAC 구조를 통해 클러스터 내 권한 관리를 체계적으로 할 수 있다. API 서버 접근 방식(Secret, kubeconfig, SDK)과 인증 방식(JWT, x509)을 이해하면 보다 안전한 클러스터 운영이 가능하다.