.NET и HasiCorp Vault: Использование секретов в настройках .NET Core приложения

от автора

Настройка

Для работы нам понадобится настроенный к8s кластер, можно использовать Mimkube или k3s. Также необходимо установить helm и jg.

Установка Vault

HasiCorp рекомендует устанавливать Vault используя официальный helm chart.

Добавим официальный репозиторий.

$helm repo add hashicorp https://helm.releases.hashicorp.com "hashicorp" has been added to your repositories

Обновим репозиторий до последней версии. 

$helm repo update Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "hashicorp" chart repository Update Complete. ⎈Happy Helming!⎈

Установим последнюю версию чарта, наша установка будет без HA, при использовании в продуктивной среде желательно ставить с HA.

$helm install vault hashicorp/vault --set='server.ha.enabled=false' NAME: vault LAST DEPLOYED: Sun Dec 18 08:54:20 2022 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: Thank you for installing HashiCorp Vault!  Now that you have deployed Vault, you should look over the docs on using Vault with Kubernetes available here:  https://www.vaultproject.io/docs/   Your release is named vault. To learn more about the release, try:    $ helm status vault   $ helm get manifest vault

Проверяем, что все установилось.

$kubectl get po NAME                                READY   STATUSRESTARTS   AGE vault-0                             0/1 Running   0      3m9s vault-agent-injector-77fd4cb69f-4brdw   1/1 Running   0      3m9s

Под vault-0 в статусе Running, но еще не готов к обработке запросов, так как мы его еще не проинициализировали. Посмотрим его статус.

$kubectl exec vault-0 -- vault status Key            Value ---            ----- Seal Type      shamir Initialized    false Sealed         true Total Shares   0 Threshold      0 Unseal Progress0/0 Unseal Nonce   n/a Version        1.12.1 Build Date     2022-10-27T12:32:05Z Storage Type   file HA Enabled     false

Мы видим, что Vault еще не проинициализирован и запечатан.  

 Инициализация Vault

При первичной установке Vault запускается  не проинициализированным и запечатанным.
Инициализируем с сохранением настроек в файл.

$kubectl exec vault-0 -- vault operator init \ -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json

Сохраним Vault unseal key в переменную среды и распечатаем. 

$VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]") $kubectl exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY Key         Value ---         ----- Seal Type   shamir Initialized true Sealed      false Total Shares1 Threshold   1 Version     1.12.1 Build Date  2022-10-27T12:32:05Z Storage Typefile Cluster Namevault-cluster-cdeb59eb Cluster ID  67d6948e-529e-98e1-e86b-d83342f0584f HA Enabled  false

Vault настроен и готов к работе.

Настройка секретов

Наше приложение будет брать настройки из секретов. Для создания секрета нужно войти в систему с root token, включить key-value secret engine, и сохранить наши секреты. 

Сначала сохраним рутовый токен в переменную среды.

$export VAULT_ROOT_TOKEN=$(cat cluster-keys.json | jq -r ".root_token")

Запустим новую терминальную сессию в контейнере Vault и передадим туда переменную. 

$kubectl exec --stdin=true --tty=true vault-0 \ -- /bin/sh -c "env VAULT_ROOT_TOKEN=$(echo $VAULT_ROOT_TOKEN)  /bin/sh"

Теперь логинимся в Vault.

/ $ vault login $VAULT_ROOT_TOKEN Success! You are now authenticated. The token information displayed below is already stored in the token helper. You do NOT need to run "vault login" again. Future Vault requests will automatically use this token.  Key              Value ---              ----- token            <your token here> token_accessor   <your token here> token_duration   ∞ token_renewable  false token_policies   ["root"] identity_policies[] policies         ["root"]

Включаем хранилище секретов kv-v2 с путем secrets.

 / $ vault secrets enable -path=secrets kv-v2 Success! Enabled the kv-v2 secrets engine at: secrets/

Создаем секрет для нашего приложения.

/ $ vault kv put secrets/services/dotnet username='Bob' password='Bob_Password' ======== Secret Path ======== secrets/data/services/dotnet  ======= Metadata ======= Key            Value ---            ----- created_time   2022-12-18T10:26:35.169923707Z custom_metadata<nil> deletion_time  n/a destroyed      false version        1

Проверяем что секрет доступен по пути secrets/services/dotnet.

/ $ vault kv get secrets/services/dotnet ======== Secret Path ======== secrets/data/services/dotnet  ======= Metadata ======= Key            Value ---            ----- created_time   2022-12-18T10:26:35.169923707Z custom_metadata<nil> deletion_time  n/a destroyed      false version        1  ====== Data ====== Key     Value ---     ----- passwordBob_Password usernameBob

Настройка аутентификации  в Kubernetes

Рутовый токен является привилегированным пользователем, который может выполнять любые операции на любом пути. Нашему же приложению требуется только возможность чтения секретов, определенных на одном пути. Для этого приложение должно пройти аутентификацию и получить токен с ограниченным доступом.

Включаем аутентификацию через Kubernetes.

/ $ vault auth enable kubernetes Success! Enabled kubernetes auth method at: kubernetes/

Настраиваем доступ к API Kubernetes.

/ $ vault write auth/kubernetes/config \ kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" Success! Data written to: auth/kubernetes/config

Создаем политику с именем service, которая включает возможность чтения для секретов по пути secrets/data/services/dotnet.

/ $ vault policy write service - <<EOF > path "secrets/data/services/dotnet" { >   capabilities = ["read"] > } > EOF Success! Uploaded policy: service 

Создаем роль для аутентификации в Kubernetes с именем service.

/ $ vault write auth/kubernetes/role/service \ >     bound_service_account_names=* \ >     bound_service_account_namespaces=* \ >     policies=service \ >     ttl=24h Success! Data written to: auth/kubernetes/role/service

Настройка приложения

Тестовое приложение доступно на github.

$git clone https://github.com/nkz-soft/dotnet-k8s-vault

Приложение разворачивается в кластере с помощью helm chart.

$cd dotnet-k8s-vault/deployment/k8s/.helm/ $helm install dotnet-vault . NAME: dotnet-vault LAST DEPLOYED: Sun Dec 18 11:28:47 2022 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: 1. Get the application URL by running these commands:   export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services dotnet-vault)   export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")   echo http://$NODE_IP:$NODE_PORT 

Проверим, что мы можем прочитать содержимое секрета. 

$export DOTNET_K8S_VAULT_PORT=$(kubectl get svc dotnet-vault -o json | jq -r ".spec.ports[].nodePort") $curl localhost:$DOTNET_K8S_VAULT_PORT/config {"VaultSecrets":null,"VaultSecrets:userName":"Bob","VaultSecrets:password":"Bob_Password"}

Как это работает

Содержимое файла Program.cs. 

var builder = WebApplication.CreateBuilder(args); builder.Services.AddHealthChecks();  builder.Configuration .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true) .AddJsonFile("/vault/secrets/appsettings.json", optional: true, reloadOnChange: true);  var app = builder.Build();  app.MapHealthChecks("/healthz"); app.MapGet("/config", (IConfiguration configuration) => Results.Ok     (configuration.GetSection("VaultSecrets")         .AsEnumerable().ToDictionary(k => k.Key, v => v.Value)));  app.Run();

Приложение будет читать настройки из файла секрета /vault/secrets/appsettings.json и отображать текущие настройки по адресу http://localhost/config

Содержимое файла configmap.yaml на основании которого создается шаблон.

apiVersion: v1 kind: ConfigMap metadata:   name: {{ include "dotnet-vault.fullname" . }}   labels: {{- include "dotnet-vault.labels" . | nindent 4 }} data:   appsettings.json: | {   "Logging": {     "LogLevel": {       "Default": "Information",       "Microsoft.AspNetCore": "Warning"     }   },   "VaultSecrets": {     {{` {{- with secret "secrets/data/services/dotnet" }}       "userName": "{{ .Data.data.username }}",       "password": "{{ .Data.data.password }}"     {{- end }} `}}   } }

Deployment использует следующие аннотации:

  vault.hashicorp.com/agent-inject: "true"   vault.hashicorp.com/agent-copy-volume-mounts: 'dotnet-vault'   vault.hashicorp.com/agent-inject-secret-appsettings.json: ""   vault.hashicorp.com/agent-inject-template-file-appsettings.json: '/vault/config/appsettings.json'   vault.hashicorp.com/role: service
  1. Подключаем sidecar Vault агента.

  2.  Копируем данные из подключенного тома.

  3. Подключаем конфигурационный и шаблонный файлы. 

  4. Устанавливаем необходимую роль для доступа.

В результате в поде агента будет лежать шаблон.

/ $ cat vault/config/appsettings.json {   "Logging": { "LogLevel": {   "Default": "Information",   "Microsoft.AspNetCore": "Warning" }   },   "VaultSecrets": {  {{- with secret "secrets/data/services/dotnet" }}   "userName": "{{ .Data.data.username }}",   "password": "{{ .Data.data.password }}" {{- end }}   } }

А уже готовый сгенерированный файл настроек который будет создаваться при старте пода агента.

/ $ cat /vault/secrets/appsettings.json {   "Logging": { "LogLevel": {   "Default": "Information",   "Microsoft.AspNetCore": "Warning" }   },   "VaultSecrets": {   "userName": "Bob",   "password": "Bob_Password"   } }


ссылка на оригинал статьи https://habr.com/ru/post/707802/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *