[k8s] vaultに対してクラスタ内でHTTPS通信できるようにした話

## 抱えていた課題

普段運用しているクラスタでは、クラスタ内に Vault を設置して、その Vault で Secret を管理しています。（↓ 参考）

https://qiita.com/piny940/items/66214e8f7c8af18ba014

ExternalSecrets を使って、Vault にある Secret を Kubernetes 内で使えるようにしているのですが、Vault との通信には HTTP を使っていました。↓

```yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-secret-store
spec:
  provider:
    vault:
      server: "http://vault.vault.svc.cluster.local:8200"
```

このままだと、クラスタ内に悪意のあるワークロードが入り込んだ場合、Secret が盗まれる可能性があり危険です。  
そこで今回は、Vault との通信を HTTPS に変更し、さらに Vault に証明書を設定して、通信を暗号化することにしました。

## 実装

### 証明書の作成

基本的には、下記のドキュメントに従って設定を行いました。

https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-minikube-tls

https://developer.hashicorp.com/vault/docs/platform/k8s/helm/examples/standalone-tls

環境変数を設定

```bash
export VAULT_K8S_NAMESPACE="vault"
export VAULT_HELM_RELEASE_NAME="vault"
export VAULT_SERVICE_NAME="vault"
export K8S_CLUSTER_NAME="cluster.local"
export WORKDIR="/tmp/vault"
mkdir -p ${WORKDIR}
```

秘密鍵を生成

```bash
openssl genrsa -out ${WORKDIR}/vault.key 2048
```

CSR（Certificate Signing Request）を作成

```bash
cat > ${WORKDIR}/vault-csr.conf <<EOF
[req]
default_bits = 2048
prompt = no
encrypt_key = yes
default_md = sha256
distinguished_name = kubelet_serving
req_extensions = v3_req
[ kubelet_serving ]
O = system:nodes
CN = system:node:*.${VAULT_K8S_NAMESPACE}.svc.${K8S_CLUSTER_NAME}
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = *.${VAULT_SERVICE_NAME}
DNS.2 = *.${VAULT_SERVICE_NAME}.${VAULT_K8S_NAMESPACE}
DNS.3 = *.${VAULT_SERVICE_NAME}.${VAULT_K8S_NAMESPACE}.svc
DNS.4 = *.${VAULT_SERVICE_NAME}.${VAULT_K8S_NAMESPACE}.svc.cluster.local
DNS.5 = ${VAULT_SERVICE_NAME}
DNS.6 = ${VAULT_SERVICE_NAME}.${VAULT_K8S_NAMESPACE}
DNS.7 = ${VAULT_SERVICE_NAME}.${VAULT_K8S_NAMESPACE}.svc
DNS.8 = ${VAULT_SERVICE_NAME}.${VAULT_K8S_NAMESPACE}.svc.cluster.local
IP.1 = 127.0.0.1
EOF
openssl req -new -key ${WORKDIR}/vault.key -out ${WORKDIR}/vault.csr -config ${WORKDIR}/vault-csr.conf
```

証明書を発行

```bash
cat | kubectl create -f - <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
   name: vault.svc
spec:
   signerName: kubernetes.io/kubelet-serving
   expirationSeconds: 8640000
   request: $(cat ${WORKDIR}/vault.csr|base64|tr -d '\n')
   usages:
   - digital signature
   - key encipherment
   - server auth
EOF
```

Approve

```bash
kubectl certificate approve vault.svc
```

証明書から secret を作成

```bash
kubectl get csr vault.svc -o jsonpath='{.status.certificate}' | openssl base64 -d -A -out ${WORKDIR}/vault.crt
kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' | base64 -d > ${WORKDIR}/vault.ca
kubectl create secret generic vault-tls -n $VAULT_K8S_NAMESPACE --from-file=vault.key=${WORKDIR}/vault.key --from-file=vault.crt=${WORKDIR}/vault.crt --from-file=vault.ca=${WORKDIR}/vault.ca
```

### Vault の設定

Helm の `values.yaml` に証明書の設定を追加します。

```yaml
global:
  enabled: true
  tlsDisable: false
extraEnvironmentVars:
  VAULT_CACERT: /vault/userconfig/vault-tls/vault.ca
  VAULT_TLSCERT: /vault/userconfig/vault-tls/vault.crt
  VAULT_TLSKEY: /vault/userconfig/vault-tls/vault.key
volumes:
  - name: userconfig-vault-tls
    secret:
      defaultMode: 420
      secretName: vault-tls
volumeMounts:
  - mountPath: /vault/userconfig/vault-tls
    name: userconfig-vault-tls
    readOnly: true
standalone:
  enabled: true
  config: |
    ui = true
    listener "tcp" {
      address = "[::]:8200"
      cluster_address = "[::]:8201"
      tls_cert_file = "/vault/userconfig/vault-tls/vault.crt"
      tls_key_file  = "/vault/userconfig/vault-tls/vault.key"
      tls_client_ca_file = "/vault/userconfig/vault-tls/vault.ca"
    }
    storage "file" {
      path = "/vault/data"
    }
ingress:
  enabled: true
  ingressClassName: nginx
  tls:
    - hosts:
        - vault.example.com
      secretName: cluster-tls
  pathType: Prefix
  hosts:
    - host: vault.example.com
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
```

ドキュメントとは異なる点は以下の 2 点です。

- `standalone.config`に `ui = true` を記述  
  これがないと、Vault の UI にアクセスできなくなります（参考：https://github.com/hashicorp/vault/issues/4345）
- ingress の `nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"` を追加  
  nginx から vault に対して HTTPS で通信するように設定します（参考：https://stackoverflow.com/questions/69169466/ingress-nginx-client-sent-http-request-to-https-server ）

### ExternalSecrets の設定

ClusterSecretStore の設定を変更して、Vault との通信を HTTPS に変更します。

```yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-secret-store
spec:
  provider:
    vault:
      server: "https://vault.vault.svc.cluster.local:8200" # HTTPS に変更
      path: k8s
      version: v2
      namespace: vault
      auth:
        kubernetes:
          mountPath: kubernetes
          role: k8s-cluster
          serviceAccountRef:
            name: vault-auth
            namespace: vault
      caProvider: # 追加
        type: Secret
        namespace: vault
        name: vault-tls
        key: vault.ca
```

これで、Vault との通信が HTTPS に変更されました。

## おわりに

今回は、Vault との通信を HTTPS に変更しました。
これにより、クラスタ内での Secret のやりとりがより安全になりました。

次回は cert-manager を使って、証明書の自動更新を行えるようにしたいと思います。

## 参考

https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-minikube-tls

https://developer.hashicorp.com/vault/docs/platform/k8s/helm/examples/standalone-tls

https://github.com/hashicorp/vault/issues/4345

https://stackoverflow.com/questions/69169466/ingress-nginx-client-sent-http-request-to-https-server

https://external-secrets.io/v0.4.3/api-clustersecretstore/