LabHub
배우기 러닝패스 코스

CKS — Kubernetesセキュリティスペシャリスト

Secretの扱いと保存時の暗号化

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

Secret 을 타입별로 만들고, 주입 방식에 따라 노출 경로가 어떻게 달라지는지 확인하고, RBAC 으로 읽기 범위를 좁힌 뒤 저장 시 암호화 설정 파일을 직접 작성합니다.

왜 중요한가

Secret 매니페스트의 값이 base64 로 되어 있어 암호화된 것처럼 보이지만, base64 는 인코딩이지 암호화가 아닙니다. 키가 없고 되돌리는 데 명령 하나면 됩니다. 기본 설정에서 Secret 은 etcd 에 평문으로 저장되므로 etcd 백업 파일을 얻은 사람은 모든 Secret 을 읽습니다. EncryptionConfiguration 을 켜야 비로소 저장된 값이 암호화되고, 이때 identity 프로바이더는 반드시 목록의 마지막이어야 합니다. 앞에 오면 평문 저장으로 되돌아갑니다.

주입 방식도 중요합니다. 환경변수는 /proc/PID/environ 으로 읽히고 자식 프로세스에 상속되며, 크래시 리포트나 디버그 페이지에 통째로 실려 나갑니다. 볼륨 마운트가 더 안전하고, defaultMode 로 파일 권한을 좁히는 것이 관례입니다.

마지막은 RBAC 입니다. 어떤 네임스페이스에 get secrets 권한이 있는 사람은 그 네임스페이스의 모든 Secret 을 읽습니다. resourceNames 로 특정 Secret 만 허용할 수 있다는 사실이 의외로 잘 알려져 있지 않습니다.

단계

  1. 네임스페이스 cks-secrets 를 만들고 그 안에 Opaque Secret db-cred 를 만든다. 키는 username(값 app)과 password(값 pr0d-Db-Pass) 두 개다.
  2. cks-secretskubernetes.io/dockerconfigjson 타입 Secret registry-cred 를 만든다. 서버는 registry.cks.local, 사용자는 ci, 비밀번호는 ci-token 이다.
  3. cks-secretskubernetes.io/tls 타입 Secret shop-tls 를 만든다. tls.crttls.key 가 모두 비어 있지 않아야 한다.
  4. cks-secrets 에 파드 env-app(컨테이너 이름 app, 이미지 nginx:1.27-alpine)을 만든다. 컨테이너의 envFrom[0].secretRef.namedb-cred 다.
  5. cks-secrets 에 파드 vol-app(컨테이너 이름 app)을 만든다. 볼륨 이름은 db, secret.secretNamedb-cred, secret.defaultMode0400(10진수 256)이고, 컨테이너는 그 볼륨을 /etc/dbreadOnly: true 로 마운트한다.
  6. cks-secrets 에 Opaque Secret app-config 를 만든다. 키는 mode(값 strict) 하나이고 immutabletrue 다.
  7. cks-secrets 에 ServiceAccount app 을 만들고, Role db-cred-reader 를 만든다. apiGroups 는 코어 그룹, resources 는 secrets, resourceNamesdb-cred 하나, verbs 는 get 하나다. RoleBinding app-db-cred 로 SA app 에 바인딩한다. 그다음 두 질문의 답(yes/no)을 순서대로 /root/cks-secrets/can-i.txt 에 두 줄로 저장한다. 첫 줄은 app SA 가 secret/db-cred 를 get 할 수 있는지, 두 줄은 secret/app-config 를 get 할 수 있는지다.
  8. /root/cks-secrets/encryption-config.yaml 에 저장 시 암호화 설정을 쓴다. apiVersion: apiserver.config.k8s.io/v1, kind: EncryptionConfiguration, resources[0].resourcessecrets 하나, providers 는 두 개로 첫 번째가 aescbc (키 이름 key1, secret 은 32 바이트를 base64 인코딩한 값), 마지막이 identity 다.

참고

Opaque Secret 만들기

네임스페이스 cks-secrets 를 만들고 그 안에 Opaque Secret db-cred 를 만든다. 키는 username(값 app)과 password(값 pr0d-Db-Pass) 두 개다.

kubectl create secret generic --from-literal= 이 가장 빠릅니다. 타입을 지정하지 않으면 Opaque 입니다.

레지스트리 자격증명 Secret

cks-secretskubernetes.io/dockerconfigjson 타입 Secret registry-cred 를 만든다. 서버는 registry.cks.local, 사용자는 ci, 비밀번호는 ci-token 이다.

타입이 정해진 Secret 은 데이터 키 이름도 정해져 있습니다. dockerconfigjson 타입은 .dockerconfigjson 키 하나만 씁니다.

TLS Secret

cks-secretskubernetes.io/tls 타입 Secret shop-tls 를 만든다. tls.crttls.key 가 모두 비어 있지 않아야 한다.

tls.crttls.key 두 키가 모두 있어야 API 서버가 받아 줍니다.

환경변수로 주입하기

cks-secrets 에 파드 env-app(컨테이너 이름 app, 이미지 nginx:1.27-alpine)을 만든다. 컨테이너의 envFrom[0].secretRef.namedb-cred 다.

envFromsecretRef 는 Secret 의 모든 키를 환경변수로 펼칩니다. 편하지만 프로세스 환경에 그대로 남는다는 점을 기억하세요.

볼륨으로 마운트하고 권한 좁히기

cks-secrets 에 파드 vol-app(컨테이너 이름 app)을 만든다. 볼륨 이름은 db, secret.secretNamedb-cred, secret.defaultMode0400(10진수 256)이고, 컨테이너는 그 볼륨을 /etc/dbreadOnly: true 로 마운트한다.

defaultMode 는 YAML 에서 8진수로 쓰면 10진수로 저장됩니다. 0400 은 소유자 읽기 전용입니다.

변경 불가 Secret

cks-secrets 에 Opaque Secret app-config 를 만든다. 키는 mode(값 strict) 하나이고 immutabletrue 다.

immutable: true 를 켜면 데이터를 수정할 수 없고 지웠다 다시 만들어야 합니다. kubelet 의 watch 부하도 줄어듭니다.

특정 Secret 만 읽게 제한

cks-secrets 에 ServiceAccount app 을 만들고, Role db-cred-reader 를 만든다. apiGroups 는 코어 그룹, resources 는 secrets, resourceNamesdb-cred 하나, verbs 는 get 하나다. RoleBinding app-db-cred 로 SA app 에 바인딩한다. 그다음 두 질문의 답(yes/no)을 순서대로 /root/cks-secrets/can-i.txt 에 두 줄로 저장한다. 첫 줄은 app SA 가 secret/db-cred 를 get 할 수 있는지, 두 줄은 secret/app-config 를 get 할 수 있는지다.

Role 의 resourceNames 로 이름을 못 박으면 그 Secret 만 읽을 수 있습니다. 결과는 auth can-iTYPE/NAME 형식으로 물어 확인하세요.

저장 시 암호화 설정 파일

/root/cks-secrets/encryption-config.yaml 에 저장 시 암호화 설정을 쓴다. apiVersion: apiserver.config.k8s.io/v1, kind: EncryptionConfiguration, resources[0].resourcessecrets 하나, providers 는 두 개로 첫 번째가 aescbc (키 이름 key1, secret 은 32 바이트를 base64 인코딩한 값), 마지막이 identity 다.

프로바이더 목록은 순서가 의미를 갖습니다. 쓰기에는 첫 번째가 쓰이고, 읽기에는 순서대로 시도합니다. identity 의 위치를 잘못 두면 평문 저장으로 되돌아갑니다.