Authentication


개발자나 관리자가 K8s Cluster 와 소통하기 위해선 항상 kube-apiserver 를 거치게된다. 때문에 kube-apiserver 에서 모든 Authentication 이 이루어진다. kube-apiserver 는 요청을 Authenticate 하기 위해 Static Password File, Static Token File, Certificates, Identity Service 등을 활용한다.

# user-details.csv
password123,user1,u0001,group1
password123,user2,u0002,group1
password123,user3,u0003,group1

위처럼 CSV 형식의 Static Password File 을 생성하고,

ExecStart=/usr/local/bin/kube-apiserver \
  ...
  --basic-auth-file=user-details.csv

kube-apiserver 를 실행할 때 지정해주거나,

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - ...
    - --basic-auth-file=user-details.csv

kubeadm 을 사용했을 경우 yaml 파일에 옵션을 추가해주면된다.

curl -v -k https://master-node-ip:6443/api/v1/pods -u "user1:password123"

이후 요청을 보낼 때 아이디와 비밀번호를 위와 같은 방식으로 전달해주면된다.

# user-token-details.csv
KpjCq12wfdfqgwdfw,user4,u0004,group1
rJjncqwe1dgi2dow0,user5,u0005,group1
mjpOFiEq1dngi3dkf,user6,u0006,group1

Static Token File 을 사용할 경우엔

--token-auth-file=user-token-details.csv

옵션을 대신 사용하면 된다.

curl -v -k https://master-node-ip:6443/api/v1/pods --header "Authorization: Bearer KpjCq12wfdfqgwdfw"

이후 요청 시 토큰을 헤더에 포함하여 전달해줄 수 있다.

위 방법은 아주 기초적인 방법으로 v1.19 에 Deprecated 됐다.

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
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

curl https://my-kube-playground:6443/api/v1/pods \
  --key admin.key
  --cert admin.crt
  --cacert ca.crt
kubectl get pods
  --server my-kube-playground:6443
  --client-key admin.key
  --client-certificate admin.crt
  --certificate-authority ca.crt
kubectl get pods --kubeconfig config

curl 또는 kubectl 로 kube-apiserver 와 통신할 때 사실 위와 같이 인증 관련 옵션들을 지정해줘야 하는데 이를 간편하게 KubeConfig 로 설정해줄 수 있다. $HOME/.kube/config 경로를 기본값으로 사용하기 때문에 여태껏 제외하고 사용 가능했던 것이다.

apiVersion: v1
kind: Config
 
current-context: my-kube-admin@my-kube-playground
 
clusters:
- 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=my-custom-config
kubectl config use-context prod-user@production

Authorization


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

  • /apps
    • /v1
      • /deployments
      • /replicasets
      • /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 와 통신할 수 있다.
  • Attribute-Based Access Controls (ABAC)
    • 각 유저마다 Policy 를 적용해서 허용 가능한 요청들을 지정할 수 있다.
  • Role-Based Access Controls (RBAC)
    • 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 순으로 권한을 확인한다.

Role-Based Access Controls

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"]
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
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io

Role 과 RoleBinding 을 통해 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
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"]
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

Role 은 Namespace 를 지정해주지 않을 경우 default namespace 로 설정된다. pods, replicasets, deployment 등 namespace 에 포함되는 K8s Object 들과 다르게 nodes, PV, namespaces 와 같은 Cluster Scoped Object 들은 Cluster Role 을 통해 접근을 제한할 수 있다.

Service Accounts


K8s Account 는 User Account 와 Service Account 로 나뉜다. User Account 는 Admin, Developer 등이 사용하고 Service Account 는 Prometheus, Jenkins 등의 Application 이 사용한다. Application 이 kube-apiserver 와 통신하기 위해 Authentication 을 거쳐야 하는데 이를 Service Account 를 통해 이뤄낼 수 있다.

kubectl create serviceaccount dashboard-sa
kubectl get serviceaccount
kubectl describe serviceaccount dashboard-sa
kubectl describe secret dashboard-sa-token-kbbdm

Service Account 를 생성하면 인증을 위해 사용되는 Token 역시 생성된다. Token 의 경우 Secret Object 를 통해 관리된다. 해당 Token 을 활용해 Application 을 인증 받을 수 있다.

만약 Application 을 Pod 의 형태로 K8s Cluster 내에서 호스팅한다면 Token 을 수동으로 제공하는 대신 볼륨을 마운팅하여 간단하게 제공할 수 있다. K8s 는 기본적으로 각 namespace 마다 Service Account 를 자동으로 생성한다. 해당 namespace 에 Pod 가 생성될 때 마다 생성된 Service Account 와 Token 이 자동으로 볼륨에 마운팅된다.

kubectl create serviceaccount dashboard-sa
kubectl create token dashbaord-sa
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: mysecretname
  annotations:
    kubernetes.io/service-account.name: dashboard-sa

v1.22 부터 위 내용이 변경되었는데, Service Account 가 TokenRequestAPI 로 부터 Token 을 받아오는 형식으로 작동한다. v1.24 엔 Service Account 를 생성하면 Token 을 직접 생성해서 설정해주어야한다.

kubectl describe pod my-kubernetes-dashboard
kubectl exec -it my-kubernetes-dashboard -- ls /var/run/secrets/kubernetes.io/serviceaccount
kubectl exec -it my-kubernetes-dashboard cat /var/run/secrets/kubernetes.io/serviceaccount/token

Pod 를 생성한 뒤 describe 를 통해 확인해보면 volumes.default-token-xxxxx 이 생성되어 있는 것을 확인할 수 있다.

apiVersion: v1
kind: Pod
metadata:
  name: my-kubernetes-dashboard
spec:
  containers:
  - name: my-kubernetes-dashboard
    image: my-kubernetes-dashboard
  serviceAccountName: dashboard-sa
  automountServiceAccountToken: false

Pod 에 Service Account 를 지정해주기 위해 spec.serviceAccountName 을 사용할 수 있고 자동으로 Service Account 가 배정되는 것을 막기 위해 spec.automountServiceAccountToken 을 사용할 수 있다.

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 를 확인할 수 있다.

Network Policy


apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-policy
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: api-pod
    - namespaceSelector:
        matchLabels:
          name: prod
    - ipBlock:
        cidr: 192.168.5.10/32
	ports:
    - protocol: TCP
      port: 3306
  egress:
  - to:
    - ipBlock:
        cidr: 192.168.5.10/32
    ports:
    - protocol: TCP
      port: 80

K8s Cluster 는 기본적으로 속한 모든 Pod 간의 통신을 허용한다. 만약 Ingress 와 Egress 를 제한하고 싶다면 NetworkPolicy 를 생성하자. 위 yaml 예시는 role=db 레이블을 가진 Pod 는 같은 네임스페이스의 role=api-pod 레이블을 가진 Pod 또는 prod 네임스페이스의 모든 Pod 또는 IP 192.168.5.10 로부터 TCP 3306 포트로만 접근을 허용하는 Ingress Rule 을 가지고 있고, role=db 레이블을 가진 Pod 는 192.168.5.10:80 (TCP) 로만의 outbound 통신을 허용하는 Egress Rule 을 가지고 있다.

Custom Resource Definition


apiVersion: flights.com/v1
kind: FlightTicket
metadata:
  name: my-flight-ticket
spec:
  from: Mumbai
  to: London
  number: 2

K8s 에선 ReplicaSet, Deployment 등 기본적으로 제공되는 Resource 외에 사용자가 커스텀으로 Resource 를 만들 수 있다. 위와 같은 Resource 를 만들고 싶을 때 Custom Resource Definition 을 생성해 Object 를 생성하고 관리할 수 있다.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: flighttickets.flights.com
spec:
  scope: Namespaced
  group: flights.com
  names:
    kind: FlightTicket
    singular: flightticket
    plural: flighttickets
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              from:
                type: string
              to:
                type: string
              number:
                type: integer
                minimum: 1
                maximum: 10

위와 같이 Custom Resource Definition 을 생성하면 정의된 Custom Resource 를 생성하고 관리할 수 있다. 다만 ETCD 에 저장만 될 뿐 어떤 기능이 있는 것은 아니다. 기능을 추가하기 위해 Custom Controller 를 작성할 수 있다.

Custom Controllers


Custom Resource 를 제어하려면 Custom Controller 를 작성해야 하는데, 파이썬 등 다양한 언어로 작성할 수 있지만 API 콜을 기반으로 통신하기 때문에 자체적으로 큐 및 캐싱 시스템을 포함해야 하는 수고가 있을 수 있다. 때문에 K8s Go 클라이언트로 Custom Controller 를 작성하면 좀 더 쉽게 Controller 를 구축할 수 있다.

Operator Framework


Operator Framework 는 CustomResourceDefinition 과 CustomController 를 패키지화하여 K8s 에 배포할 수 있도록 도와준다.

References