Application Lifecycle Management - etcd에 저장되는 secret 암호화하기

2025. 12. 30. 14:28·CKA

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)”다.
  • 방법은:
    1. EncryptionConfiguration(enc.yaml) 생성(Secret 대상으로, provider 순서 중요)
    2. kube-apiserver에 --encryption-provider-config 옵션 추가
    3. enc.yaml을 kube-apiserver에 hostPath로 마운트
    4. 새 Secret 생성 후 etcd에서 원문 흔적이 사라졌는지 확인
    5. 기존 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
'CKA' 카테고리의 다른 글
  • Application Lifecycle Management - 오토스케일링과 HPA
  • Application Lifecycle Management - 멀티 컨테이터 파드
  • Application Lifecycle Management - Secret
  • Application Lifecycle Management - ConfigMap
5jyan5
5jyan5
  • 5jyan5
    jyan
    5jyan5
  • 전체
    오늘
    어제
    • 분류 전체보기 (243)
      • 김영한의 스프링 핵심 원리(기본편) (8)
      • 김영한의 스프링 핵심 원리 - 고급편 (11)
      • 김영한의 스프링 MVC 1편 (1)
      • 김영한의 스프링 DB 1편 (3)
      • 김영한의 스프링 MVC 2편 (3)
      • 김영한의 ORM 표준 JPA 프로그래밍(기본편) (9)
      • 김영한의 스프링 부트와 JPA 활용2 (2)
      • 김영한의 실전 자바 - 중급 1편 (1)
      • 김영한의 실전 자바 - 고급 1편 (9)
      • 김영한의 실전 자바 - 고급 2편 (9)
      • Readable Code: 읽기 좋은 코드를 작성.. (2)
      • 김영한의 실전 자바 - 고급 3편 (9)
      • CKA (119)
      • 개발 (37)
      • 경제 (4)
      • 리뷰 (1)
      • 정보 (2)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      프록시 팩토리
      @within
      log trace
      스레드
      typequery
      단방향 맵핑
      jpq
      Thread
      reentarantlock
      @discriminatorcolumn
      프록시
      고급
      JPQL
      양방향 맵핑
      gesingleresult
      WAS
      김영한
      조회 성능 최적화
      페치 조인
      락
      @args
      버퍼
      Target
      hibernate5module
      cglib
      requset scope
      빈 후처리기
      자바
      jdk 동적 프록시
      @discriminatorvalue
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.2
    5jyan5
    Application Lifecycle Management - etcd에 저장되는 secret 암호화하기
    상단으로

    티스토리툴바