개요 - 민감 정보를 클러스터에 저장하려면?
애플리케이션을 운영하다 보면 데이터베이스 접속 정보, API 키, 인증서 등과 같은 민감 정보를 안전하게 관리해야 한다. Kubernetes에서는 ConfigMap, Secret과 같은 환경변수 관리 목적의 리소스를 제공하지만, 이러한 방법들을 통해 민감 정보를 다루는 것은 보안적인 측면에서 명백히 한계를 갖고 있다.
본 글에서는 아래와 같이 민감 정보가 전달되는 과정에서 어떻게 민감 정보를 다루는 것이 더 좋을지 각 단계별 데이터의 형태를 기준으로 살펴보고자 한다.

- Local Machine - 개발자가 작업하는 로컬 머신
- External Storage - 작업물이 올라가는 저장소. GitHub과 같은 서비스
- Kubernetes Cluster - 애플리케이션(Pod)이 속한 클러스터
- Pod - 환경 변수를 사용하는 주체인, 애플리케이션이 구동되는 Pod
환경변수를 저장하는 전통적인 방식
ConfigMap
ConfigMap은 kubernetes에서 환경변수를 관리하기 위한 Object로, 평문 key-value 형식으로 변수를 저장한다.
그러니 아래와 같은 형태로 데이터가 전달된다고 볼 수 있다.

즉, 어느 단계에서든 데이터를 확인할 수 있으니 외부 저장소의 접근 권한, 클러스터 접근 권한, 각 단계의 통신의 도청자 등 다양한 사람이 민감 정보에 접할 수 있다. 가장 보안에 취약한 방법이라고 볼 수 있다. (애당초 민감 정보 저장용으로 정의된 리소스가 아니기도 하다.)
그러니 만약 “민감 정보를 어떻게 클러스터까지 전달하면 좋을까요?” 라는 고민을 털어놓았을 때 ConfigMap에 저장하자고 하는 동료 개발자가 있다면 그 사람은 유령일 수도 있으니 조심하는 편이 좋다.
Secret
Secret은 k8s에서 민감 정보를 저장하기 위해 제공하는 Object이다. 이름과 의도만 보면 훌륭한 보안 솔루션을 제공할 것 같지만, 실상은 그렇지 않다.
Secret은 로컬 머신에서 Kubernetes 명령어 도구 kubectl을 통해 클러스터에 등록할 수 있다. 기본적으로는 암호화되는 값이 아니기 때문에 GitHub 등의 외부 스토리지에 올리지 않도록 유의해야 한다.
Secret은 Kubernetes 클러스터 내 저장소인 etcd에 base64로 인코딩되어 저장된다. 이 환경변수를 사용하는 pod에 주입될 때에는 decode되어 제공된다.
따라서, 아래와 같은 형태로 데이터가 전달된다고 볼 수 있다.

Configmap에 비해 안전하지만, 클러스터의 etcd에 접근할 수 있는 사람들은 모두 Secret 값을 확인할 수 있다. 또한, kubectl get secret
명령을 통해 Secret 자체에 접근하여 값을 확인할 수도 있다. 따라서, 이 방식을 사용할 시 RBAC(역할 기반 접근 제어)를 잘 설정하는 것이 중요하다.
etcd에 사실상 평문(base64 인코딩값)이 저장되는 이슈는 EncryptionConfiguration 이라는 etcd 암호화 리소스를 도입하여 해결할 수 있다. (이 글의 주제에 벗어나므로 자세한 사용법은 다루지 않는다.)
하지만 본 글에서는 만약 당신이 GitOps를 사용하고 있다면 더 좋을 수 있는 방법을 소개해보고자 한다.
Sealed Secret
정의

Sealed Secret이란, Bitnami에서 개발한 Kubernetes 컨트롤러 및 CLI 도구로, Secret을 대칭키 방식으로 암호화하여 민감 정보를 안전하게 저장할 수 있게 돕는다.
어떤 점에서, 어떤 이유로 secret 보다 좋은가?
단순히 개념만 듣는다면, etcd에 암호화를 추가하는 것과 다를 게 뭐가 있는지, 오히려 외부 시스템을 추가하는 복잡함과 러닝커브만 감수해야 하는 게 아닌지 하는 생각이 들 수 있다. 하지만 위에서 다뤘던 flow가 Sealed secret을 적용하면 어떻게 변경되는지 확인해보자.

먼저, Kubernetes 클러스터에 Sealed Secret Controller라는 리소스가 올라가있어야 한다. 이 컨트롤러는 내부적으로 대칭키를 하나 소유하고 있으며, 로컬 머신에서 이 대칭키 중 공개키를 기반으로 자신이 다루는 민감 정보를 암호화하는 요청을 보낼 수 있다.
일단 데이터가 암호화되고 나면, 이 데이터는 비밀키를 소유하고 있는 Sealed Secret Controller만이 복호화할 수 있게 된다. 그러니, GitHub과 같은 외부 스토리지에 암호화된 데이터를 올려도 상관이 없다.
또한, 위의 전체 플로우 중 클러스터 외부에 해당하는 부분에서는 항상 데이터가 암호화된 상태로 다뤄진다. 즉, CI/CD 파이프라인의 가장 앞 단계에서 암호화하고, 가장 마지막(클러스터 내부)에서 복호화하기 때문에, 중간에 가로채서 민감 정보가 유출될 위험을 최소화하는 방법이라고 볼 수 있다.
사용 방법
1. Kubernetes 클러스터에 Sealed Secret Controller 리소스를 추가한다.
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/controller.yaml
2. 로컬 머신에서 Sealed Secret Controller와 통신하기 위해, kubeseal이라는 cli 도구를 설치해야 한다. 해당 도구는 bitnami-labs에서 관리하는 Sealed Secret GitHub 저장소에서 다운받을 수 있다.
Releases · bitnami-labs/sealed-secrets
A Kubernetes controller and tool for one-way encrypted Secrets - bitnami-labs/sealed-secrets
github.com
3. 작업자의 로컬 머신에서 Secret을 생성한 뒤 SealedSecret으로 암호화한다.
# Secret 생성
kubectl create secret generic {secret-name} -o yaml > my-secret.yaml
# Sealed Secret 생성
kubeseal --controller-namespace {namespace} -o yaml < my-secret.yaml > my-sealed-secret.yaml
4. 앞 단계에서 생성한 SealedSecret 리소스를 클러스터에 적용하면 컨트롤러에 의해 복호화되고 클러스터에 Secret 리소스가 등록된다.
kubectl apply -f my-sealed-secret.yaml
반드시 좋은 대안인가?
당연하지만 은총알은 없다. Sealed Secret 또한 앞서 언급했듯 외부 리소스(Sealed Secret Controller)와 도구(kubeseal)에 의존성이 생기며, 이들의 학습에 시간을 투자해야 한다는 단점이 있다.
하지만 당신이 애플리케이션의 운영에 GitOps를 사용하고 있다면, Sealed Secret은 확실히 좋은 대안이 될 수 있다. GitHub/GitLab에 민감 정보를 올려도 된다는 것은 민감 정보를 관리하기 위한 별도의 방법을 마련하지 않아도 된다는 점에서 관리 공수가 절약되며, 대부분의 GitOps 방식은 Deploy와 함께 Kubernetes 리소스들이 자동으로 관리되므로, Secret의 변경이 필요한 경우에도 Sealed Secret만 잘 재생성해주면 복잡하지 않게 민감 정보를 업데이트할 수 있다.