В данной статье будет показан процесс установки 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 здесь на Хабре есть несколько замечательных статей. Повторять их нет необходимости.

Комментарии (2)


  1. vasyakrg
    31.05.2024 16:43

    Зачем консул, если есть родной рафт ?


    1. vsb2007 Автор
      31.05.2024 16:43

      Спасибо за вопрос!
      Это, как говорится, на вкус и цвет :-) Данная статья про использование Consul.
      сравнение можно почитать тут, можно для себя отметить как плюсы, так и минусы