보안 기초와 TLS
- 15.1 보안 기본 원칙
- 15.2 인증
- 15.3 TLS 기초
- 15.4 Kubernetes의 TLS
- 15.5 인증서 생성
- 15.6 인증서 상태 확인
쿠버네티스 클러스터를 운영한다면 보안은 피할 수 없는 주제야. API 서버에 누가 접근하는지, 그 사람이 뭘 할 수 있는지를 통제하는 게 시작이고, 구성 요소 사이의 모든 통신을 TLS로 보호하는 게 기본이야. 인증서가 뭔지부터 실제로 만들고 점검하는 방법까지 한 번에 훑어보자.
Kubernetes 보안의 첫 번째 방어선은 kube API 서버야. 클러스터에서 일어나는 거의 모든 작업이 API 서버를 거치거든. kubectl을 쓰든, REST API를 직접 호출하든 결국 API 서버한테 요청이 가는 거야. 그래서 **"누가 클러스터에 접근할 수 있는가"**와 "접근한 다음에 뭘 할 수 있는가", 이 두 가지를 제어하는 게 핵심이야.
"누가 접근할 수 있는가"는 인증(Authentication) 메커니즘이 결정해. 사용자 이름/비밀번호 파일, 토큰 파일, 인증서, LDAP 같은 외부 인증 공급자를 쓸 수도 있고, 머신(봇)이 접근하는 경우엔 서비스 계정을 만들어서 쓰게 돼.
"뭘 할 수 있는가"는 권한 부여(Authorization) 메커니즘이 정해. 가장 대표적인 게 **RBAC(역할 기반 액세스 제어)**인데, 사용자를 특정 권한이 있는 그룹에 연결하는 방식이야. 그 외에도 ABAC(속성 기반), Node Authorizer, Webhook 같은 모듈도 있어.
그 다음으로 중요한 건 클러스터 내부 통신 보안이야. etcd, kube-controller-manager, scheduler, API 서버, 그리고 워커 노드의 kubelet, kube-proxy 같은 구성 요소들 사이의 모든 통신은 TLS 암호화로 보호돼. 인증서를 어떻게 설정하는지는 뒤에서 자세히 다뤄.
마지막으로 클러스터 안에서 파드 간 통신도 신경 써야 해. 기본적으로 모든 파드는 클러스터 내 다른 모든 파드에 접근할 수 있거든. 이걸 제한하려면 **네트워크 정책(Network Policy)**을 쓰면 돼. 그리고 이 모든 것 이전에, 클러스터를 호스팅하는 인프라 자체도 당연히 보호해야 해. root 접근 비활성화, 비밀번호 인증 끄기, SSH 키 기반 인증만 허용하는 식으로. 인프라가 뚫리면 그 위에 있는 건 다 뚫리는 거니까.
보안의 큰 그림을 잡았으니 이제 인증 메커니즘을 좀 더 깊이 들어가보자. Kubernetes 클러스터에 접근하는 모든 요청은 kube API 서버를 통해 이루어지고, API 서버가 요청을 처리하기 전에 반드시 인증부터 해.
클러스터에 접근하는 주체는 크게 두 부류야. 관리자나 개발자 같은 사람, 그리고 다른 프로세스나 서비스 같은 봇(머신). 최종 사용자가 애플리케이션에 접근하는 건 애플리케이션 자체가 알아서 처리하니까 여기선 논외야.
중요한 포인트가 하나 있는데, Kubernetes는 사용자 계정을 자체적으로 관리하지 않아. 사용자 정보가 담긴 파일이나 LDAP 같은 외부 ID 서비스에 의존하는 거야. 그래서 kubectl create user 같은 명령은 없고, 사용자 목록을 볼 수도 없어. 반면에 **서비스 계정(Service Account)**은 Kubernetes가 직접 관리할 수 있어서 API로 생성하고 관리할 수 있어.
인증 메커니즘은 여러 가지가 있어. 가장 단순한 건 정적 비밀번호 파일이나 정적 토큰 파일이야. CSV 파일에 비밀번호(또는 토큰), 사용자 이름, 사용자 ID를 넣고, 선택적으로 네 번째 열에 그룹 정보를 추가할 수 있어. 이 파일을 kube API 서버에 --basic-auth-file 또는 --token-auth-file 옵션으로 전달하면 돼. 토큰 파일을 쓰는 경우 인증할 때 요청 헤더에 Bearer 토큰으로 지정하면 되고.
다만 이 방식은 비밀번호나 토큰을 일반 텍스트로 저장하기 때문에 안전하지 않아서 프로덕션에선 절대 권장되지 않아. 인증의 기본 개념을 이해하기 쉬운 방법이라서 소개한 거지. kubeadm으로 설정하는 경우엔 kube-apiserver가 파드로 돌아가기 때문에 인증 파일을 전달하기 위한 볼륨 마운트도 고려해야 하고, 새 사용자에 대한 권한(Authorization) 설정도 빠뜨리면 안 돼. 실제로는 인증서 기반 인증이 훨씬 많이 쓰이는데, 그 기반이 되는 TLS를 먼저 이해해야 해.
TLS 인증서는 통신하는 두 당사자 사이에 신뢰를 보장하는 거야. 사용자가 웹 서버에 접속할 때 통신이 암호화되어 있는지, 그리고 그 서버가 진짜 맞는지를 보장해주는 역할을 해.
보안 연결이 없으면 뭐가 문제인지부터 보자. 온라인 뱅킹에 로그인할 때 자격 증명이 일반 텍스트로 전송되면 네트워크를 스니핑하는 해커가 그걸 그대로 가로챌 수 있어. 당연히 위험하지. 그래서 암호화 키를 써서 데이터를 암호화해야 해.
대칭 암호화는 하나의 키로 암호화하고 같은 키로 복호화하는 방식이야. 문제는 서버도 그 키가 있어야 복호화할 수 있으니까 키를 네트워크로 보내야 하는데, 해커가 그 키도 스니핑할 수 있다는 거야.
비대칭 암호화는 이 문제를 해결해. **공개 키(public key)**와 개인 키(private key) 한 쌍을 쓰는 거야. 공개 키로 암호화한 건 개인 키로만 풀 수 있어. 개인 키는 절대 공유하면 안 되고, 공개 키는 누구한테든 줘도 돼.
SSH 접속을 예로 들면, ssh-keygen으로 키 쌍을 생성하고, 공개 키를 서버의 ~/.ssh/authorized_keys에 넣어. 그러면 개인 키를 가진 사람만 SSH 접속할 수 있어. 공개 키는 여러 서버에 복사해 놓을 수 있고, 다른 사용자도 자기만의 키 쌍을 만들어서 같은 방식으로 접근할 수 있어.
웹 서버에서는 이런 식으로 동작해. 서버가 OpenSSL로 공개 키/개인 키 쌍을 만들어. 사용자가 HTTPS로 접속하면 서버가 공개 키를 보내. 사용자의 브라우저는 대칭 키를 하나 생성하고, 그걸 서버의 공개 키로 암호화해서 보내. 서버는 개인 키로 복호화해서 대칭 키를 꺼내. 이제 둘 다 같은 대칭 키를 갖고 있으니까 그걸로 앞으로의 통신을 암호화해. 해커는 공개 키밖에 없으니까 대칭 키를 꺼낼 수가 없어. 비대칭 암호화로 대칭 키를 안전하게 전달하고, 이후에는 대칭 암호화로 실제 통신을 보호하는 거야.
여기서 한 가지 문제가 더 있어. 해커가 진짜 은행 사이트와 똑같이 생긴 가짜 사이트를 만들고, 자기도 공개/개인 키 쌍을 만들어서 HTTPS로 서비스하면 어떻게 될까? 사용자 입장에선 암호화된 통신을 하고 있으니까 안전하다고 착각할 수 있어. 하지만 실제로는 해커 서버랑 통신하고 있는 거지.
이걸 막기 위해 **인증서(Certificate)**가 있어. 서버가 키만 보내는 게 아니라 인증서 안에 키를 넣어서 보내. 인증서에는 발급 대상(Subject), 대체 이름(SAN), 발급자(Issuer) 같은 정보가 들어있어. 핵심은 "누가 이 인증서에 서명했는가"야. 자기가 직접 서명한 자체 서명(Self-Signed) 인증서는 브라우저가 신뢰하지 않아. 브라우저에는 인증서 유효성 검사 메커니즘이 내장되어 있어서 가짜 인증서를 감지하면 경고를 띄워줘.
여기서 **인증 기관(CA, Certificate Authority)**이 등장해. Symantec, DigiCert, Comodo 같은 잘 알려진 CA한테 **CSR(인증서 서명 요청)**을 보내면, CA가 도메인 소유권을 확인하고 서명해서 돌려줘. OpenSSL로 개인 키를 만들고, 그 키와 도메인 이름으로 CSR을 생성한 다음 CA에 보내는 거야. 해커가 같은 방법으로 CSR을 보내도 검증 단계에서 실패하니까 유효한 인증서를 받을 수 없어. 브라우저에는 주요 CA들의 공개 키가 내장되어 있어서, 인증서가 진짜 그 CA가 서명한 건지 CA의 공개 키로 확인할 수 있어.
조직 내부에서만 쓰는 사이트는 공개 CA로는 안 되니까 사설 CA를 호스팅할 수 있어. 내부 CA의 공개 키를 직원 브라우저에 설치하면 조직 내에서 보안 연결을 설정할 수 있지.
클라이언트 인증서도 있어. 서버가 클라이언트한테 "너도 인증서 내놔"라고 요청할 수 있는 거야. 일반 웹사이트에서는 잘 안 쓰이지만, Kubernetes 같은 시스템에서는 적극적으로 활용돼. 클라이언트도 유효한 CA에서 키 쌍과 서명된 인증서를 만들어서 서버에 제출해야 해.
전체 흐름을 정리하면, 서버가 CA에 CSR을 보내고, CA가 개인 키로 서명해서 인증서를 돌려줘. 서버는 이 인증서로 웹 애플리케이션을 구성해. 사용자가 접속하면 서버가 인증서(공개 키 포함)를 보내고, 브라우저가 CA의 공개 키로 인증서를 검증한 다음 서버의 공개 키를 꺼내. 그리고 대칭 키를 생성해서 서버의 공개 키로 암호화해 보내고, 서버가 개인 키로 복호화해서 대칭 키를 얻어. 이후 통신은 대칭 키로 진행돼. 이 전체 인프라를 **PKI(Public Key Infrastructure)**라고 해.
파일 이름 규칙도 알아두면 좋아. 공개 키/인증서는 .crt나 .pem 확장자를 쓰고, 개인 키는 .key가 붙거나 파일명에 -key가 들어가. 예를 들어 server.crt는 인증서, server.key나 server-key.pem은 개인 키야. "key"라는 단어가 있으면 개인 키, 없으면 공개 키/인증서라고 기억하면 편해.
TLS의 기본을 알았으니 이제 쿠버네티스에서 이게 어떻게 적용되는지 보자. Kubernetes 클러스터에서 모든 구성 요소 간 통신은 TLS로 보호해야 해. 마스터 노드와 워커 노드 사이, 관리자와 API 서버 사이, 내부 구성 요소끼리 전부 다. 이걸 위해 서버 인증서와 클라이언트 인증서 두 종류가 필요해.
서버 인증서가 필요한 구성 요소부터 보자. kube API 서버는 HTTPS 서비스를 노출하는 서버니까 apiserver.crt와 apiserver.key가 필요해. ETCD 서버도 클러스터 정보를 저장하는 서버라서 etcdserver.crt와 etcdserver.key가 필요하고. 워커 노드의 kubelet도 HTTPS API 엔드포인트를 노출하니까 kubelet.crt와 kubelet.key가 필요해.
클라이언트 인증서는 이 서버들에 접근하는 쪽에서 쓰는 거야. 관리자가 API 서버에 접근하려면 admin.crt와 admin.key가 필요하고, 스케줄러(scheduler.crt/key), 컨트롤러 매니저(controller-manager.crt/key), kube-proxy(kube-proxy.crt/key) 모두 API 서버의 클라이언트니까 각각 인증서가 필요해.
서버끼리도 통신하거든. kube API 서버는 ETCD 서버와 통신하는데, 이때 API 서버는 ETCD의 클라이언트 역할이야. 기존 apiserver.crt/key를 재사용하거나 별도의 인증서를 만들 수 있어. API 서버가 각 노드의 kubelet과 통신할 때도 마찬가지야. ETCD 서버와 통신하는 건 오직 kube API 서버뿐이라는 것도 기억해두면 좋아.
이 모든 인증서에 서명하려면 **인증 기관(CA)**이 필요해. 클러스터에 CA가 최소 하나는 있어야 하고, ETCD 전용 CA를 따로 둘 수도 있어. CA 자체도 ca.crt와 ca.key라는 인증서/키 쌍을 갖고 있어.
정리하면, 클라이언트가 주로 API 서버에 연결할 때 쓰는 클라이언트 인증서 세트, 그리고 API 서버/ETCD/kubelet이 자기 자신을 증명하는 서버 인증서 세트로 나뉘어. 인증서 이름은 클러스터를 누가 어떻게 설정했느냐에 따라 다를 수 있으니까, 이름보다는 역할로 구분하는 게 중요해.
이제 실제로 인증서를 생성하는 방법을 보자. OpenSSL 도구를 사용할 거야. CA 인증서부터 만들자. 순서는 세 단계야.
# 1. CA 개인 키 생성
openssl genrsa -out ca.key 2048
# 2. CSR(인증서 서명 요청) 생성 - CN에 CA 이름 지정
openssl req -new -key ca.key -subj "/CN=KUBERNETES-CA" -out ca.csr
# 3. 자체 서명으로 인증서 생성 (CA니까 자기가 자기를 서명)
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
CA는 자기 자신이 서명하니까 자체 서명 인증서야. 다른 모든 인증서는 이 CA 키 쌍으로 서명할 거야. 이제 CA의 개인 키(ca.key)와 루트 인증서(ca.crt)가 준비됐어.
클라이언트 인증서를 만들어보자. 관리자 사용자부터 시작하면, 같은 프로세스를 따르는데 마지막에 CA 인증서와 CA 키를 지정해서 서명하는 게 달라.
# 1. 개인 키 생성
openssl genrsa -out admin.key 2048
# 2. CSR 생성 - CN에 사용자 이름, O에 그룹 지정
openssl req -new -key admin.key -subj "/CN=kube-admin/O=system:masters" -out admin.csr
# 3. CA 키 쌍으로 서명하여 인증서 생성
openssl x509 -req -in admin.csr -CA ca.crt -CAkey ca.key -out admin.crt
**CN(Common Name)**은 kubectl이 인증할 때 쓰는 이름이야. 감사 로그 등에 이 이름이 찍히니까 관련 있는 이름을 넣어야 해. 꼭 kube-admin이 아니어도 되지만, 의미 있는 이름이 좋겠지. 그리고 관리자 권한을 가지려면 system:masters 그룹에 속해야 하는데, CSR 만들 때 O=system:masters로 그룹 정보를 추가해. 키와 인증서 쌍을 생성하는 이 전체 과정은 새 사용자 계정을 만드는 것과 비슷해. 인증서가 검증된 사용자 ID이고, 키가 비밀번호 같은 역할을 하는 거야. 단순한 사용자 이름/비밀번호보다 훨씬 안전하고.
스케줄러, 컨트롤러 매니저, kube-proxy 같은 시스템 구성 요소의 인증서도 같은 방식으로 만들어. 다만 이것들은 시스템 구성 요소니까 이름 앞에 system: 접두사를 붙여야 해. system:kube-scheduler, system:kube-controller-manager 이런 식으로.
이 인증서를 쓰는 방법은 두 가지야. REST API 호출할 때 옵션으로 직접 지정하거나, kubeconfig 파일에 넣어두는 거야.
curl https://kube-apiserver:6443/api/v1/pods \
--key admin.key --cert admin.crt --cacert ca.crt
중요한 건, 클라이언트든 서버든 인증서를 설정할 때 반드시 CA 루트 인증서(ca.crt)도 함께 지정해야 한다는 거야. 상대방이 보낸 인증서가 우리 CA가 서명한 게 맞는지 확인하려면 CA 공개 인증서가 필요하거든.
서버 인증서도 만들어야 해. ETCD 서버는 같은 절차로 인증서를 만들고, 고가용성 환경에서 여러 서버로 클러스터를 구성하면 피어 인증서도 추가로 생성해야 해. ETCD 서버 시작할 때 키 파일, 인증서 파일, CA 파일, 피어 인증서 파일들을 옵션으로 지정해.
kube API 서버 인증서는 좀 특별해. API 서버는 클러스터 안에서 여러 이름으로 불리거든. kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local, 그리고 호스트 IP 주소까지. 이 모든 이름을 인증서에 **SAN(Subject Alternative Name)**으로 넣어야 해. 이걸 위해 OpenSSL 설정 파일을 만들어서 대체 이름 섹션에 필요한 DNS 이름과 IP 주소를 전부 넣고, CSR 생성할 때 -config 옵션으로 전달해. 그리고 CA 인증서와 키로 서명하면 API 서버 인증서가 완성돼.
API 서버 실행 파일이나 서비스 구성 파일에는 이 인증서들의 위치를 전달해야 해. CA 파일, TLS 인증서, ETCD 연결용 클라이언트 인증서, kubelet 연결용 클라이언트 인증서까지 전부 지정해줘야 하거든.
kubelet 인증서는 노드마다 하나씩 만들어. 인증서 이름은 노드 이름을 따라가 (node01, node02 등). 클라이언트 인증서(kubelet이 API 서버에 접근할 때 쓰는)는 system:node:<노드이름> 형식이어야 하고, system:nodes 그룹에 속해야 해. 그래야 API 서버가 어떤 노드인지 식별하고 적절한 권한을 줄 수 있어. 인증서가 생성되면 kubelet 구성 파일에 넣는데, 루트 인증서를 지정하고 kubelet 노드 인증서를 제공하면 돼. 클러스터의 각 노드에 대해 이 작업을 반복해야 해.
인증서를 만드는 법을 알았으니, 이미 돌아가는 클러스터의 인증서를 점검하는 방법도 알아야지. 새로운 팀에 합류해서 Kubernetes 환경 관리를 맡게 됐는데 인증서 관련 문제가 있다고 들었어. 그래서 전체 클러스터의 인증서 상태 확인(health check)을 해야 하는 상황이야.
먼저 클러스터가 어떻게 설정됐는지 파악하는 게 중요해. 처음부터 수동으로 구축했으면 인증서도 직접 만든 거고, 모든 구성 요소가 OS의 네이티브 서비스로 돌아가. kubeadm 같은 도구를 썼으면 인증서가 자동으로 만들어지고, 구성 요소들이 파드로 배포돼. 어디서 정보를 찾아야 하는지가 달라지니까 설정 방식을 먼저 확인해야 해.
kubeadm으로 구축된 클러스터를 기준으로 설명할게. 상태 확인은 이런 순서로 해. 먼저 시스템에서 사용되는 모든 인증서를 식별해야 해. /etc/kubernetes/manifests/ 폴더에서 kube-apiserver 정의 파일을 찾아봐. API 서버를 시작하는 데 사용되는 명령에 서버가 사용하는 모든 인증서에 대한 정보가 있거든. 인증서 파일 경로, 이름, 대체 이름, 소속 조직, 발급자, 만료일을 전부 정리해두면 좋아.
각 인증서의 상세 정보를 보려면 OpenSSL 명령을 써.
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout
여기서 확인해야 할 것들이 있어. Subject 섹션에서 인증서 이름(CN)을 확인해. kube-apiserver 인증서라면 CN이 kube-apiserver여야 하지. Subject Alternative Name에 필요한 DNS 이름과 IP 주소가 전부 들어있는지도 봐야 하고, kube API 서버는 대체 이름이 꽤 많으니까 모두 있는지 확인해. Validity 섹션에서 만료일을 확인하고, Issuer를 보고 올바른 CA가 발급한 건지 확인해. kubeadm은 CA 이름을 kubernetes로 지정하니까 Issuer가 kubernetes여야 해. 다른 모든 인증서도 같은 방식으로 점검하면 돼.
올바른 이름과 SAN을 쓰고 있는지, 올바른 조직에 속해 있는지, 올바른 발급자가 서명했는지, 만료되지 않았는지를 확인하는 게 핵심이야. 인증서 요구 사항은 Kubernetes 공식 문서에 자세히 나와 있어.
문제가 발생하면 로그를 봐야 하는데, 클러스터 설정 방식에 따라 다르게 접근해. 직접 구축한 경우에는 OS의 로깅 기능(journalctl 등)으로 서비스 로그를 보면 되고, kubeadm 환경에서는 kubectl logs <파드이름>으로 확인해. 만약 API 서버나 etcd 같은 핵심 구성 요소가 다운돼서 kubectl이 안 먹히면, 한 단계 아래로 내려가서 docker ps -a로 컨테이너를 찾고 docker logs <컨테이너ID>로 로그를 봐야 해.
정리
15장 읽고 기억할 거 세 가지:
- 쿠버네티스 보안의 핵심은 API 서버 접근 제어야. "누가 접근하는가(인증)"와 "뭘 할 수 있는가(권한 부여)"를 통제하고, 구성 요소 간 모든 통신은 TLS로 암호화해야 해.
- 인증서는 서버 인증서(API 서버, ETCD, kubelet)와 클라이언트 인증서(admin, 스케줄러, 컨트롤러 매니저, kube-proxy)로 나뉘고, 모두 클러스터 CA가 서명해. 시스템 구성 요소는
system:접두사, API 서버는 SAN에 모든 별칭을 넣어야 한다는 규칙을 기억하자. - 인증서 점검은
openssl x509 -text -noout명령으로 CN, SAN, Issuer, 만료일을 확인하고, 문제 시kubectl logs나docker logs로 로그를 추적하면 돼.