암호, 토큰 같은 소량의 기밀 데이터를 저장하는데 쓰는 오브젝트. Secret에 저장된 값은 Pod이 사용한다.

4 minute read

암호, 토큰 같은 소량의 기밀 데이터를 저장하는데 쓰는 오브젝트. Secret에 저장된 값은 Pod이 사용한다.

ConfigMap과 유사하지만, 기밀 데이터를 보관하기 위한 특별한 오브젝트이다.

단, 주의할 것은 Secret 오브젝트가 완벽한 데이터 암호화를 제공하는 것은 아니다라는 점이다. 자세한 내용은 아래 내용 참조.

Secret 생성

yaml 파일로 생성

ConfigMap과 달리 data 항목의 값이 base64 형식으로 인코딩 되어 있어야 한다. base64 인코딩은 아래의 방법을 사용하면 된다.

# encoding
$ echo "root" | base64
cm9vdAo=

# decoding
$ echo "cm9vdAo=" | base64 -d
root


apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque # 임의의 사용자 정의 데이터
data:
  username: cm9vdAo= # (base64) root
  password: cGFzc3dvcmQxMjMK # (base64) password123

type: Opaque로 설정되어 있는데, Secret 오브젝트의 디폴트 타입이다. 직접 정의해서 쓰는 Secret을 Opaque(불분명한) 타입으로 분류한다.

kubectl로 생성

$ kubectl create secret generic [secret-name] \
  --from-literal=username=root \
  --from-literal=password=password123

kubectl로 생성하면, 평문값을 넣어도 알아서 base64로 인코딩 해준다. Secret 오브젝트는 kubectl로 생성하는게 더 편할지도!


Pod의 Secret 사용

환경 변수로 받기

...
spec:
  containers:
    - name: ...
      image: ...
      envFrom:
        - secretRef:
            name: my-secret

볼륨 마운트를 통해 받기

ConfigMap의 방식과 거의 비슷하므로 패-스!



Secret의 취약점

Secret은 ConfigMap 때와 달리 데이터를 base64 인코딩해서 보관한다는 것 말고는 아무런 차이가 없다. 실제로 마음만 먹으면 원래의 평문값을 알 수 있다.

$ k get secrets db-secret -o json
{
    "apiVersion": "v1",
    "data": {
        "DB_Host": "c3FsMDE=",
        "DB_Password": "cGFzc3dvcmQxMjM=",
        "DB_User": "cm9vdA=="
    },
    "kind": "Secret",
    "metadata": {
        ...
    },
    "type": "Opaque"
}

$ echo "c3FsMDE=" | base64 -d
root

k get secrets -o json으로 확인한 base64 값을 다시 디코딩 하기만 하면 되기 때문이다.


ConfigMap 오브젝트와 Secret 오브젝트는 etcd에 저장된다. 평소엔 api-server를 통해 etcd에 접속해 데이터를 가져오는 방식으로 key-value 값을 읽어오는 것이다.

그런데 Secret은 etcd에 암호화 되지 않은 상태로 저장된다. 즉, 누군가 etcd에 접근하는 권한을 가지고 있다면, Secret의 평문값을 알 수 있다.


어러나 저러나 Secret 오브젝트는 값을 암호화 하는데는 조금 부족한 녀석이다. 그래서 “저장된 데이터 암호화(Encryption at Rest)” 기능을 활성화 해서, Secret의 취약점을 보완해야 한다.

저장된 데이터 암호화(Encryption at Rest)

etcd에 저장된, “at rest” 상태의 Secret 데이터를 암호화 하는 방법이다. Udemy 강좌에서는 etcdctl을 통해 etcd 클러스터에 접속해서 실제 평문값을 확인하는 것도 보여줬지만, 여기서는 패-스 하겠다!

원리는 간단하다. ConfigMap/Secret 오브젝트의 값을 etcd에 저장할 때 암호화해서 저장할 것이다. 단, api-server는 Encryption Provider를 통해 암호화된 데이터를 해독할 수 있다.

Encryption Configuration 생성

etcd 암호화 방식을 정의하자.

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
    providers:
      - identity: {}
      - aescbc: # 암호화 알고리즘
          keys:
            - name: key1
              secret: cGFzc293cmQxMjMK # (base64) password123

api-server에 Encryption Provider 설정

api-server의 .yaml 파일을 열어서 Encryption Provider를 설정해준다.

...
spec:
  containers:
    - command:
        - kube-apiserver
        ...
        - --encryption-provider-config=/etc/kubernetes/enc/enc.yaml
        ...
      volumeMounts:
        - name: enc
          mountPath: /etc/kubernetes/enc
          readonly: true
        ...
  volumes:
    - name: enc
      hostPath:
        path: /etc/kubernetes/enc
        type: DirectoryOrCreate

kube-apiserver 커맨드의 인자로 --encryption-provider-config도 넘겨줘야 하고, 볼륨과 볼륨 마운트로 설정해줘야 한다.

api-server의 .yaml 파일 수정해주면, api-server의 Static Pod이 재생성되어 적용된다.

Encryption 체크

잘 되었겠지!

단, 기존에 이미 있던 ConfigMap/Secret이 암호화되는 것은 아니다. api-server 재생성 후에 만들어진 ConfigMap/Secret만 암호화 되어 etcd에 저장되는 것!


References