TOPIC/Infra

Encrypting Secret Data at Rest in ETCD

H-Y-E-N 2024. 3. 12. 13:38

안녕하세요. HYEN입니다.

이번 글은 Encrypting Secret Data at Rest in ETCD에 대한 내용인데요. 

개인적으로 한번은 꼭 해보고 싶었던 테스트라 VM으로 Kubernetes Cluster를 구성한 김에 진행해 보았습니다. 😉


Contents

     

    1. ETCD란?

    ETCD Encryption at Rest 테스트를 진행하기에 앞서 먼저 ETCD가 무엇인지 간단하게 알아보도록 하겠습니다.

     

    ETCD란?

    ETCD는 Kubernetes Cluster의 모든 데이터를 담는 Kubernetes의 Key-Value 형태의 저장소입니다.

     

    우리가 kubectl 명령어를 입력해서 받는 여러 Kubernetes Objects에 대한 정보는 모두 ETCD에 저장된 데이터에서 가져오는 정보죠.

     

    따라서 Secret 데이터도 ETCD에 저장되게 됩니다.

    그렇다면 Secret 데이터가 ETCD에 저장될 때 암호화되어 저장이 될까요?

     

    답은 No입니다.

     

    기본적으로 ETCD에 저장되는 Secret 데이터는 평문으로 저장이 됩니다.

    그러나 Kubernetes는 ETCD에 저장되는 Secret 데이터를 암호화하는 방법을 제공하고 있습니다.

     

    그렇다면 ETCD를 암호화하는 이유는 뭘까요?

    그 이유는 Control Plane에 root 권한을 가진 사람은 ETCD에 접근해서 Secret 데이터를 볼 수 있기 때문입니다.

     

    이것이 바로 이번 글에서 다룰 Encrypting Secret Data at Rest in ETCD 입니다.

     

    그 전에 한 가지 설명하고자 하는 것은 ETCD pod의 형태입니다.

     

    ETCD뿐만 아니라 Control Plane의 Component는 사용자가 배포하는 Pod와는 조금 다른 성질을 가지고 있습니다.

    바로, ETCD나, API Server, Scheduler와 같은 Component는 Static Pod의 형태로 kube-system Namespace에 배포된 다는 것인데요.

     

    Static Pod는 또 무엇일까요?

     

    일반적으로 kubectl create 또는 kubectl apply 명령어로 pod를 생성할 때에는 kube-apiserver가 그 요청을 받아서 kubelet에게 알려주고 그 다음 각 Node에 있는 kubelet이 이를 처리하게 됩니다.

     

    그러나 Control Plane의 /etc/kubernetes/manifests/ 디렉토리(default 경로) 하위에 있는 yaml file을 기반으로 kubelet은 자동으로 생성하고 관리하는 pod들이 있는데요.

     

    이것이 바로 Static Pod입니다.

     

    Control Plane의 Component를 Static Pod로 생성하면 kubelet이 해당 디렉토리를 지속적으로 관리하고 감시하여서 manifest file에 정의된 대로 각 pod의 상태를 유지하고자 합니다.

     

    실제로 Control Plane 서버에서 ls /etc/kubernetes/manifests/ 명령어를 실행해 보면 하기 스크린샷과 같이 각 Control Plane Component에 대한 yaml file이 존재하는 것을 알 수 있습니다.

     

    이 외에도 Static Pod로 생성하고 싶은 pod의 manifest file을 해당 디렉토리에 위치시키면 kubelet이 자동으로 pod를 생성하고 관리하게 됩니다.

     

    이렇게 ETCD pod의 성질을 간단히 알아 보았는데요. 

     

    다시 본 주제로 돌아와서 ETCD에 저장된 Secret 값을 암호화하는 테스트를 진행해 보겠습니다.

     

    2. ETCDCTL 설치 

    Encrypting Secret Data at Rest in ETCD 테스트를 진행하기 전 현재 ETCD에 저장된 데이터가 실제 평문으로 저장되어 있는지를 확인해 봅니다.

    이전 글에서 kubeadm으로 Kubernetes Cluster를 생성하였기 때문에 ETCD를 위한 별도의 설치는 필요하지 않습니다.

     

    그러나 ETCD와 상호작용하기 위해서 etcdctl이라는 Command Line Tool을 설치해야 합니다.

     

    공식 문서 (https://etcd.io/docs/v3.4/install/)를 참고하여 etcdctl을 설치합니다.

     

    • ETCD repo를 다운로드하기 위해 git clone -b v3.4.28 https://github.com/etcd-io/etcd.git 명령어를 입력합니다.

     

    • cd etcd/ 명령어로 디렉토리를 이동한 다음 ./build 명령어를 통해 build script를 실행합니다.
      만약 ./build 명령어 실행 시 하기와 같은 에러가 발생한다면 Go를 설치해 주어야 합니다.

     

      • Go 설치 (공식 문서 링크 : https://go.dev/doc/install)
        • 이전에 Go를 설치했을 수도 있기 때문에 해당 디렉토리를 삭제하고 새로 설치합니다. 
          rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.1.linux-amd64.tar.gz

        • 상기 명령어가 제대로 작동하지 않을 경우에는  wget https://go.dev/dl/go1.22.1.linux-amd64.tar.gz 명령어를 입력한 후 tar Cxzvf /usr/local go1.22.1.linux-amd64.tar.gz를 실행하여 tar 파일을 /usr/local 디렉토리에 압축해제 합니다.

        • ls /usr/local 명령어를 입력하여 Go가 정상적으로 설치된 것을 확인합니다.
        • vi ~/.bashrc 명령어를 입력한 후 파일 가장 하단에 PATH=$PATH:/usr/local/go/bin를 추가하여 /usr/local/go/bin 디렉토리를 PATH에 영구히 추가합니다.
        • source ~/.bashrc 명령어를 입력하여 변경 사항을 반영합니다.

        • go version 명령어를 입력하여 설치를 다시 한번 확인합니다.

     

      • 다시 cd etcd && ./build 명령어를 수행하면 이전과는 다르게 제대로 설치가 되는 것을 확인할 수 있습니다.

     

    • vi ~/.bashrc 명령어를 입력한 후 앞서 추가했던 PATH 뒤에 :/root/etcd/bin를 입력하여 /root/etcd/bin 디렉토리를 PATH에 영구히 추가합니다. 

     

    • source ~/.bashrc 명령어를 입력하여 변경 사항을 반영합니다.

     

    • etcd --version 명령어를 입력하여 etcdctl이 제대로 설치되었는지 확인합니다.

     

    ETCDCTL도 설치가 완료되었으니 이제 정말로 Encrypting Secret Data at Rest in ETCD를 테스트해 보겠습니다. 

     

    3. ETCD에 저장된 Secret 값 확인

    ETCD에 저장되는 Secret 값은 기본적으로 평문으로 저장된다고 앞서 말씀 드렸는데요.

    실제로 그런지 알아보도록 하겠습니다.

     

    먼저 테스트용 Secret을 생성하겠습니다.

     

    • kubectl create secret generic secret-hyein --from-literal key=hyeintest 명령어를 입력하여 secret을 생성합니다.
    • kubectl get secret secret-hyein -o yaml 명령어를 입력해서 확인해 보면 base64로 인코딩된 형태로 데이터가 저장되어 있는 것을 알 수 있습니다.

     

    • echo "aHllaW50ZXN0" | base64 -d 명령어를 입력하여 디코딩하면 쉽게 value 값을 확인할 수 있습니다. 

     

    그렇다면 ETCD에 저장된 Secret도 똑같은지 확인해 보겠습니다.

     

    ETCD Server에 연결하기 위해서는 --cacert, --cert, --key 값이 필요한데요.

    이런 값은 어디서 확인해야 할까요?

     

    앞서 설명 드렸던 Static Pod들이 저장된 디렉토리의 etcd.yaml 파일을 확인해 보면 알 수 있습니다.

     

    • cat /etc/kubernetes/manifest/etcd.yaml 명령어를 입력하여 필요한 값들에 대한 정보를 확인합니다.

     

    • 하기와 같은 명령어를 입력해서 ETCD에 저장된 Secret 데이터를 확인합니다.
    ETCDCTL_API=3 etcdctl\
       --cacert=/etc/kubernetes/pki/etcd/ca.crt \
       --cert=/etc/kubernetes/pki/etcd/server.crt \
       --key=/etc/kubernetes/pki/etcd/server.key \
       get /registry/secrets/default/secret-hyein | hexdump -C

     

    • 스크린샷의 오른쪽 하단을 보면 key의 값이 hyeintest인 것을 볼 수 있습니다.

     

    kubectl get secret secret-hyein -o yaml 명령어를 입력했을 때에는 적어도 base64로 인코딩된 형태로 존재했지만 ETCD에 저장된 데이터는 평문 그대로 저장된 것을 확인할 수 있습니다.

     

    이 경우, 만약 누군가가 Control Plane 서버에 접근해서 --cacert, --cert, --key 값만 알 수 있다면 Cluster에 존재하는 Secret 데이터가 유출 될 수 있는 것입니다.

     

    이를 방지하기 위해, 지금부터 ETCD에 저장되는 데이터에 대한 암호화를 구성해 보도록 하겠습니다. 

     

    4. Encrypting Secret Data at Rest in ETCD

    (참고 링크 : https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)

     

    ETCD Encryption을 진행하기 위해서는 두 가지 과정이 필요합니다.

    1️⃣ EncryptionConfiguration Object 생성
    2️⃣ kube-apiserver 설정 변경

     

    1번부터 진행해 보도록 하겠습니다.

     

    4.1 EncryptionConfiguration Object 생성

    EncryptionConfiguration Object는 Kubernetes Cluster의 구성 중 하나로 ETCD의 데이터를 암호화하는 데 사용됩니다.

     

    EncryptionConfiguration를 설정하면 EncryptionConfiguration manifest 파일에 지정한 리소스(ex. secrets, configmaps)의 데이터는 모두 암호화됩니다.

     

    암호화를 위해서는 보안 키가 필요하며 이 키는 EncryptionConfiguration의 manifest 파일에 지정됩니다.

    (key 값은 base64로 인코딩하여 기재해야 합니다.)

     

    manifest file을 생성하기에 앞서 먼저 key로 사용할 값을 인코딩 해 보겠습니다. 

     

    • 32바이트의 랜덤한 키를 생성하기 위해 head -c 32 /dev/urandom | base64 명령어를 입력합니다. (출력 내용 저장 필요)
      • AES-CBC 암호화를 사용하는 키는 128비트, 192비트, 256비트의 길이를 가져야 합니다. (16자, 24자, 32자)
        그렇지 않을 경우, 에러가 발생할 수 있습니다.

     

    • /etc/kubernetes/enc/ 디렉토리에 enc.yaml이라는 이름으로 EncryptionConfiguration manifest 파일을 생성합니다. 
      해당 디렉토리가 없을 경우 mkdir /etc/kubernetes/enc/ 명령어로 디렉토리를 먼저 생성합니다. 
      (폴더는 어디든 상관 없습니다.)
    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
          - secrets
        providers:
          - aescbc:
              keys:
                - name: key1
                  secret: 3WJOfWFj2SNy2Jj2+EUVUSzEMoRZh13hQ5t4ZWfUhcE=
          - identity: {}
    • manifest file 설명
      • resources field에는 어떤 종류의 리소스(Object)를 암호화할 것인지를 지정합니다. 
        본 테스트에서는 Secrets만 지정해 주었습니다.

      • providers field에는 어떤 암호화 provider를 사용할 것인지를 지정합니다.
        provider field는 첫 번째로 정의된 provider가 우선이 되기 때문에 순서가 중요합니다. 
        ex. identity provider가 가장 첫 번째에 위치하면 모든 Secret 데이터는 평문으로 저장됩니다.

        [암호화 방식]
        • aescbc
          • AES-CBC 암호화를 사용하는 프로바이더로 keys field에 지정한 key1이라는 이름의 키를 사용하여 암호화를 수행합니다.

          • secret field에는 실제 사용될 보안 키가 base64로 인코딩되어 있습니다.

        • identity
          • 별도의 암호화를 수행하지 않은 채로 데이터를 평문으로 제공하는 provider로 default 값입니다.

     

    • 파일 작성이 완료되면 저장한 후 cat /etc/kubernetes/enc/enc.yaml 명령어를 입력하여 파일이 제대로 작성되었는지 확인합니다.

     

    4.2  kube-apiserver 설정 변경

    kube-apiserver도 Static Pod이기 때문에 kube-apiserver 관련 manifest 파일은 Control Plane의 /etc/kubernetes/manifest/ 디렉토리에 존재합니다.

     

    • vi /etc/kubernetes/manifests/kube-apiserver.yaml 명령어를 입력하여 해당 파일을 편집합니다. 
      (문제가 발생했을 때 rollback을 빠르게 하기 위해 backup 파일을 생성한 후 해당 파일을 변경하는 것이 권장됩니다.)

     

    • spec.containers.command 하위의 flag들이 모여있는 곳에 --encryption-provider-config flag를 추가합니다.
      경로는 4.1에서 생성한 manifest file이 저장된 경로(ex. /etc/kubernetes/enc/enc.yaml)를 입력해 주면 됩니다.

     

    • volumes과 volumeMounts field에도 관련된 내용을 추가해 줍니다. (실제 존재하는 경로를 입력해 주어야 합니다.)
      • volumeMounts field

        • volumes field

     

    • 변경 사항을 한번 더 확인하고 저장하면 kubelet이 변경 사항을 자동으로 감지하여 kube-apiserver를 자동으로 재생성합니다. (재생성에는 시간이 소요되기 때문에 잠시 기다려야 합니다.)

     

    • kubectl get po -n kube-system 명령어를 입력하였을 때 하기와 같이 pod가 정상적으로 조회가 되어야 합니다.

     

    4.3 Secret 데이터 암호화 확인 

    모든 설정이 완료되었으므로 실제로 변경 사항이 적용되었는지 테스트 해 보겠습니다.

     

    먼저 새로운 Secret을 생성하여 Secret 데이터가 암호화되어 저장되는지 확인해 보겠습니다.

     

    • kubectl create secret generic secret-hyein-new --from-literal key=hyeintest-new 명령어를 입력하여 secret을 생성합니다.

     

    • 그 다음 하기 명령어를 입력하여 ETCD에 저장된 Secret 데이터가 암호화 되었는지 확인합니다.
    ETCDCTL_API=3 etcdctl\
       --cacert=/etc/kubernetes/pki/etcd/ca.crt \
       --cert=/etc/kubernetes/pki/etcd/server.crt \
       --key=/etc/kubernetes/pki/etcd/server.key \
       get /registry/secrets/default/secret-hyein-new | hexdump -C

     

    • 하기 스크린샷의 첫 번째 빨간 박스를 보면 provider가 Secret 데이터를 암호화했음을 보여주는 k8s:enc:aescbc:v1: 이라는 문구가 Secret 이름 뒤에 붙어 있음을 알 수 있습니다.
      또한 두 번째 빨간 박스를 보면 이전과는 다르게 Secret 데이터가 평문으로 보이지 않는 것을 알 수 있습니다.

     

    그렇다면 기존 Secret의 데이터도 암호화 되었을까요?

     

    기존 Secret의 데이터를 조회해 보면 하기와 같이 아직까지 암호화가 되어 있지 않은 것을 확인할 수 있습니다.

     

    따라서, kubectl get secrets -A -o json | kubectl replace -f - 명령어를 통해 기존의 모든 Secret 데이터를 암호화하여 ETCD에 저장하여야 합니다. 

     

    명령어를 적용하면 하기 스크린샷과 같이 Cluster 내의 모든 Secret이 업데이트되는 것을 확인할 수 있습니다.

     

    다시 한번 기존의 Secret의 데이터를 조회해 보겠습니다.

     

    아까와는 다르게 Secret 데이터가 평문이 아닌 암호화된 형태로 보이는 것을 확인할 수 있습니다.

     

    이렇게 ETCD에 저장되는 데이터에 대한 암호화를 진행해 보았는데요.

     

    이제부터 ETCD에 데이터를 저장할 때에는 암호화가 적용되어서 평문이 아닌 암호화된 형태로 보여지겠 지만

    여전히 API Server를 통해서 Secret 값을 가져올 때에는,

    즉, kubectl get secrets secret-hyein -o yaml 명령어로 Secret에 저장된 데이터를 조회할 때에는

    그대로 Secret 데이터가 노출된다는 문제가 남아 있습니다. 

     

    따라서 이 부분까지도 암호화를 하고자 할 경우에는 KMS 플러그인 등을 사용해야 합니다.

     


    이렇게 해서 ETCD에 저장되는 Confidential한 데이터를 암호화하여 저장하는 방법에 대해 알아보았습니다. 

    완전한 암호화를 위해서는 여전히 고려해야 할 부분이 남아 있지만 CKA를 준비할 때부터 한번쯤은 해보고 싶은 테스트를 무사히 끝냈다는 점에서 굉장히 만족스러운 글이었습니다. 🎉🎉

     

    다음에도 좀 더 유익하고 재밌는 글로 돌아오겠습니다! 

    728x90
    320x100
    SMALL