Authentication


쿠버네티스는 다양한 주체가 클러스터와 상호작용한다. 사람이 kubectl 명령어를 실행하거나 Pod 가 쿠버네티스 API 를 호출하여 kube-apiserver 를 거쳐 리소스를 생성, 조회, 수정, 삭제할 수 있다. 때문에 kube-apiserver 에서 모든 Authentication 이 이루어진다. 요청 주체를 크게 User 와 ServiceAccount 로 구분한다. 쿠버네티스는 내부적으로 User 리소스를 저장하지 않는다. 대신 Certificates 나 외부 Identity Service 인증 시스템과 연동하여 사용자를 확인한다. kubectl 명령어를 실행하는 User 는 보통 certificate 이나 IdP 인증 정보를 가진 kubeconfig 파일을 사용하여 kube-apiserver 에 인증한다. Pod 의 경우 지정된 ServiceAccount 의 토큰을 자동으로 마운트하여 인증한다.

TLS Certificates for Cluster Components

K8s 내부의 암호화된 통신을 위해 각 컴포넌트 마다 TLS Certificate 과 Private Key 가 필요하다.

  • CA
    • ca.crt
    • ca.key
  • Admin
    • admin.crt
    • admin.key
  • kube-scheduler
    • scheduler.crt
    • scheduler.key
  • kube-controller-manager
    • controller-manager.crt
    • controller-manager.key
  • kube-proxy
    • kube-proxy.crt
    • kube-proxy.key
  • kube-apiserver
    • apiserver.crt
    • apiserver.key
    • apiserver-etcd-client.crt
    • apiserver-etcd-client.key
    • apiserver-kubelet-client.crt
    • apiserver-kubelet-client.key
  • etcd-server
    • etcdserver.crt
    • etcdserver.key
  • kubelet-server
    • kubelet.crt
    • kubelet.key
# Key 생성
openssl genrsa -out ca.key 2048
 
# Certificate Signing Request 생성
openssl req -new -key ca.key -subj "/CN=KUBERNETES-CA" -out ca.csr
 
# Sign Certificate
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt

먼저 CA Certificate 을 발급받은 후,

# Key 생성
openssl genrsa -out admin.key 2048
 
# Certificate Signing Request 생성
openssl req -new -key admin.key -subj "/CN=kube-admin" -out admin.csr
 
# Admin Privilage 를 포함한 CSR 생성
openssl req -new -key admin.key -subj "/CN=kube-admin/O=system:masters" -out admin.csr
 
# Sign Certificate
openssl x509 -req -in admin.csr -CA ca.crt -CAkey ca.key -out admin.crt

Admin 을 위한 Certificate 을 발급받는다. 이후 모든 컴포넌트에 동일한 작업을 수행해주면된다.

openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout

Certificate 을 확인하기 위해서 위 명령어를 실행한 뒤 Issuer, Subject 등의 내용을 확인할 수 있다.

cat /etc/kubernetes/manifests/kube-apiserver.yaml

kube-apiserver 에 어떤 Certificate 이 사용됐는지 확인하려면 위 yaml 파일에서 --tls-cert-file 옵션을 확인해보자.

cat /etc/kubernetes/manifests/etcd.yaml

etcd 에 어떤 Certificate 이 사용됐는지 확인하려면 위 yaml 파일을 찾아보자.

Certificates API

openssl genrsa -out meatsby.key 2048
openssl req -new -key meatsby.key -subj "/CN=meatsby" -out meatsby.csr
cat meatsby.csr | base64 -w 0

CSR, CertificateSigningRequest

apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
  name: meatsby
spec:
  groups:
  - system:authenticated
  usages:
  - digital signature
  - key encipherment
  - server auth
  request:
    <certificate-goes-here>
kubectl get csr
kubectl certificate approve meatsby
kubectl get csr meatsby -o yaml
echo "<certificate>" | base64 --decode

K8s 는 Admin 의 Certificate 작업을 대신해주기 위한 API 를 제공한다. Controller Manager 가 Certificate 관련 작업을 모두 처리한다.

kubeconfig

# ~/.kube/config (기본 위치)
apiVersion: v1
kind: Config
 
current-context: my-kube-admin@my-kube-playground # 현재 사용 중인 접속 설정
 
clusters: # kube-apiserver 주소 및 인증서 정보
- name: my-kube-playground
  cluster:
    certificate-authority: ca.crt
    server: https:///my-kube-playground:6443
 
contexts: # 어느 사용자와 클러스터를 조합해서 사용할 지 정의
- name: my-kube-admin@my-kube-playground
  context:
    cluster: my-kube-playground
    user: my-kube-admin
    namespace: finance
 
users: # 인증서 또는 토큰으로 이루어진 사용자 정보
- name: my-kube-admin
  user:
    client-certificate: admin.crt
    client-key: admin.key
kubectl config view # 현재 사용 중인 kubeconfig 확인
kubectl config view --kubeconfig=my-custom-config
kubectl config use-context prod-user@production
kubectl config set-context $(kubectl config current-context) --namespace=dev

kubectl config 명령어를 통해 kubectl 작업 위치를 특정 namespace 로 이동시킬 수도 있다.

Service Accounts

default ServiceAccount

k get sa
NAME            SECRETS    AGE
default         1          1d
 
k describe sa default
Name:                       default
Namespace:                  default
...
 
k describe pod my-pod
Name:                       my-pod
Namespace:                  default
Priority:                   0
Service Account:            default
...

ServiceAccount 는 Pod 가 kube-apiserver 에 인증할 수 있도록 자격 증명을 제공하는 namespace 범위 리소스다. 기본적으로 각 namespace 마다 default ServiceAccount 가 자동으로 생성된다. Pod 가 이 ServiceAccount 를 사용하려면, ServiceAccount Token 이 필요하다. namespacePod 가 생성될 때 마다 생성된 ServiceAccount 와 Token 이 자동으로 볼륨에 마운팅된다.

ServiceAccount

kubectl create serviceaccount dashboard-sa
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dashboard-sa
  namespace: default
automountServiceAccountToken: false # Token 자동 마운트 여부 제어

kubectl 또는 YAML 파일로 작성하여 생성할 수 있다.

PodServiceAccount 연결하기

apiVersion: v1
kind: Pod
metadata:
  name: my-kubernetes-dashboard
spec:
  containers:
  - name: my-kubernetes-dashboard
    image: my-kubernetes-dashboard
  serviceAccountName: dashboard-sa    # Pod 가 사용할 ServiceAccount 지정
  automountServiceAccountToken: false # Token 자동 마운트 여부 제어

PodServiceAccount 를 사용하도록 연결하는 과정에서 ServiceAccount Token 이 Pod 내부 /var/run/secrets/kubernetes.io/serviceaccount/token 경로에 자동으로 마운트되어 kube-apiserver 와 통신할 수 있다. 별도로 지정하지 않는 경우, default ServiceAccount 가 자동 할당된다. 즉, 명시하지 않아도 기본 SA 를 사용하게 된다. 자동으로 ServiceAccount Token 이 자동으로 마운트하지 않도록 spec.automountServiceAccountToken 을 설정할 수 있다.

ServiceAccount Token

ServiceAccount Token 은 JWT 형식으로 발급된다. ServiceAccountTokenRequestAPI 로 부터 Token 을 받아오는 방식으로 작동한다. Token 안에는 ServiceAccount 이름, namespace, 발급 시각, 만료 시각 등의 정보가 담겨 있다. Pod 내부의 Application 이 이 Token 을 HTTP 요청의 Authorization 헤더에 담아 kube-apiserver 로 보내면, kube-apiserver 는 해당 Token 이 유효한지 검사하고, RBAC 규칙에 따라 권한을 부여한다. ServiceAccount Token 의 종류는 아래와 같다.

1. TokenRequest API 기반 (권장)
  • 짧게 쓰고 사라지는 임시 토큰 (기본 유효기간: 1시간)
  • 만료되면 자동으로 새 토큰 발급, 보안에 유리
  • Pod 삭제 시 토큰도 함께 소멸
  • 최신 쿠버네티스에서 기본값으로 사용
2. 수동 Secret 방식 (비권장)
  • kubernetes.io/service-account-token 타입의 Secret 으로 생성
  • 만료되지 않는 영구 토큰, 보안에 취약
  • 유출될 경우 kube-apiserver 에 무기한 접근 가능
3. 바인딩 토큰
  • 토큰 안에 특정 Pod, Node, Secret 정보가 들어있음
  • 해당 리소스에 묶여 있어서 다른 곳에서 사용하면 인증 실패
  • TokenReview API 로 유효성 확인 가능

Authorization


K8s 에 대한 작업을 위해 curl 이나 kubectl 로 요청을 보내면 kube-apiserver 에서 제공하는 API 에서 요청을 처리하게 된다. 이 때 K8s Object 에 대한 작업들을 API Group 으로 나뉘게 되는데, /apis 엔드포인트 기준으로 아래와 같은 API Group 이 존재한다.

  • /apps/v1/deployments
  • /apps/v1/replicasets
  • /apps/v1/statefulsets
  • /extensions
  • /networking.k8s.io
  • /storage.k8s.io
  • /authentication.k8s.io
  • /certificates.k8s.io

K8s 는 위와 같은 다양한 Resource 에 대한 작업의 권한을 위해 아래와 같은 방법을 사용한다.

  • Node Authorization
    • kubelet 의 경우 Certificate 에 명시된 system:node:node01 이라는 이름을 통해 Node Authorization 방식으로 kube-apiserver 와 통신할 수 있다.
  • ABAC, Attribute-Based Access Controls
    • 각 유저마다 Policy 를 적용해서 허용 가능한 요청들을 지정할 수 있다.
  • RBAC, Role-Based Access Controls
    • Policy 를 Role 로 생성해 유저들이 Role 을 기반으로 요청할 수 있도록 한다.
  • Webhook
ExecStart=/usr/local/bin/kube-apiserver \
  ...
  --authorization-mode=Node,RBAC,Webhook

kube-apiserver 를 실행할 때 --authorization-mode 옵션을 통해 적용할 Authorization Mechanism 을 지정해줄 수 있다. 위와 같이 설정해 줄 경우, Node Authorization 을 시도하고 실패하면 RBAC, 그리고 Webhook 순으로 권한을 확인한다.

RBAC, Role-Based Access Controls

Role

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["pods"]
  verbs: ["get", "update", "create"]
  resourceNames: ["blue", "orange"]
- apiGroups: [""]
  resources: ["ConfigMap"]
  verbs: ["create"]

Role 이 생성되는 namespace 내에서 무엇을 어떻게 할 수 있을지 정의한다. namespace 를 지정해주지 않을 경우 default namespace 로 설정된다.

RoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: devuser-developer-binding
subjects:
- kind: User
  name: dev-user # "name" is case sensitive
  apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
  name: dev-sa # "name" is case sensitive
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io

RoleBinding 을 통해 해당 namespaceRole 을 특정 사용자에 연결하여 RBAC 을 적용할 수 있다.

kubectl get roles
kubectl get rolebindings
kubectl describe role developer
kubectl describe rolebinding devuser-developer-binding

Role 과 RoleBinding 모두 K8s Object 이기 때문에 위 명령어들로 조회가 가능하다.

kubectl auth can-i create deployments
kubectl auth can-i delete nodes
 
kubectl auth can-i create deployments --as dev-user
kubectl auth can-i create pods --as dev-user
 
kubectl auth can-i create pods --as dev-user --namespace test

Role 이 어떤 작업을 수행할 수 있는지 확인해야할 경우 위 명령어들을 사용할 수 있다.

Cluster Roles and Role Bindings

# namespace 에 포함된 리소스 확인
kubectl api-resources --namespaced=true
 
# namespace 에 포함되지 않은 리소스 확인
kubectl api-resources --namespaced=false

pods, replicasets, deployment 등 namespace 에 포함되는 K8s Object 들과 다르게 nodes, PV, namespaces 와 같은 Cluster Scoped Object 들은 Cluster Role 을 통해 접근을 제한할 수 있다.

ClusterRole

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-administrator
rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["nodes"]
  verbs: ["get", "list", "delete", "create"]

클러스터 전역에서 무엇을 어떻게 할 수 있는지 정의한다.

ClusterRoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-admin-role-binding
subjects:
- kind: User
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-administrator
  apiGroup: rbac.authorization.k8s.io

클러스터 전역의 ClusterRoleClusterRoleBinding 을 통해 특정 사용자에 연결한다.

Image Security


docker login private-registry.io
docker run private-registry.io/apps/internal-app

Pod 에서 실행될 Container 의 Image 는 기본적으로 Docker Hub 에서 가져와 사용하게 된다. 만약 Private Registry 에서 Image 를 가져온다면 위와 같이 Docker 로 로그인해서 가져오게 된다.

kubectl create secret docker-registry regcred \
  --docker-server=private-registry.io \
  --docker-username=registry-user \
  --docker-password=registry-password \
  --docker-email=registry-user@org.com
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
  - name: nginx
    image: private-registry.io/apps/internal-app
  imagePullSecrets:
  - name: regcred

K8s 에서 Pod Definition File 로 Private Registry 를 이용하려면 Image 의 전체경로와 Private Registry 에 로그인하기 위한 Secret 를 연동해줘야 한다.

Security Contexts


Docker Security

Docker Container 는 기본적으로 호스트의 Process 로써 실행되고 Linux namespace 를 통해 격리된다.

docker run --user=1000 ubuntu sleep 3600

Docker Container 는 기본적으로 root user 로 실행되기 때문에 Container 를 실행할 때 --user 옵션을 통해 root user 권한을 제한할 수 있다.

docker run ubuntu

Docker Container 는 mac_admin, broadcast, net_admin, sys_admin 등 호스트에 영향을 줄 수 있는 Capability 들이 제한된다. 모든 Capability 는 /usr/include/linux/capability.h 에서 확인할 수 있다.

docker run --cap-add MAC_ADMIN ubuntu
docker run --cap-drop KILL ubuntu
docker run --privillege ubuntu

위 옵션들을 통해 Container 의 권한을 제어할 수 있다.

Kubernetes Security

apiVersion: v1
kind: Pod
metadata:
  name: web-pod
spec:
  securityContext:
    runAsUser: 1000
    capabilities:
	  add: ["MAC_ADMIN"]
  containers:
  - name: ubuntu
    image: ubuntu
    command: ["sleep", "3600"]
    securityContext:
      runAsUser: 1001
      capabilities:
        add: ["MAC_ADMIN"]

K8s Pod 에서도 마찬가지로 user 의 권한을 securityContext 를 통해 제어할 수 있다. Container 레벨에 선언된 securityContext 는 해당 Container 의 권한을 제어하고, Pod 레벨에 선언된 securityContext 는 Pod 내 모든 Container 의 권한을 제어한다.

kubectl exec ubuntu-sleeper -- whoami

위 명령어를 통해 Container User 를 확인할 수 있다.

References