k8s security

yeongjin·2026년 1월 12일

계층별 보안 방법

  • 인프라 보안 : kubernetes가 돌아가고 있는 물리/가상 서버의 보안
  • kube-apiserver 접근 제어 : kubernetes의 모든 작업은 kube-apiserver를 통하기에 이를 보호하는 것은 중요. kube-apiserver에서 인증/인가 처리를 함.
  • kubernetes 모듈 간 통신 보안 : 클러스터 내부 구성요소간의 모든 통신은 tls 인증서를 통해 암호화됨
  • 애플리케이션 보안 : 기본적으로 모든 pod들은 서로 통신 가능하지만 네트워크 정책을 사용해 특정 pod간의 통신만 허용할 수 있음

사용자의 종류

kubernetes에 접근하는 대상은 크게 두 가지로 분류됨

  • 사람 : 관리자, 개발자 등 kubernetes 내부에서 직접 관리하지 않고 외부 소스에 의존함. kubectl get users로 조회 못함
  • service account : 프로세스나 애플리케이션 등. kubernetes가 직접 api로 생성하고 관리할 수 있음

kube-apiserver 인증 방법

  • 정적 파일 인증 : 권장되지 않음
  • 인증서 기반 인증 : 사용자가 tls인증서를 가지고 신원을 확인하는 방식. kube/config를 이용한 접속도 이 방식에 속함
  • 외부 인증 서비스

암호화의 2가지 방식

  • 대칭키 : 암호화/복호화 시 똑같은 키를 사용
  • 비대칭키: 암호화/복호화 시 개인키와 공개키 한쌍을 사용

웹 통신에서 TLS 작동 방식

  1. 서버는 사용자에게 자신의 공개키가 담긴 인증서 보냄
  2. 브라우저는 통신에 사용할 대칭키를 생성해 이를 서버의 공개키로 암호화함
  3. 서버는 자신의 개인키로 이를 복호화해 대칭키를 얻음
  4. 양측이 공유한 대칭키로 데이터를 안전하게 주고 받음

인증 기관 (ca)

누가 가짜 인증서를 주장할 수 있으므로 제3자의 인증이 필요하다. ca가 이 역할을 함.

  1. 서버 주인이 ca에 내 인증서에 서명해달라 요청
  2. ca는 신원을 확인한 후 자신의 개인키로 인증서에 서명
  3. 브라우저의 검증: 브라우저들은 이미 ca들의 공개키를 내장하고 있으므로 이를통해 서버가 보낸 인증서가 진짜인지 확인

인증서의 종류

  • 서버 인증서 : 서버가 자신의 신원을 증명하기 위해 사용 (HTTPS 서비스 제공용)
  • 클라이언트 인증서 : 서버에 접속하는 클라이언트가 자신을 인증하기 위해 사용
  • 루트 인증서 : ca가 가지고 있으며, 위의 서버/클라이언트 인증서들을 신뢰할 수 있게 서명해 주는 용도

Kubernetes 구성요소별 인증서

서버 역할

  • kube api server
  • etcd server
  • kubelet

클라이언트 역할

  • admin user
  • kube scheduler
  • kube controller manager
  • kube proxy

어떤 요소는 서버이면서 클라이언트 역할을 하기도 함.

  • api server는 etcd, kubelet에 접속할때 클라이언트 인증서를 사용함

이 모든 인증서가 진짜임을 보증하기 위해서는 클러스터에는 최소 하나 이상의 ca의 인증서가 필요함.


CA 인증서 생성

  1. ca 개인키 생성
  2. csr(서명 요청서) 생성
  3. 본인의 키로 직접 서명해 ca.crt 생성

클라이언트 인증서 생성

admin user

  • cn은 admin, group은 system.masters로 지정해야함 (관리자 권한을 받기 위한 약속된 그룹 이름)

시스템 컴포넌트 (schedular, controller manager, kube-proxy

  • 이름 앞에 system: 접두사 사용해야함

만들어진 인증서는 kubeconfig 또는 REST API 호출시 헤더에 포함

서버 인증서 생성

etcd 서버

  • HA 환경이라면 노드 간 통신을 위해 peer 인증서 필요

kube api 서버

  • API 서버는 다양한 이름으로 불리기 때문에 SAN(Subject Alternative Names)설정이 필수

kubelet 서버

  • 각 노드마다 별도의 인증서 필요

kubeadm 환경 인증서 확인

  1. /etc/kubernetes/manifests/ 폴더 안의 kube-apiserver.yaml 파일을 열어보면 인증서 파일의 경로를 알 수 있음.
  2. 파일 경로를 알아냈다면 openssl을 사용해 실제 내용 해독

Certificates API

kubernetes에서 인증서를 더 효율적이고 자동화된 방식으로 관리하기 위해 Certificates API를 사용

기존 방식 (수동 관리)

  1. 사용자가 개인키와 csr 생성 후 관리자에게 전달
  2. 관리자가 ca서버(주로 마스터 노드)에 직접 로그인해 ca의 개인키와 인증서로 수동 서명
  3. 생성된 인증서를 다시 사용자에게 전달

Certificates API

  1. 사용자가 개인키와 csr 생성 후 관리자에게 전달
  2. 관리자는 사용자의 csr 내용을 담은 CertificateSigningRequest 쿠버네티스 객체를 생성
  3. 관리자가 명령어를 통해 승인 kubectl certificate approve

Certificates API 동작을 담당하는 주체는 kube-controller-manager


Kubeconfig

원래 kube api server에 요청을 보낼 때는 여러가지 긴 옵션이 필요함. (서버주소, 사용자 개인키, 사용자 인증서, ca인증서) 이 정보를 하나의 파일에 정리해둔 것이 바로 kubeconfig. kubectl은 기본적으로 ~/.kube/config 경로에 있는 파일을 읽어 사용

명령어

  • kubectl config view : 현재 사용 중인 전체 설정 출력
  • kubectl config use-context <컨텍스트 이름> : 컨텍스트 변경

API Group

쿠버네티스에 kubectl이나 rest api요청을 날릴때 사실은 특정 url 경로로 요청을 보내는 것임.

api group

  • core group (/api/v1) : 가장 기본이 되는 리소스들 (pod, node, namespace, service, configMap, secret)
  • named group (/apis/그룹명/버전) : 최신 기능과 특정 목적을 가진 리소스들이 조직적으로 분류된 그룹

Kubectl Proxy

  • 인증서 없이 api 서버에 직접 curl을 날리면 거부됨. 이때 사용하는 것이 kubectl proxy
  • kubectl proxy를 로컬에 띄어 사용자의 kubeconfig 인증 정보를 자동으로 사용하여 api 서버와 통신하게 해줌

Kubernetes의 인가 방식

  • node authorizer : kubelet의 요청을 처리하기 위한 전용 노드 (내부 통신용)
  • ABAC : 사용자나 그룹을 속성(json 파일)과 직접 연결, 설정 변경시 api 서버 재시작 필요
  • RBAC : 역할을 먼저 정의하고 거기에 사용자를 할당 (k8s 표준)
  • WebHook : 외부시스템에 인가 여부를 물어보고 결정

RBAC

Role은 어떤 자원에 대해 어떤 행위를 할 수 있는가를 정의하는 객체

Role의 구성 요소

  • apiGroups : 리소스가 속한 api 그룹
  • resources : 대상 리소스 ( ex : pod, configmap 등등)
  • verbs : 허용할 작업 ( ex : get, list, create )

role은 특정 namespace 내에서만 유효

이렇게 만들어진 role을 user에 매핑

ClusterRole

  • Role과 비슷하지만, 특정 namespace가 아닌 cluster 전체 리소스에 대한 권한을 정의할 때 사용
  • ClusterRoleBinding 리소스를 이용하여 ClusterRole을 특정 user, group 또는 service account와 연결

Service Account

  • 사람이 아닌 애플리케이션, 모니터링 도구, 빌드 도구 등 머신이 사용하는 계정
  • service account는 api 호출 시에 토큰을 포함하여 요청을 보내 인증함
  • 모든 네임스페이스에는 기본으로 default라는 이름의 service account가 생성됨

컨테이너 이미지 보안

이미지 명명 규칙

  • registry 주소(생략시 docker.io) / 계정명(생략시 library) / 이미지명
    • ex: docker.io/library/nginx

비공개 레지스트리에서 이미지 가져오기

kubectl create secret docker-registry <시크릿-이름> \
  --docker-server=<레지스트리-주소> \
  --docker-username=<사용자명> \
  --docker-password=<비밀번호> \
  --docker-email=<이메일>
apiVersion: v1
kind: Pod
metadata:
  name: my-private-pod
spec:
  containers:
    - name: nginx
      image: my-private-repo.io/apps/internal-nginx:v1
  imagePullSecrets:
    - name: <시크릿-이름>  # 위에서 생성한 Secret 이름

Docker 보안

컨테이너 내의 프로세스는 호스트 os에서 실행되지만 linux namespace 기술을 통해 격리됨.

  • 컨테이너 관점 : 자신이 독자적인 시스템인 것처럼 보이며, pid 1번을 가짐. 다른 컨테이너나 호스트의 프로세스를 볼 수 없음
  • 호스트 관점 : 컨테이너 내 프로세스는 호스트에서 실행되는 수많은 프로세스 중 하나일 뿐이며, 호스트 기준의 다른 pid를 부여받음

기본적으로 도커는 컨테이너 안에서 프로세스를 root 사용자로 실행하는데, 실행 시나 빌드 시 설정으로 이를 다른 사용자로 변경할 수 있음

컨테이너 안의 root가 호스트os의 root와 똑같다면 매우 위험함. 이를 방지하기 위해 도커는 linux capabilities를 사용하여 사용자의 권한을 제한함

Security Context

pod나 container의 액세스 제어 설정을 의미함.

실행 사용자 설정

apiVersion: v1
kind: Pod
metadata:
  name: multi-container-pod
spec:
  securityContext:        # 포드 수준 설정 (모든 컨테이너에 적용)
    runAsUser: 1000
  containers:
    - name: web-container
      image: nginx
    - name: sidecar-container
      image: ubuntu
      securityContext:    # 컨테이너 수준 설정 (이 컨테이너만 2000으로 실행)
        runAsUser: 2000

리눅스 capabilities 설정

spec:
  containers:
    - name: ubuntu
      image: ubuntu
      command: ["sleep", "3600"]
      securityContext:
        capabilities:
          add: ["NET_ADMIN", "SYS_TIME"] # 네트워크 관리 및 시스템 시간 변경 권한 추가

Network Policy

kubernetes는 기본적으로 클러스터 내 모든 pod가 서로 통신할 수 있도록 트래픽이 허용되어 있음. 하지만 보안상 특정 pod가 특정 대상으로부터만 트래픽을 받도록 제한하고 싶을 때 NetworkPolicy를 사용

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-policy
spec:
  podSelector:        # 정책을 적용할 대상 포드 선택
    matchLabels:
      role: db
  policyTypes:        # 적용할 트래픽 유형 (Ingress, Egress 중 선택)
    - Ingress
  ingress:            # 허용할 Ingress 규칙 정의
    - from:           # 트래픽을 보낼 수 있는 대상 (Egress일경우 to 사용)
        - podSelector:
            matchLabels:
              name: api-pod
      ports:          # 허용할 포트
        - protocol: TCP
          port: 3306

기본 원칙

  • 트래픽이 시작되는 방향만 고려. stateful함.
  • 특정 pod에 policy를 연결하는 순산, 해당 pod는 허용 규칙에 없는 모든 트래픽을 차단

selector의 종류

  • podSelector : pod 지정
  • namespaceSelector : namespace 지정
  • ipBlock : ip 대역 지정

and 조건

- from:
    - namespaceSelector:
        matchLabels: {user: prod}
      podSelector:
        matchLabels: {role: api}

or 조건

- from:
    - namespaceSelector:
        matchLabels: {user: prod}
    - podSelector:
        matchLabels: {role: api}

CRD (Custom Resource Definition)

쿠버네티스의 모든 기본 기능은 두 가지 요소로 작동함

  • Resource : etcd에 저장된 선언적 데이터 (pod 3개 만들거야)
  • Controller : etcd를 지속적으로 감시하며 실제 상태를 resource에 기술된 상태와 일치시키는 백그라운드 프로세스 (pod 3개를 실제로 생성함)

기본 리소스 외에 내가 직접 정의한 리소스를 사용하고 싶을때 CRD를 사용. 이를 통해 만들어진 것을 CR(Custom Resource)라고 함

Custom Controller

  • CRD만 생성하면 etcd에 데이터를 저장하고 조회할 수는 있지만 실제 동작은 일어나지 않음. Custom Controller를 정의하면 cr의 생성, 수정, 삭제를 감시하다가 이벤트가 발생하면 특정 로직을 실행
  • 주로 golang으로 구현

Operator

  • CRD와 Custom Controller를 하나로 묶어 관리하는 프레임 워크. 수동으로 crd를 만들고 custom controller를 배포하는 대신, Operator를 배포하면 내부적으로 crd와 custom controller를 한번에 생성.
  • operator hub를 이용하면 전 세계 개발자들이 만들어 놓은 operator를 찾아볼 수 있음

0개의 댓글