kubestronautへの道 ~CKS編 その23 killer coda「Secret ETCD Encryption」~

tech article

今日覚えること

etcd

Kubernetesの全てのクラスター情報の保存場所として利用されている、一貫性、高可用性を持ったキーバリューストアのこと

EncryptionConfiguration

APIサーバーが永続的なデータを保存するときに、etcd に保存されるデータを暗号化するための設定ファイル

Secret ETCD Encryption

Enable ETCD Encryption

  1. Create an EncryptionConfiguration file at /etc/kubernetes/etcd/ec.yaml and make ETCD use it.
  2. One provider should be of type aesgcm with password this-is-very-sec . All new secrets should be encrypted using this one.
  3. One provider should be the identity one to still be able to read existing unencrypted secrets.

いろいろ書いていますが、etcdで暗号化できるようにせよ、と言っています。

etcdとは、 一貫性、高可用性を持ったキーバリューストアで、Kubernetesの全てのクラスター情報の保存場所として利用されています。
各ワーカーノードに対して、etcdに記載されている状態に一致するようにkubeletへ指令が出され、その指令をもとに各ワーカーノードにリソースが作成されます。
(このあたりかなりややこしいので細かく見たら多分間違ってます。なんとなくそういう感じなんだな、ぐらいに思っていてください。)

そして、kubernetesクラスターに保管しているデータを暗号化するにはEncryptionConfigurationというリソースを利用します。

今回は、EncryptionConfigurationリソースを作成して、etcdで保存するデータを暗号化していきます。
EncryptionConfigurationについては以下が参考になります。

Encrypting Confidential Data at Rest
All of the APIs in Kubernetes that let you write persistent API resource data support at-rest encryption. For example, you can enable at-rest encryption for Sec...

ポイントを列挙します

  • 各 resources 配列アイテムが独立した固有の設定である
  • resources.resources フィールドは、暗号化の対象となるkubernetesリソースを指定している(Secrets、ConfigMapsなど)
  • providers 配列は、指定されたAPI(リソース)に対して使用可能な暗号化プロバイダーの順序付きリストである
  • resources[*].providers.<provider name>.key 配下の配列に、使用する鍵の名前&base64でエンコードした鍵を記載する

こんなところでしょうか。

続いて、マニフェストファイルのサンプルです。

---
#
# CAUTION: this is an example configuration.
#          Do not use this for your own cluster!
#
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
      - pandas.awesome.bears.example # a custom resource API
    providers:
      # This configuration does not provide data confidentiality. The first
      # configured provider is specifying the "identity" mechanism, which
      # stores resources as plain text.
      #
      - identity: {} # plain text, in other words NO encryption
      - aesgcm:
          keys:
            - name: key1
              secret: c2VjcmV0IGlzIHNlY3VyZQ==
            - name: key2
              secret: dGhpcyBpcyBwYXNzd29yZA==
      - aescbc:
          keys:
            - name: key1
              secret: c2VjcmV0IGlzIHNlY3VyZQ==
            - name: key2
              secret: dGhpcyBpcyBwYXNzd29yZA==
      - secretbox:
          keys:
            - name: key1
              secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
  - resources:
      - events
    providers:
      - identity: {} # do not encrypt Events even though *.* is specified below
  - resources:
      - '*.apps' # wildcard match requires Kubernetes 1.27 or later
    providers:
      - aescbc:
          keys:
          - name: key2
            secret: c2VjcmV0IGlzIHNlY3VyZSwgb3IgaXMgaXQ/Cg==
  - resources:
      - '*.*' # wildcard match requires Kubernetes 1.27 or later
    providers:
      - aescbc:
          keys:
          - name: key3
            secret: c2VjcmV0IGlzIHNlY3VyZSwgSSB0aGluaw==

EncryptionConfigurationについてなんとなく理解したところで、構築作業を進めていきましょう。

まず、EncryptionConfigurationのマニフェストファイルを格納するディレクトリを作成します。

controlplane $ mkdir -p /etc/kubernetes/etcd

次に、指定されたパスワードをbase64で暗号化します。
このとき、改行コードを含めず暗号化できるように -n オプションをつけるようにします。
このオプションを忘れると出力結果も変わってしまうので要注意です。

controlplane $ echo -n this-is-very-sec | base64
dGhpcy1pcy12ZXJ5LXNlYw==

#改行コード無しの場合
controlplane $ echo this-is-very-sec | base64
dGhpcy1pcy12ZXJ5LXNlYwo=

続いて、EncryptionConfigurationのマニフェストファイルを/etc/kubernetes/etcd/ec.yamlに作成します。

先ほどのサンプルファイルからざっくりコピーします。

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
      - pandas.awesome.bears.example # a custom resource API
    providers:
      # This configuration does not provide data confidentiality. The first
      # configured provider is specifying the "identity" mechanism, which
      # stores resources as plain text.
      #
      - identity: {} # plain text, in other words NO encryption
      - aesgcm:
          keys:
            - name: key1
              secret: c2VjcmV0IGlzIHNlY3VyZQ==
            - name: key2
              secret: dGhpcyBpcyBwYXNzd29yZA==
      - aescbc:
          keys:
            - name: key1
              secret: c2VjcmV0IGlzIHNlY3VyZQ==
            - name: key2
              secret: dGhpcyBpcyBwYXNzd29yZA==
      - secretbox:
          keys:
            - name: key1
              secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=

ここから修正を加えていきます。

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - aesgcm:
        keys:
        - name: key1
          secret: dGhpcy1pcy12ZXJ5LXNlYw==
    - identity: {}

鍵のパスワードを先ほどbase64で暗号化したものに変えたくらいで、特に難しい操作はしていません。
identity: {}は既存の暗号化されていないシークレットを引き続き読み取るために必要なので記載しておきます。

仕上げに、これらのリソースを/etc/kubernetes/manifest/kube-apiserver.yamlで使用するように設定していきます。

catした内容は以下です。
追記個所は赤太文字にしています。

controlplane $ cat  /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 172.30.1.2:6443
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=172.30.1.2
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction
    - --enable-bootstrap-token-auth=true
    - --encryption-provider-config=/etc/kubernetes/etcd/ec.yaml
    - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
    - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
    - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
    - --etcd-servers=https://127.0.0.1:2379
    - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
    - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
    - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
    - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
    - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
    - --requestheader-allowed-names=front-proxy-client
    - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
    - --requestheader-extra-headers-prefix=X-Remote-Extra-
    - --requestheader-group-headers=X-Remote-Group
    - --requestheader-username-headers=X-Remote-User
    - --secure-port=6443
    - --service-account-issuer=https://kubernetes.default.svc.cluster.local
    - --service-account-key-file=/etc/kubernetes/pki/sa.pub
    - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
    - --service-cluster-ip-range=10.96.0.0/12
    - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
    image: registry.k8s.io/kube-apiserver:v1.30.0
    imagePullPolicy: IfNotPresent
    livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 172.30.1.2
        path: /livez
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    name: kube-apiserver
    readinessProbe:
      failureThreshold: 3
      httpGet:
        host: 172.30.1.2
        path: /readyz
        port: 6443
        scheme: HTTPS
      periodSeconds: 1
      timeoutSeconds: 15
    resources:
      requests:
        cpu: 50m
    startupProbe:
      failureThreshold: 24
      httpGet:
        host: 172.30.1.2
        path: /livez
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    volumeMounts:
    - mountPath: /etc/ssl/certs
      name: ca-certs
      readOnly: true
    - mountPath: /etc/ca-certificates
      name: etc-ca-certificates
      readOnly: true
    - mountPath: /etc/pki
      name: etc-pki
      readOnly: true
    - mountPath: /etc/kubernetes/pki
      name: k8s-certs
      readOnly: true
    - mountPath: /usr/local/share/ca-certificates
      name: usr-local-share-ca-certificates
      readOnly: true
    - mountPath: /usr/share/ca-certificates
      name: usr-share-ca-certificates
      readOnly: true
    - mountPath: /etc/kubernetes/etcd
      name: etcd
      readOnly: true
  hostNetwork: true
  priority: 2000001000
  priorityClassName: system-node-critical
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  volumes:
  - hostPath:
      path: /etc/ssl/certs
      type: DirectoryOrCreate
    name: ca-certs
  - hostPath:
      path: /etc/ca-certificates
      type: DirectoryOrCreate
    name: etc-ca-certificates
  - hostPath:
      path: /etc/pki
      type: DirectoryOrCreate
    name: etc-pki
  - hostPath:
      path: /etc/kubernetes/pki
      type: DirectoryOrCreate
    name: k8s-certs
  - hostPath:
      path: /usr/local/share/ca-certificates
      type: DirectoryOrCreate
    name: usr-local-share-ca-certificates
  - hostPath:
      path: /usr/share/ca-certificates
      type: DirectoryOrCreate
    name: usr-share-ca-certificates
  - hostPath:
      path: /etc/kubernetes/etcd
      type: DirectoryOrCreate
    name: etcd
status: {}
controlplane $ 

diffをとった内容が以下です。

controlplane $ cp -p /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.org
controlplane $ vi  /etc/kubernetes/manifests/kube-apiserver.yaml                                                  
controlplane $ diff /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.org
22d21
<     - --encryption-provider-config=/etc/kubernetes/etcd/ec.yaml
98,100d96
<     - mountPath: /etc/kubernetes/etcd
<       name: etcd
<       readOnly: true
132,135d127
<   - hostPath:
<       path: /etc/kubernetes/etcd
<       type: DirectoryOrCreate
<     name: etcd

詳しい内容は以下に記載されています。

Encrypting Confidential Data at Rest
All of the APIs in Kubernetes that let you write persistent API resource data support at-rest encryption. For example, you can enable at-rest encryption for Sec...
22d21
<     - --encryption-provider-config=/etc/kubernetes/etcd/ec.yaml

は、kube-apiserverが起動時に叩くコマンドを指定しています。
–encryption-provider-configには、EncryptionConfigurationファイルのパスを指定します。

98,100d96
<     - mountPath: /etc/kubernetes/etcd
<       name: etcd
<       readOnly: true
132,135d127
<   - hostPath:
<       path: /etc/kubernetes/etcd
<       type: DirectoryOrCreate
<     name: etcd

は、まず

132,135d127
<   - hostPath:
<       path: /etc/kubernetes/etcd
<       type: DirectoryOrCreate
<     name: etcd

ホストの /etc/kubernetes/etcd をボリュームとしてアタッチしています。
type: DirectoryOrCreateはホストに指定したディレクトリが存在しない場合は作成するといったオプションです。

ボリューム
コンテナ内のディスク上のファイルは一時的なものであり、コンテナ内で実行する場合、重要なアプリケーションでいくつかの問題が発生します。1つの問題は、コンテナがクラッシュしたときにファイルが失われることです。kubeletはコンテナを再起動しますが、クリーンな状態です。 2番目の問題は、Podで一緒に実行されているコンテナ...

そして、

98,100d96
<     - mountPath: /etc/kubernetes/etcd
<       name: etcd
<       readOnly: true

で先ほどアタッチしたボリュームをkube-apiserver用のpodにおける/etc/kubernetes/etcdにマウントするように設定してます。

これで作業は完了です。

Encrypt existing Secrets

  1. Encrypt all existing Secrets in Namespace one using the new provider
  2. Encrypt all existing Secrets in Namespace two using the new provider
  3. Encrypt all existing Secrets in Namespace three using the new provider

既存のSecretsを暗号化せよ、と言っています。

json形式で出力し、その内容に対して k replace を実行して暗号化していきます。
行末のハイフンは標準入力を表わしています。

kubectl -n one get secrets -o json | kubectl replace -f -
kubectl -n two get secrets -o json | kubectl replace -f -
kubectl -n three get secrets -o json | kubectl replace -f -

これでOKです。

タイトルとURLをコピーしました