Kubernetes

ETCD Encryption

Operation CWAL 2021. 4. 21. 23:31

Kubernetes Secret

일반적으로 K8s 환경에서 Password, Access Token와 같이 보안에 민감한 데이터를 Secret 리소스로 관리한다. 다음은 간단한 Secret을 생성하는 예시이다. 

kubectl create secret generic secret1 --from-literal user=admin
kubectl create secret generic secret2 --from-literal pass=12345678
  • secret1: user=admin
  • secret2: pass=12345678

그리고 다음과 같이 위 Secret 2개를 포함하는 Pod을 정의한다.

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: main
    env:
    - name: PASSWORD
      valueFrom:
        secretKeyRef:
          key: pass
          name: secret2
    image: nginx
    volumeMounts:
    - mountPath: /etc/secret1
      name: secret1
      readOnly: true
  volumes:
  - name: secret1
    secret:
      secretName: secret1

 

Secret이 정상적으로 삽입되었는지 Pod을 확인해보자.

 

위에서 알 수 있듯이, K8s에서 Secret은 base64로 인코딩 될 뿐 전혀 암호화되지 않는 점에서 보안에 취약하다. 'in transit' 상태에선 HTTPS를 통해 암호화되므로 다행이지만, etcd에 저장된 'at rest' 상태에선 특히나 그렇다.

 

해킹 - ETCD에서 Secret값 가져오기

Secret 데이터는 다른 K8s 리소스와 마찬가지로, 평소엔 etcd에 저장되어 있으며 API 서버와 kubelet을 거쳐 컨테이너로 전달된다.

보통은 권한을 가진 Client 또는 Component가 API 서버에 요청하여 Secret을 가져오는 상황만을 가정했지만, 혹시 etcd에 직접 접근해서 Secret 값을 조회할 수 있지 않을까?  

실제로 다음 명령어를 사용하여 etcd에 저장된 Secret을 바로 확인할 수 있다.

ETCDCTL_API=3 etcdctl --cert /etc/kubernetes/pki/apiserver-etcd-client.crt --key /etc/kubernetes/pki/apiserver-etcd-client.key --cacert /etc/kubernetes/pki/etcd/ca.crt get /registry/secrets/default/secret1

Binary 형식으로 저장되어 있기 때문에 일부 텍스트가 깨져서 보이지만, user와 password 값이 별도의 인코딩이나 암호화되지 않은 상태로 존재하는 것을 알 수 있다. 만약 해커가 Master 서버에서 인증서를 탈취할 수 있다면 클러스터에 존재하는 모든 Secret도 유출될 수 있다는 얘기다.

ETCD 암호화

다행히도 Kubernetes는 API 서버를 통해 ETCD에 저장된 Secret 데이터를 암호화할 수 있는 기능을 제공한다. 해당 기능을 사용하기 위해선 Master Node에 다음 두가지 설정이 필요하다.

  • EncryptionConfiguration Manifest 파일 추가
  • kube-apiserver의 '--encryption-provider-config' 옵션 활성화

 

이제 위 과정을 차례대로 진행해보자.

 

1. EncryptionConfiguration 정의

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - aescbc:
        keys:
        - name: key1
          secret: <BASE 64 ENCODED SECRET>
    - identity: {}
  • provider에 어떤 알고리즘(ex: aescbc, aesgcm, etc)과 key를 사용해서 암호화할지 정의한다.  provider의 순서가 매우 중요한데, 암호화시 항상 처음 위치한 provider를 사용하기 때문이다. 만약 identity를 첫번째 provider로 정의시, Plain Text로 저장되므로 주의해야 한다.
  • 'identity: {}'를 추가하지 않으면, Plain Text로 저장된 기존 Secret 값을 읽을 수 없는 문제가 발생하므로 반드시 필요하다.
  • provider의 secret 필드엔 Encryption Key로 사용할 텍스트의 base64 인코딩 값을 입력한다. 참고로 지정된 길이(16, 24, 32)의 텍스트만 사용가능하다. 

다음과 같은 텍스트를 인코딩하여 암호화에 사용할 키값을 생성하고, '/etc/kubernetes/etcd' 경로에 ec1.yaml이라는 파일을 추가하였다(해당 경로는 따로 생성)

echo -n password12345678 | base64

/etc/kubernetes/etcd/ec1.yaml

 

2. API 서버 설정

'/etc/kubernetes/manifests/kube-apiserver.yaml'을 다음과 같이 수정한다. 

--encryption-provider-config 옵션 추가
'etcd' 볼륨 추가
volumeMounts 추가

해당 파일을 수정하면 kubelet이 이를 감지하여 자동으로 Static Pod을 재생성되며, 위 내용이 반영된 kube-apiserver를 사용할 수 있다.

 

 

이제 모든 설정이 완료되었으므로, Secret의 암호화되어 저장되는지 확인해볼 시간이다. 다음과 같이 간단한 Secret을 생성해보았다.

kubectl create secret generic mysecret --from-literal hello=world

이번엔 etcd에 저장된 secret값을 조회한 결과이다.

기대했던대로 etcd에 암호화되어 저장된 것을 확인할 수 있다. 이쯤되니 기존 Secret 데이터는 어떤지 궁금하므로, 미리 생성했던 secret1을 조회해보았다.

 

기존 Secret 데이터는 암호화되지 않은 상태로 저장되었기 때문에, 아직 Plain Text가 그대로 노출되는 것을 알 수 있다.하지만 다음 명령어를 통해 현재 클러스터에 존재하는 모든 Secret을 쉽게 암호화할 수 있다.

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

다시 etcd를 확인해보니, 이번엔 정상적으로 암호화된 것을 알 수 있다.

물론 etcd에 저장될 때만 암호화될 뿐, API 서버를 통해 가져오는 경우 원래 데이터 그대로 decryption이 이루어진다.

 

 

Production Level의 Secret 관리

사실 이 방법은 해커가 etcd에만 접근 가능한 경우에 유효하다는 점에서 어느 정도 한계가 있다. 만약 API 서버와 같은 Control Plane이 해킹된다면 이 방식은 아무 의미가 없다. 따라서 Production Level에선 EncryptionConfiguration 방식에만 의존할 것이 아니라, KMS 플러그인 또는 HashCorp Vault와 같은 3rd Party Tool의 도입을 고려해야 한다. 나중에 기회가 된다면 Vault에 대해서 자세히 설명하고자 한다. 

 

참고

 

Encrypting Secret Data at Rest

This page shows how to enable and configure encryption of secret data at rest. Before you begin You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. If you do not already have a clust

kubernetes.io

 

'Kubernetes' 카테고리의 다른 글

ETCD Backup&Restore  (0) 2021.05.29
Open Policy Agent  (0) 2021.05.05
Imperative Vs. Declarative  (0) 2021.04.08
Container Runtime과 Docker  (0) 2021.03.09
Calico IPAM  (0) 2021.01.31