[CKS] Minimize Microservice Vulnerabilities (5) - Manage Kubernetes secrets
Kubernetes Secrets 관리 방법을 배웁니다. ConfigMap과의 차이, base64 인코딩, Pod 주입 방식, etcd 저장 시 암호화 활성화까지 보안 관리의 전반을 다룹니다.
개요
CKS 자격증을 위해 아래 내용을 정리했습니다.
Kubernetes Secrets을 관리하는 방법
예시 애플리케이션에서 MySQL에 연결하기 위한 값들이 하드코딩 되어있습니다.
민감하지 않은 데이터(호스트 or 사용자 이름)은 ConfigMap을 활용할 수 있습니다.
import os
from flask import Flask, render_template # Added render_template import
app = Flask(__name__)
@app.route("/")
def main():
# Warning: Hardcoding credentials (host, user, password) is not secure.
mysql.connector.connect(host="mysql", database="mysql",
user="root", password="paswrd")
return render_template('hello.html', color=fetchcolor())
if __name__ == "__main__":
app.run(host="0.0.0.0", port="8080")
Kubernetes에서 ConfigMap 과 Secrets 의 차이를 보면 다음과 같습니다.
- ConfigMap: 평문 저장, 민감하지 않은 설정용
- Secret: base64 인코딩 저장 (보안 목적 아님, 데이터 호환성 목적), RBAC로 접근 제어 분리 가능
Kubernetes Secrets 사용 방법
비밀 키를 생성하는 방법은 두 가지 단계로 이루어집니다
- Secrets 생성
- Secrets를 Pod에 주입
우선 일반 텍스트를 base64 인코딩 값으로 변환합니다.
DB Host: mysql >> bXlzcWw=
DB User: root >> cm9vdA==
DB Password: paswrd >> cGFzd3Jk
Secrets 생성하기
Secrets 생성하는 방법은 크게 imperative(명령형) , declarative(선언형) 2가지 입니다.
또한 평문을 사용하지 않고 인코딩이 필요합니다. base64 인코딩을 수행하는 명령어는 다음과 같습니다.
echo -n 'mysql' | base64
echo -n 'root' | base64
echo -n 'paswrd' | base64
명령형
명령형 방식을 사용하면 아래와 같이 app-secret 이름을 가진 secret을 생성할 수 있습니다.
- 명령형은 Kubernetes가 자동으로 base64 인코딩
kubectl create secret generic app-secret --from-literal=DB_Host=mysql --from-literal=DB_User=root --from-literal=DB_Password=paswrd
만약 파일이라면 다음과 같이 작성할 수 있습니다.
kubectl create secret generic app-secret --from-file=app_secret.properties
선언형
Secret의 모든 값은 base64로 인코딩되어야 합니다. 평문으로 저장하면 보안에 취약할 수 있습니다.
- 선언형은 직접 base64 인코딩해서 넣어야합니다.
apiVersion: v1
kind: Secret
metadata:
name: app-secret
data:
DB_Host: bXlzcWw=
DB_User: cm9vdA==
DB_Password: cGFzd3Jk
Viewing, Decoding Secret
특정 Secret의 실제 값을 공개하지 않고 자세한 정보를 확인할 수 있습니다.
kubectl describe secrets app-secret
Name: app-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
DB_Host: 10 bytes
DB_User: 4 bytes
DB_Password: 6 bytes
실제 인코딩 된 값을 확인하기 위해선 -o yaml 로 출력을 지정합니다.
kubectl get secret app-secret -o yaml
해당 값을 확인하여 base64 디코딩하면 됩니다.
echo -n 'bXlzcWw=' | base64 --decode
echo -n 'cm9vdA==' | base64 --decode
Secrets를 Pod에 주입하기
Secret을 생성한 후 환경 변수 와 마운트된 볼륨 을 기반으로 secret을 주입할 수 있습니다.
- 환경 변수로 주입
apiVersion: v1
kind: Pod
metadata:
name: simple-webapp-color
labels:
name: simple-webapp-color
spec:
containers:
- name: simple-webapp-color
image: simple-webapp-color
ports:
- containerPort: 8080
envFrom:
- secretRef:
name: app-secret
- 볼륨으로 주입
volumes:
- name: app-secret-volume
secret:
secretName: app-secret
마운트하고 /opt/app-secret-volumes 을 확인합니다.
- 각 키가 개별 파일로 생성됩니다
ls /opt/app-secret-volumes
cat /opt/app-secret-volumes/DB_Password
# Output: paswrd
secret 보안 관리 고려사항
- kubernetes Secrets은 암호화되지 않고 인코딩되어 있습니다. → 누구나 디코딩해서 원본 값 확인 가능
- Github 같은 곳에 절대 Secret 선언 파일을 커밋하지 마세요.
- etcd에 저장된 secret은 기본적으로 암호화되지 않습니다. 저장 시 암호화 활성화를 고려하세요.
저장 시 암호화 활성화
Secret이 etcd에 저장될 때 암호화되며 읽을 때 복호화됩니다.
- identity: 암호화 안 함 (평문 그대로)
- aesgcm: AES-GCM 알고리즘 사용
- aescbc: AES-CBC 알고리즘 사용
- secretbox: XSalsa20 + Poly1305 알고리즘 사용
암호화를 제대로 활성화하기 위해선 Provider 설정 위치에 주의합니다.(암호화는 맨 위, 복호화는 전부 사용)
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- identity: {}
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHN1bXdlcnZlZQ==
- name: key2
secret: dGhpcPyBcyBwYXNzd29yZA==
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHN1bXdlcnZlZQ==
- name: key2
secret: dGhpcPyBcyBwYXNzd29yZA==
- secretbox:
keys:
- name: key1
secret: YWjZGVmZ2hpamtsbW5vcHyc3R1nd4eXokMjY=
kube-apiserver 상에서 암호화 설정 파일과 볼륨, 볼륨 마운트 설정이 필요합니다.
- EncryptionConfiguration 파일 만들기 (
/etc/kubernetes/enc/enc.yaml) - kube-apiserver Pod 설정 수정:
-encryption-provider-config옵션 추가- volumes 추가 (호스트 경로 연결)
- volumeMounts 추가 (컨테이너에 마운트)
- kube-apiserver 재시작 (설정 변경 적용)
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.10.30.4:6443
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
# ... other command arguments ...
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml # Add this line
volumeMounts:
# ... other mounts ...
- name: enc
mountPath: /etc/kubernetes/enc
readOnly: true # Add this line
volumes:
# ... other volumes ...
- name: enc
hostPath:
path: /etc/kubernetes/enc
type: DirectoryOrCreate # Add this line
보안을 더 강화하기 위해선 AWS, Azure, GCP, Vault Provider의 시크릿을 사용하는 것도 좋습니다.
고급 보안 제어와 중요 설정 파일을 분리하여 고도화된 보안 구성이 가능합니다.
- Secret 버전 관리 (Vault 등)
- 자동 로테이션 (주기적으로 비밀번호 변경)
- 감사 로그 (누가 언제 접근했는지)
- 클러스터와 분리된 권한 체계