В данной статье будет показан процесс установки HashiCorp Vault в связке с Consul, который выступает хранилищем для Vault, в HA режиме с включенным tls шифрованием в Kubernetes кластер.
Исходные данные:
- Kubernetes кластер (например yandex)
- В кластере установлен nginx ingress controller, в статье класс определен как nginx-internal
- Для UI существуют TLS/SSL сертификаты
- Установлен helm 3
- есть доступ к кластеру через kubectl с правами администратора
- есть возможность запустить docker контейнер
Установка Consul
Скачать исходники чартов consul-k8s https://github.com/hashicorp/consul-k8s/tree/main/charts/consul
Создать namespace:
$ kubectl create namespace consul
Создать secret для ssl сертификатов для ingress контроллера.
Допустим у вас есть ssl сертификаты от https://letsencrypt.org/ :
Создать фаил secret-tls-consul.yaml :
apiVersion: v1 kind: Secret metadata: name: secret-tls data: tls.crt: <tls.crt base64> tls.key: <tls.key base64> type: kubernetes.io/tls
где:
вместо <tls.crt base64> подставить заначение:$ cat fullchain1.pem | base64 -w0
вместо <tls.key base64> подставить значение:$ cat privkey1.pem | base64 -w0
Применить данный манифест:
$ kubectl -n consul apply -f secret-tls-consul.yaml
Создать фаил values-work.yaml где переопределить некоторые переменные:
global: enabled: true logLevel: "info" logJSON: true datacenter: vault-storage gossipEncryption: autoGenerate: false secretName: consul-consul-gossip-encryption-key secretKey: key tls: enabled: true enableAutoEncrypt: true server: enabled: "-" replicas: 3 storage: 5Gi storageClass: yc-network-ssd connect: true client: enabled: false connectInject: enabled: false ui: enabled: "-" service: enabled: true ingress: enabled: true annotations: | "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS" ingressClassName: nginx-internal pathType: Prefix hosts: - host: consul.k8s.domain.com tls: - secretName: secret-tls hosts: - consul.k8s.domain.com
Основные моменты файла:gossipEncryption : включаем шифрование, указываем имя секрета, где будет храниться ключ. Сам ключ создается так (<KeyString> нужно сгенерировать):
$ docker run --rm -ti --entrypoint=/bin/sh hashicorp/consul:latest / # consul keygen <KeyString>
$ kubectl -n consul create secret generic \ consul-consul-gossip-encryption-key --from-literal=key=<KeyString>
replicas: 3: количество реплик, должно быть достаточное количество нод в k8s либо включен автоскейлингstorageClass: yc-network-ssd - один из классов в yandex. Острожно, у него RECLAIMPOLICY=Delete, это означает, что если удалить pvc, то диск, который будет создан, тоже будет удален.enableAutoEncrypt: true: благодаря этой строчке будет создан самоподписной CA (секреты: consul-consul-ca-cert,consul-consul-ca-key), а также сертифкат и ключ для самого consul (секрет: consul-consul-server-cert).
Можно попробовать запустить:
$ helm -n consul upgrade --install consul -f values-work.yaml ./consul-1.3.1
где ./consul-1.3.1 - папка с чартом consul
$ kubectl -n consul get pods,pvc NAME READY STATUS RESTARTS AGE pod/consul-consul-server-0 1/1 Running 0 30m pod/consul-consul-server-1 1/1 Running 0 30m pod/consul-consul-server-2 1/1 Running 0 30m NAME STATUS persistentvolumeclaim/data-consul-consul-consul-server-0 Bound persistentvolumeclaim/data-consul-consul-consul-server-1 Bound persistentvolumeclaim/data-consul-consul-consul-server-2 Bound
Самоподписные SSL:
При использовании опции enableAutoEncrypt: true Сертификат для consul создается сроком на два года, чтобы его обновлять автоматом необходимо периодически переустанавливать сам consul. К тому же выпускается от стороннего производителя, примерно такой:
Issuer: C = US, ST = CA, L = San Francisco, street = 101 Second Street, postalCode = 94105, O = HashiCorp Inc., CN = Consul Agent CA
Необходимо создать свой CA и свои сертификаты, заполнить по необходимости, или взять уже существующие:
$ openssl genrsa -out rootCA.key 4096 $ openssl req -x509 -new -key rootCA.key -days 10000 -out rootCA.crt ........ Country Name (2 letter code) [AU]:RU State or Province Name (full name) [Some-State]:Moscow Locality Name (eg, city) []:Moscow Organization Name (eg, company) [Internet Widgits Pty Ltd]:Domain LTD Organizational Unit Name (eg, section) []:devops Common Name (e.g. server FQDN or YOUR name) []: Email Address []:devops@domain.com
Создать фаил consul.conf:
[req] default_bits = 4096 prompt = no default_md = sha256 req_extensions = v3_req distinguished_name = dn [dn] C = RU ST = Moscow L = Moscow CN = server.vault-storage.consul [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment
Создать фаил consul.ext:
subjectAltName=DNS:consul-consul-server, DNS:*.consul-consul-server, DNS:*.consul-consul-server.consul, DNS:consul-consul-server.consul, DNS:*.consul-consul-server.consul.svc, DNS:consul-consul-server.consul.svc, DNS:*.server.vault-storage.consul, DNS:server.vault-storage.consul, DNS:localhost, IP:127.0.0.1
Список этих subjectAltName можно подсмотреть в сертификате, который был сгенерирован автоматически.
Сгенерировать ключ и выпустить/подписать сертификат:
$ openssl genrsa -out consul.key 4096 $ openssl req -new \ -key consul.key \ -out consul.csr \ -config consul.conf $ openssl x509 -req \ -in consul.csr \ -CA rootCA.crt -CAkey rootCA.key \ -CAcreateserial -out consul.crt \ -extfile consul.ext \ -days 5000
Создать секреты с этими ключом и сертификатами:
$ kubectl -n consul create secret generic root-ca --from-file=tls.crt=rootCA.crt $ kubectl -n consul create secret tls consul --key consul.key --cert consul.crt
Поменять values-work.yaml для использования этих секретов:
global: enabled: true logLevel: "info" logJSON: true datacenter: vault-storage gossipEncryption: autoGenerate: false secretName: consul-consul-gossip-encryption-key secretKey: key tls: enabled: true enableAutoEncrypt: false caCert: # The name of the Kubernetes secret. secretName: root-ca # The key of the Kubernetes secret. secretKey: tls.crt verify: true server: enabled: "-" replicas: 3 storage: 5Gi storageClass: yc-network-ssd connect: true serverCert: # The name of the Vault secret that holds the PEM encoded server certificate. # @type: string secretName: consul client: enabled: false connectInject: enabled: false ui: enabled: "-" service: enabled: true ingress: enabled: true annotations: | "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS" ingressClassName: nginx-internal pathType: Prefix hosts: - host: consul.k8s.domain.com tls: - secretName: secret-tls hosts: - consul.k8s.domain.com
Удалить ненужные секреты и запустить с новыми параметрами:
$ kubectl -n consul delete secrets consul-consul-ca-cert consul-consul-ca-key consul-consul-server-cert $ helm -n consul upgrade --install consul -f values-work.yaml ./consul-1.3.1
Удалить все поды consul, так как из-за новых сертификатов, они не смогут взаимодействовать друг с другом:
$ kubectl -n consul delete pods --all
Закрыть токеном UI consul
Привести конфигурацию values-work.yaml к виду:
global: enabled: true logLevel: "info" logJSON: true datacenter: vault-storage gossipEncryption: autoGenerate: false secretName: consul-consul-gossip-encryption-key secretKey: key tls: enabled: true enableAutoEncrypt: false caCert: # The name of the Kubernetes secret. secretName: root-ca # The key of the Kubernetes secret. secretKey: tls.crt verify: true server: enabled: "-" replicas: 3 storage: 5Gi storageClass: yc-network-ssd connect: true serverCert: # The name of the Vault secret that holds the PEM encoded server certificate. # @type: string secretName: consul extraConfig: | { "addresses": { "http": "0.0.0.0" }, "acl": { "enabled": true, "default_policy": "deny", "down_policy": "extend-cache", "enable_token_persistence": true } } client: enabled: false connectInject: enabled: false ui: enabled: "-" service: enabled: true ingress: enabled: true annotations: | "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS" ingressClassName: nginx-internal pathType: Prefix hosts: - host: consul.k8s.domain.com tls: - secretName: secret-tls hosts: - consul.k8s.domain.com
Применить/перезапустить:
$ helm -n consul upgrade --install consul -f values-work.yaml ./consul-1.3.1
Дождаться пока перезапустятся все поды, и будет выбран лидер - можно смотреть по логам:
$ kubectl -n consul logs consul-consul-server-2 -f
Запустить генерацию bootstrap токена:
$ kubectl -n consul exec -ti consul-consul-server-0 -- sh -c "consul acl bootstrap | grep -i secretid" Defaulted container "consul" out of: consul, locality-init (init) SecretID: <bootstrap token string>
Токен (SecretID) необходимо сохранить в безопасном месте.
Через web интерфейс проверить, что все работает: авторизоваться, используя полученный токен.
Важный момент для диагностики, если что-то пошло не так
В моем случае в логах бы��а ошибка, что нет ключа для дешифровки сообщений. Необходимо проверить следующее:
$ kubectl -n consul exec -ti consul-consul-server-0 -- \ cat /consul/data/serf/local.keyring ["<KeyString>"]
Ключ должен находиться в списке всех этих файлов (в каждом поде). Это тот самый ключ из секрета consul-consul-gossip-encryption-key, здесь он будет указан без base64 кодировки.
Создать политику и токен для Vault:


Токен понадобится далее.
Установка Vault
Создать tls/ssl сертификаты
Создать файлы:
vault.conf:
[req] default_bits = 4096 prompt = no default_md = sha256 req_extensions = v3_req distinguished_name = dn [dn] C = RU ST = Russia L = Moscow CN = vault-server-tls.vault.svc [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment
vault.ext:
subjectAltName=DNS:vault,DNS:localhost,DNS:vault-0.vault-internal,DNS:vault-1.vault-internal,DNS:vault-2.vault-internal,IP:127.0.0.1
Создать ключ и сертификат:
$ openssl genrsa -out vault.key 4096 $ openssl req -new \ -key vault.key \ -out vault.csr \ -config vault.conf $ openssl x509 -req \ -in vault.csr \ -CA rootCA.crt -CAkey rootCA.key \ -CAcreateserial -out vault.crt \ -extfile vault.ext \ -days 5000
Где rootCA.key и rootCA.crt - ключ и сертификат CA созданные при установке consul
Создать secret-ы из сертификатов:
$ kubectl create ns vault $ kubectl -n vault create secret tls vault --key vault.key --cert vault.crt $ kubectl -n vault create secret generic root-ca --from-file=tls.crt=rootCA.crt
Скачать с github исходники helm для Vault https://github.com/hashicorp/vault-helm.git
Создать secret для ssl сертификатов для ingress контроллера.
Допустим у вас есть ssl сертификаты от https://letsencrypt.org/ :
Создать фаил secret-tls-vault.yaml :
apiVersion: v1 kind: Secret metadata: name: secret-tls data: tls.crt: <tls.crt base64> tls.key: <tls.key base64> type: kubernetes.io/tls
где:
вместо <tls.crt base64> подставить значение:$ cat fullchain1.pem | base64 -w0
вместо <tls.key base64> подставить значение:$ cat privkey1.pem | base64 -w0
Применить данный манифест:
$ kubectl -n vault apply -f secret-tls-vault.yaml
Создать фаил values-work.yaml, где переопределить некоторые значения:
global: tlsDisable: false server: standalone: enabled: false dataStorage: enabled: false extraEnvironmentVars: VAULT_CACERT: /vault/userconfig/root-ca/tls.crt extraVolumes: - type: secret name: root-ca - type: secret name: vault ha: enabled: true replicas: 3 config: | ui = true listener "tcp" { tls_disable = 0 address = "[::]:8200" cluster_address = "[::]:8201" tls_cert_file = "/vault/userconfig/vault/tls.crt" tls_key_file = "/vault/userconfig/vault/tls.key" } storage "consul" { path = "vault" address = "consul-consul-server.consul.svc:8501" scheme = "https" tls_ca_file = "/vault/userconfig/root-ca/tls.crt" token = "<vault-token>" } service_registration "kubernetes" {} ingress: enabled: yes annotations: nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" ingressClassName: nginx-internal hosts: - host: vault.k8s.domain.com tls: - secretName: secret-tls hosts: - vault.k8s.domain.com injector: enabled: false ui: enabled: false
Основные моменты файла:
tls_disable = 0: включение шифрования трафикаtoken = "<vault-token>": токен, который был создан в Consul для Vault.address = "consul-consul-server.consul.svc:8501": имя consul-consul-server.consul.svc должно быть прописано в ssl сертификате для consul.
Установить:
$ helm upgrade -n vault --install vault -f values-work.yaml ./vault-0.27.0
Запустить инициализацию, подставить свои значения для количества ключей и количества для ключей для unseal:
$ kubectl -n vault exec -ti vault-0 -- /bin/sh / $ vault operator init -key-shares=3 -key-threshold=2
Сохранить ключи и токен root в надежном месте!
Распечатать каждый под Vault необходимое количество раз (значение -key-threshold):
$ kubectl -n vault exec -ti vault-0 -- /bin/sh / $ vault operator unseal Unseal Key (will be hidden): Key Value --- ----- Seal Type shamir Initialized true Sealed true Total Shares 3 Threshold 2 Unseal Progress 1/2 Unseal Nonce cb2c64c9-ca57-0c98-2141-e99235f05853 Version 1.15.2 Build Date 2023-11-06T11:33:28Z Storage Type consul HA Enabled true
Успешным итогом будет такой вывод для каждого пода:
$ kubectl -n vault exec -ti vault-0 -- vault status Key Value --- ----- Recovery Seal Type shamir Initialized true Sealed false Total Recovery Shares 3 Threshold 2 Version 1.15.2 Build Date 2023-11-06T11:33:28Z Storage Type consul Cluster Name vault-cluster-51496cc6 Cluster ID b77b3b96-9bfa-11a9-21b9-bcdc331bfe00 HA Enabled true
Проверить доступность через UI
Можно пользоваться!
Для настройки автоматического Unseal здесь на Хабре есть несколько замечательных статей. Повторять их нет необходимости.
