Vault HA + Consul HA + k8s
В данной статье будет показан процесс установки 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
Либо через helm, но доступ из России сейчас запрещен:
$ helm repo add hashicorp https://helm.releases.hashicorp.com
$ helm pull hashicorp/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.key:
type: kubernetes.io/tls
где:
вместо $ cat fullchain1.pem | base64 -w0
вместо $ 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
: включаем шифрование, указываем имя секрета, где будет храниться ключ. Сам ключ создается так (
$ docker run --rm -ti --entrypoint=/bin/sh hashicorp/consul:latest
/ # consul keygen
$ kubectl -n consul create secret generic \
consul-consul-gossip-encryption-key --from-literal=key=
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:
Токен (SecretID) необходимо сохранить в безопасном месте.
Через web интерфейс проверить что все работает: авторизоваться используя полученный токен.
Важный момент для диагностики, если что-то пошло не так
В моем случае в логах была ошибка, что нет ключа для дешифровки сообщений. Необходимо проверить следующее:
$ kubectl -n consul exec -ti consul-consul-server-0 -- \
cat /consul/data/serf/local.keyring
[""]
Ключ должен находиться в списке всех этих файлов (в каждом поде). Это тот самый ключ из секрета 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.key:
type: kubernetes.io/tls
где:
вместо $ cat fullchain1.pem | base64 -w0
вместо $ 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 = ""
}
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 = "
токен, который был создан в 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 здесь на Хабре есть несколько замечательных статей. Повторять их нет необходимости.