[CKS] Minimize Microservice Vulnerabilities (5) - Manage Kubernetes secrets

Kubernetes Secrets 관리 방법을 배웁니다. ConfigMap과의 차이, base64 인코딩, Pod 주입 방식, etcd 저장 시 암호화 활성화까지 보안 관리의 전반을 다룹니다.

[CKS] Minimize Microservice Vulnerabilities (5) - Manage Kubernetes secrets
Photo by Stefan Steinbauer / Unsplash

개요

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에서 ConfigMapSecrets 의 차이를 보면 다음과 같습니다.

  • ConfigMap: 평문 저장, 민감하지 않은 설정용
  • Secret: base64 인코딩 저장 (보안 목적 아님, 데이터 호환성 목적), RBAC로 접근 제어 분리 가능

Kubernetes Secrets 사용 방법

비밀 키를 생성하는 방법은 두 가지 단계로 이루어집니다

  1. Secrets 생성
  2. 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 등)
  • 자동 로테이션 (주기적으로 비밀번호 변경)
  • 감사 로그 (누가 언제 접근했는지)
  • 클러스터와 분리된 권한 체계