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

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을 생성할 수 있습니다.

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로 인코딩되어야 합니다. 평문으로 저장하면 보안에 취약할 수 있습니다.

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 보안 관리 고려사항

저장 시 암호화 활성화

Secret이 etcd에 저장될 때 암호화되며 읽을 때 복호화됩니다.

암호화를 제대로 활성화하기 위해선 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 상에서 암호화 설정 파일과 볼륨, 볼륨 마운트 설정이 필요합니다.

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의 시크릿을 사용하는 것도 좋습니다.

고급 보안 제어와 중요 설정 파일을 분리하여 고도화된 보안 구성이 가능합니다.