В данной статье будет показан процесс установки 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 здесь на Хабре есть несколько замечательных статей. Повторять их нет необходимости.
vasyakrg
Зачем консул, если есть родной рафт ?
vsb2007 Автор
Спасибо за вопрос!
Это, как говорится, на вкус и цвет :-) Данная статья про использование Consul.
сравнение можно почитать тут, можно для себя отметить как плюсы, так и минусы