Secret을 kubectl get secret -o yaml로 보면 값이 Base64로 보이죠. 하지만 Base64는 암호화가 아니라 인코딩이기 때문에, Secret을 읽을 수 있는 권한만 있으면 누구나 디코딩해서 원문을 볼 수 있습니다.
이 글에서 다루는 핵심은 그게 아닙니다.
- Base64는 그대로 유지됩니다(바뀌지 않음).
- 우리가 해결하려는 건 Secret이 etcd(클러스터 저장소)에 “평문에 가까운 형태”로 저장되는 문제입니다.
- 즉, etcd 디스크/백업/스냅샷/직접 접근 같은 시나리오에서 Secret이 노출되는 위험을 줄이기 위해, Kubernetes의 Encryption at Rest(저장 시 암호화) 를 활성화합니다.
1) 먼저 확인: Secret은 Base64로 쉽게 복원된다 (이건 이번 범위 밖이지만 전제)
예시로 Secret 하나를 만들고:
kubectl create secret generic my-secret \
--from-literal=key1=supersecret
YAML로 보면 Base64 형태입니다.
kubectl get secret my-secret -o yaml
그리고 이 값은 이렇게 쉽게 디코딩됩니다.
echo 'c3VwZXJzZWNyZXQ=' | base64 --decode
# supersecret
여기서 중요한 경고:
- Secret YAML을 그대로 GitHub에 올리면 안 됩니다.
- Base64는 “가리는 것”이 아니라 “변환”일 뿐이라서 누구든 복원 가능합니다.
하지만 다시 강조하자면, 이 글의 목표는 Base64를 암호화로 바꾸는 게 아니라, etcd에 저장될 때 암호화되도록 만드는 것입니다.
2) 문제의 본질: etcd에 Secret이 어떻게 저장되는가?
Kubernetes 리소스는 최종적으로 etcd에 저장됩니다. Secret도 예외가 아닙니다.
kubeadm 기반 클러스터(컨트롤 플레인에 etcd가 떠 있는 형태)에서는 보통 아래처럼 etcdctl로 직접 값을 확인할 수 있습니다.
2-1) etcdctl 준비
컨트롤 플레인 노드에서 etcdctl 클라이언트를 설치하거나(예: etcd-client)
혹은 etcd 파드 안으로 들어가서 실행할 수도 있습니다.
2-2) etcd 인증서 위치
kubeadm 구성에서는 etcd 접근 인증서가 보통 아래에 있습니다.
/etc/kubernetes/pki/etcd/
예: ca.crt, server.crt, server.key 등
2-3) etcd에서 Secret 키 조회
Secret은 etcd에서 대개 이런 경로로 저장됩니다.
registry/secrets/<namespace>/<secretName>
예를 들어 default 네임스페이스의 my-secret이면:
export ETCDCTL_API=3
etcdctl get /registry/secrets/default/my-secret \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
출력이 바이너리/혼합 형태로 보여서 보기 불편할 수 있어 hexdump를 붙이기도 합니다.
... | hexdump -C | head
핵심은: 암호화를 켜기 전에는 etcd 안에서 Secret의 원문 흔적을 찾을 수 있다는 점입니다.
즉, etcd 접근권(노드 접근, 백업 접근 등)이 있는 사람에게 Secret이 그대로 노출될 수 있습니다.
3) 해결책: kube-apiserver에 “EncryptionConfiguration” 연결하기
Encryption at Rest는 kube-apiserver가 담당합니다.
- API 서버가 etcd에 쓰기 전에 암호화
- etcd에서 읽어올 때 복호화
3-1) 먼저 확인: 이미 암호화가 켜져 있는지?
API 서버 프로세스 옵션에 --encryption-provider-config가 있는지 확인합니다.
- kubeadm 클러스터라면 kube-apiserver는 보통 Static Pod로 실행됩니다.
- 매니페스트 파일은 보통 여기 있습니다.
/etc/kubernetes/manifests/kube-apiserver.yaml
다음 중 하나로 확인합니다.
# (컨트롤 플레인 노드에서)
cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep encryption-provider-config
없다면 아직 “저장 시 암호화”가 켜져 있지 않은 상태입니다.
4) EncryptionConfiguration 파일 만들기 (Secret만 암호화)
암호화 구성 파일은 예를 들어 /etc/kubernetes/enc/enc.yaml로 만들겠습니다.
4-1) 32바이트 키 생성
AES 기반 알고리즘(aescbc, aesgcm 등)은 키가 필요합니다.
head -c 32 /dev/urandom | base64
출력된 문자열을 아래 secret:에 넣습니다.
4-2) enc.yaml 예시 (aescbc 사용)
강의 흐름처럼 secrets만 대상으로 지정하고, provider 순서를 중요하게 둡니다.
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <여기에_방금_생성한_32바이트_base64_키>
- identity: {}
provider “순서”가 중요한 이유
- 맨 위 provider가 “암호화에 사용”됩니다.
- 아래 provider들은 복호화 호환을 위해 존재합니다.
identity는 “암호화 없음”이므로, 이게 위에 있으면 아무 것도 암호화되지 않습니다.
참고: 최근 권장 흐름에서는
aesgcm을 선호하는 경우도 있습니다. 다만 강의는aescbc로 설명하니 동일 흐름으로 이해하고, 실제 운영에서는 클러스터 정책/버전/권장사항에 따라 알고리즘을 선택하세요.
5) kube-apiserver에 enc.yaml을 마운트하고 옵션 추가하기 (kubeadm/Static Pod 기준)
5-1) 파일 배치
먼저 디렉터리를 만들고 파일을 옮깁니다.
sudo mkdir -p /etc/kubernetes/enc
sudo cp enc.yaml /etc/kubernetes/enc/enc.yaml
5-2) kube-apiserver 매니페스트 수정
파일: /etc/kubernetes/manifests/kube-apiserver.yaml
(1) kube-apiserver 실행 옵션 추가
컨테이너 command args에 아래 옵션을 추가합니다.
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml
(2) volumeMounts 추가
kube-apiserver 컨테이너에 마운트:
volumeMounts:
- mountPath: /etc/kubernetes/enc
name: enc-config
readOnly: true
(3) volumes 추가
호스트 경로를 파드에 연결:
volumes:
- name: enc-config
hostPath:
path: /etc/kubernetes/enc
type: DirectoryOrCreate
Static Pod 매니페스트를 저장하면 kubelet이 변경을 감지해 kube-apiserver를 재시작합니다. (잠깐 사라졌다가 다시 올라오는 게 정상)
6) 적용 확인: kube-apiserver 재기동 & 옵션 존재 확인
잠깐 기다린 뒤 kube-apiserver가 다시 올라왔는지 확인합니다.
- 컨테이너 런타임이 containerd면
crictl로 확인하기도 합니다.
예시:
crictl ps | grep kube-apiserver
그리고 매니페스트/프로세스에서 옵션이 있는지 확인합니다.
cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep encryption-provider-config
7) “암호화가 실제로 됐는지” 검증하기
이제 암호화 활성화 이후 새 Secret을 하나 더 만듭니다.
kubectl create secret generic my-secret-2 \
--from-literal=key2=topsecret
그리고 동일하게 etcd에서 값을 확인해봅니다.
export ETCDCTL_API=3
etcdctl get /registry/secrets/default/my-secret-2 \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
| hexdump -C | head
여기서 기대하는 결과:
- 암호화 활성화 전처럼
topsecret같은 원문 흔적이 보이지 않아야 합니다. - 보통
k8s:enc:...같은 암호화 prefix/바이너리 형태가 나타납니다.
8) 중요한 포인트: “암호화 활성화 이후 생성된 것만” 자동 암호화된다
강의에서 가장 실전적인 함정이 이 부분입니다.
- 암호화를 켠 뒤 새로 생성되는 Secret은 암호화되어 저장됩니다.
- 하지만 기존에 이미 etcd에 저장되어 있던 Secret은 자동으로 재암호화되지 않습니다.
- 그래서 “예전에 만든 my-secret은 여전히 평문 흔적이 보이고, my-secret-2는 안 보인다” 같은 상황이 발생합니다.
기존 Secret까지 암호화하려면 “재기록(rewrite)”가 필요
가장 흔한 방식이 “그대로 다시 replace”해서 API 서버가 다시 저장하도록 만드는 것입니다.
예시(전체 네임스페이스 Secret 재기록; 운영에서는 신중히):
kubectl get secrets -n default -o json | kubectl replace -f -
또는 특정 Secret만:
kubectl get secret my-secret -o json | kubectl replace -f -
이 작업을 한 뒤 다시 etcd를 확인하면, 기존 Secret에서도 원문 흔적이 사라지는 것을 확인할 수 있습니다.
9) 운영에서 꼭 알아야 할 주의사항
9-1) 키/구성 파일 분실 = 복구 불가
EncryptionConfiguration의 키를 잃어버리면:
- etcd에 암호화되어 저장된 Secret을 복호화할 수 없고
- 결과적으로 애플리케이션 장애로 이어집니다.
enc.yaml과 키는 안전하게 백업/관리해야 합니다.
9-2) 키 로테이션(회전)
키를 바꿔야 한다면 provider에 키를 추가하고(새 키를 위로), 점진적으로 재암호화합니다.
예시 개념:
providers:
- aescbc:
keys:
- name: key2 # 새 키(암호화에 사용)
secret: ...
- name: key1 # 기존 키(복호화 호환)
secret: ...
- identity: {}
그 후 기존 Secret을 rewrite 해서 key2로 재암호화되게 만들고, 충분히 전환되면 key1을 제거합니다.
9-3) HA 컨트롤 플레인
컨트롤 플레인이 여러 대면:
- 모든 kube-apiserver가 동일한 enc.yaml(또는 동일 내용)을 공유/배포받아야 합니다.
- 불일치하면 일부 API 서버에서 복호화가 실패할 수 있습니다.
9-4) 관리형(KSaaS) 환경(EKS/GKE/AKS 등)
관리형에서는 kube-apiserver 매니페스트를 직접 편집하는 방식이 불가능하거나 제한됩니다. 대신:
- 클러스터 옵션으로 etcd 암호화 제공
- KMS 연동 제공(Cloud KMS)
같은 “관리형 제공 기능”을 사용해야 합니다.
10) 정리
- Secret의 Base64는 암호화가 아니다(누구나 디코딩 가능).
- 이번 주제는 Base64가 아니라 etcd에 저장되는 Secret의 “미사용 시 암호화(Encryption at Rest)”다.
- 방법은:
- EncryptionConfiguration(enc.yaml) 생성(Secret 대상으로, provider 순서 중요)
- kube-apiserver에
--encryption-provider-config옵션 추가 - enc.yaml을 kube-apiserver에 hostPath로 마운트
- 새 Secret 생성 후 etcd에서 원문 흔적이 사라졌는지 확인
- 기존 Secret은 재기록(rewrite)해야 재암호화됨
'CKA' 카테고리의 다른 글
| Application Lifecycle Management - 오토스케일링과 HPA (0) | 2025.12.30 |
|---|---|
| Application Lifecycle Management - 멀티 컨테이터 파드 (1) | 2025.12.30 |
| Application Lifecycle Management - Secret (0) | 2025.12.30 |
| Application Lifecycle Management - ConfigMap (0) | 2025.12.30 |
| Application Lifecycle Management - Docker CMD/Entrypoint와 쿠버네티스 (0) | 2025.12.30 |