feat: добавлены аддоны SMTP Relay, HashiCorp Vault, External Secrets Operator
- SMTP Relay (bokysan/mail): Postfix relay через Yandex SMTP, порт 465 с TLS wrappermode, trusted networks only (pod/service CIDR), без аутентификации внутри кластера — поды отправляют на smtp-relay:25 - HashiCorp Vault (hashicorp/vault): standalone и HA (Raft) режимы, auto-unseal: k8s Secret (homelab), AWS KMS, GCP CKMS, Azure Key Vault, Transit; Vault Agent Injector по умолчанию; Job инициализации + Unsealer Deployment для k8s режима; README с полным гайдом по injection в YAML/Helm - External Secrets Operator (ESO): синхронизирует Vault секреты в k8s Secrets, ClusterSecretStore с AppRole auth, README с примерами ExternalSecret в YAML манифестах, Helm чартах и ArgoCD Обновлены: addons.yml (3 новых флага + секции), vault.yml.example (smtp_relay_password, aws_kms_*, eso_approle_secret_id), playbooks/addons.yml, Makefile
This commit is contained in:
13
Makefile
13
Makefile
@@ -57,6 +57,7 @@ DOCKER_RUN := docker run --rm -it \
|
||||
addon-loki addon-promtail addon-tempo addon-pushgateway \
|
||||
addon-harbor addon-gitea addon-owncloud addon-nextcloud \
|
||||
addon-csi-s3 addon-csi-ceph addon-csi-glusterfs addon-vaultwarden \
|
||||
addon-smtp-relay addon-vault addon-external-secrets \
|
||||
add-node remove-node \
|
||||
add-etcd-node remove-etcd-node \
|
||||
etcd-backup etcd-restore etcd-list-snapshots \
|
||||
@@ -386,6 +387,18 @@ addon-vaultwarden: _check_env _check_image ## Установить Vaultwarden
|
||||
@printf "$(CYAN)Устанавливаю Vaultwarden...$(NC)\n"
|
||||
$(DOCKER_RUN) addon vaultwarden $(ARGS)
|
||||
|
||||
addon-smtp-relay: _check_env _check_image ## Установить SMTP Relay — Postfix → Yandex (уведомления из подов без внешней авторизации)
|
||||
@printf "$(CYAN)Устанавливаю SMTP Relay...$(NC)\n"
|
||||
$(DOCKER_RUN) addon smtp-relay $(ARGS)
|
||||
|
||||
addon-vault: _check_env _check_image ## Установить HashiCorp Vault — менеджер секретов (ARGS="-e vault_mode=ha -e vault_auto_unseal_type=k8s")
|
||||
@printf "$(CYAN)Устанавливаю HashiCorp Vault...$(NC)\n"
|
||||
$(DOCKER_RUN) addon vault $(ARGS)
|
||||
|
||||
addon-external-secrets: _check_env _check_image ## Установить External Secrets Operator → Vault/AWS/GCP (ARGS="-e external_secrets_vault_role_id=...")
|
||||
@printf "$(CYAN)Устанавливаю External Secrets Operator...$(NC)\n"
|
||||
$(DOCKER_RUN) addon external-secrets $(ARGS)
|
||||
|
||||
# Generic цель — любой аддон из addons/<name>/playbook.yml
|
||||
addon-%: _check_env _check_image
|
||||
@if [ ! -f "addons/$*/playbook.yml" ]; then \
|
||||
|
||||
288
addons/external-secrets/README.md
Normal file
288
addons/external-secrets/README.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# External Secrets Operator (ESO)
|
||||
|
||||
Синхронизирует секреты из внешних хранилищ (HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager и др.) в нативные Kubernetes Secrets.
|
||||
|
||||
**Когда использовать ESO вместо Vault Agent Injector:**
|
||||
- Нужны стандартные k8s Secrets (`secretRef`, `envFrom`) — Helm чарты не менять
|
||||
- Секреты нужны до старта пода (init containers, admission webhooks)
|
||||
- Нужна синхронизация между namespace
|
||||
- Нет желания добавлять Vault annotations в каждый Deployment
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
# Базовая установка (настрой ClusterSecretStore вручную после)
|
||||
make addon-external-secrets
|
||||
|
||||
# Сразу с настроенным ClusterSecretStore → Vault (если AppRole уже создан)
|
||||
make addon-external-secrets ARGS="-e external_secrets_vault_role_id=<role-id>"
|
||||
```
|
||||
|
||||
## Настройка AppRole в Vault
|
||||
|
||||
ESO использует AppRole для аутентификации в Vault. Выполни один раз:
|
||||
|
||||
```bash
|
||||
# 1. Port-forward к Vault
|
||||
kubectl port-forward -n vault svc/vault 8200:8200 &
|
||||
export VAULT_ADDR=http://localhost:8200
|
||||
vault login <root_token>
|
||||
|
||||
# 2. Включить AppRole auth (если ещё не включён)
|
||||
vault auth enable approle
|
||||
|
||||
# 3. Создать политику для ESO (read-only на все секреты)
|
||||
vault policy write eso-policy - <<EOF
|
||||
path "secret/data/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
path "secret/metadata/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
EOF
|
||||
|
||||
# 4. Создать AppRole роль
|
||||
vault write auth/approle/role/eso-role \
|
||||
secret_id_ttl="0" \
|
||||
token_ttl="1h" \
|
||||
token_max_ttl="24h" \
|
||||
token_policies="eso-policy"
|
||||
|
||||
# 5. Получить Role ID (публичный, вставить в addons.yml)
|
||||
vault read -field=role_id auth/approle/role/eso-role/role-id
|
||||
# → Скопируй в external_secrets_vault_role_id в group_vars/all/addons.yml
|
||||
|
||||
# 6. Получить Secret ID (секретный, вставить в vault.yml)
|
||||
vault write -f -field=secret_id auth/approle/role/eso-role/secret-id
|
||||
# → Скопируй в vault_eso_approle_secret_id в group_vars/all/vault.yml
|
||||
|
||||
# 7. Применить ClusterSecretStore
|
||||
make addon-external-secrets ARGS="-e external_secrets_vault_role_id=<role-id>"
|
||||
```
|
||||
|
||||
## Проверка подключения
|
||||
|
||||
```bash
|
||||
# Статус ClusterSecretStore (должен быть Valid)
|
||||
kubectl get clustersecretstore vault-backend
|
||||
|
||||
# Подробности
|
||||
kubectl describe clustersecretstore vault-backend
|
||||
```
|
||||
|
||||
## Использование в YAML манифестах
|
||||
|
||||
### Простой ExternalSecret — синхронизация одного секрета
|
||||
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: myapp-db-secret
|
||||
namespace: myapp
|
||||
spec:
|
||||
refreshInterval: 1h # как часто синхронизировать
|
||||
secretStoreRef:
|
||||
name: vault-backend # ClusterSecretStore
|
||||
kind: ClusterSecretStore
|
||||
target:
|
||||
name: myapp-db-credentials # имя k8s Secret который будет создан
|
||||
creationPolicy: Owner # ESO управляет Secret (авто-удаление)
|
||||
data:
|
||||
- secretKey: DB_PASSWORD # ключ в k8s Secret
|
||||
remoteRef:
|
||||
key: myapp/database # путь в Vault: secret/data/myapp/database
|
||||
property: password # поле внутри секрета
|
||||
- secretKey: DB_USER
|
||||
remoteRef:
|
||||
key: myapp/database
|
||||
property: username
|
||||
```
|
||||
|
||||
После применения ESO создаст k8s Secret `myapp-db-credentials`.
|
||||
|
||||
### Использование в Deployment (через envFrom)
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: myapp
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: myapp:latest
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: myapp-db-credentials # создан ExternalSecret выше
|
||||
# ИЛИ выборочно:
|
||||
env:
|
||||
- name: DATABASE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: myapp-db-credentials
|
||||
key: DB_PASSWORD
|
||||
```
|
||||
|
||||
### Весь секрет целиком (все поля из Vault)
|
||||
```yaml
|
||||
spec:
|
||||
dataFrom:
|
||||
- extract:
|
||||
key: myapp/config # все поля из secret/data/myapp/config станут ключами в k8s Secret
|
||||
```
|
||||
|
||||
### ExternalSecret с template (форматирование)
|
||||
```yaml
|
||||
spec:
|
||||
target:
|
||||
name: myapp-connection-string
|
||||
template:
|
||||
data:
|
||||
DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@postgres:5432/mydb"
|
||||
dataFrom:
|
||||
- extract:
|
||||
key: myapp/database
|
||||
```
|
||||
|
||||
## Использование в Helm чартах
|
||||
|
||||
### Способ 1: ExternalSecret в том же Helm чарте
|
||||
```yaml
|
||||
# templates/external-secret.yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: {{ include "myapp.fullname" . }}-secrets
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
refreshInterval: {{ .Values.secrets.refreshInterval | default "1h" }}
|
||||
secretStoreRef:
|
||||
name: vault-backend
|
||||
kind: ClusterSecretStore
|
||||
target:
|
||||
name: {{ include "myapp.fullname" . }}-secrets
|
||||
creationPolicy: Owner
|
||||
dataFrom:
|
||||
- extract:
|
||||
key: {{ .Values.secrets.vaultPath }} # задаётся в values.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# values.yaml
|
||||
secrets:
|
||||
vaultPath: "myapp/production/config"
|
||||
refreshInterval: "30m"
|
||||
|
||||
# Deployment использует стандартный envFrom:
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: "{{ include "myapp.fullname" . }}-secrets"
|
||||
```
|
||||
|
||||
### Способ 2: ESO как внешняя зависимость (рекомендуется)
|
||||
Секреты создаются заранее через отдельный ExternalSecret, Helm чарт просто ссылается на k8s Secret:
|
||||
|
||||
```bash
|
||||
# 1. Создать ExternalSecret (один раз, в GitOps репозитории)
|
||||
kubectl apply -f external-secret.yaml
|
||||
|
||||
# 2. Установить Helm чарт — он найдёт уже существующий Secret
|
||||
helm install myapp ./mychart --set existingSecret=myapp-db-credentials
|
||||
```
|
||||
|
||||
```yaml
|
||||
# values.yaml чарта
|
||||
existingSecret: "" # если задан — используется, иначе chart создаёт свой
|
||||
|
||||
# templates/deployment.yaml
|
||||
{{- if .Values.existingSecret }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ .Values.existingSecret }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
### Способ 3: ArgoCD + ESO
|
||||
```yaml
|
||||
# argocd Application
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: myapp
|
||||
spec:
|
||||
source:
|
||||
helm:
|
||||
values: |
|
||||
# Секреты создаются ESO автоматически — чарт просто ссылается
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: myapp-vault-secrets
|
||||
```
|
||||
|
||||
## PushSecret — запись секретов в Vault из k8s
|
||||
|
||||
ESO поддерживает обратную синхронизацию (k8s → Vault):
|
||||
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1alpha1
|
||||
kind: PushSecret
|
||||
metadata:
|
||||
name: push-to-vault
|
||||
namespace: myapp
|
||||
spec:
|
||||
refreshInterval: 10s
|
||||
secretStoreRefs:
|
||||
- name: vault-backend
|
||||
kind: ClusterSecretStore
|
||||
selector:
|
||||
secret:
|
||||
name: my-local-secret # существующий k8s Secret
|
||||
data:
|
||||
- match:
|
||||
secretKey: password
|
||||
remoteRef:
|
||||
remoteKey: myapp/config
|
||||
property: password
|
||||
```
|
||||
|
||||
## Управление несколькими SecretStore
|
||||
|
||||
### Namespace-scoped SecretStore (для разных Vault paths/политик)
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: vault-myapp # только для namespace myapp
|
||||
namespace: myapp
|
||||
spec:
|
||||
provider:
|
||||
vault:
|
||||
server: "http://vault.vault.svc.cluster.local:8200"
|
||||
path: "myapp" # ограниченный путь
|
||||
version: "v2"
|
||||
auth:
|
||||
appRole:
|
||||
path: "approle"
|
||||
roleId: "<myapp-role-id>"
|
||||
secretRef:
|
||||
name: vault-approle-myapp
|
||||
key: secretId
|
||||
```
|
||||
|
||||
## Мониторинг
|
||||
|
||||
Если `addon_prometheus_stack: true`, метрики ESO доступны в Grafana:
|
||||
- `external_secrets_sync_calls_total` — количество синхронизаций
|
||||
- `external_secrets_sync_call_errors_total` — ошибки
|
||||
- Дашборд: импортируй Grafana Dashboard ID 21045
|
||||
|
||||
```bash
|
||||
# Статус всех ExternalSecrets в кластере
|
||||
kubectl get externalsecrets --all-namespaces
|
||||
|
||||
# Подробности (условие Ready/SecretSynced)
|
||||
kubectl describe externalsecret myapp-db-secret -n myapp
|
||||
```
|
||||
7
addons/external-secrets/playbook.yml
Normal file
7
addons/external-secrets/playbook.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Install External Secrets Operator
|
||||
hosts: k3s_master[0]
|
||||
gather_facts: false
|
||||
become: true
|
||||
roles:
|
||||
- role: "{{ playbook_dir }}/../addons/external-secrets/role"
|
||||
37
addons/external-secrets/role/defaults/main.yml
Normal file
37
addons/external-secrets/role/defaults/main.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
external_secrets_version: "" # "" = автоматически последняя версия чарта
|
||||
external_secrets_namespace: "external-secrets"
|
||||
external_secrets_chart_repo: "https://charts.external-secrets.io"
|
||||
|
||||
# ── Vault backend ─────────────────────────────────────────────────────────────
|
||||
# Адрес Vault (по умолчанию — внутренний Service addon_vault)
|
||||
external_secrets_vault_url: "http://vault.vault.svc.cluster.local:8200"
|
||||
|
||||
# Путь к секретам в Vault (KV v2)
|
||||
external_secrets_vault_kv_path: "secret"
|
||||
external_secrets_vault_kv_version: "v2"
|
||||
|
||||
# ── Vault AppRole аутентификация для ESO ─────────────────────────────────────
|
||||
# Role ID задаётся при создании AppRole в Vault (шаг 3 в README.md)
|
||||
external_secrets_vault_role_id: ""
|
||||
# Secret ID задаётся в vault.yml: vault_eso_approle_secret_id
|
||||
external_secrets_vault_secret_id: "{{ vault_eso_approle_secret_id | default('') }}"
|
||||
|
||||
# AppRole path (по умолчанию: approle)
|
||||
external_secrets_vault_approle_path: "approle"
|
||||
|
||||
# ── ClusterSecretStore ────────────────────────────────────────────────────────
|
||||
# Имя глобального ClusterSecretStore для Vault
|
||||
external_secrets_vault_store_name: "vault-backend"
|
||||
|
||||
# ── Метрики ───────────────────────────────────────────────────────────────────
|
||||
external_secrets_metrics_enabled: true
|
||||
|
||||
# ── Ресурсы ───────────────────────────────────────────────────────────────────
|
||||
external_secrets_resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
94
addons/external-secrets/role/tasks/main.yml
Normal file
94
addons/external-secrets/role/tasks/main.yml
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
- name: Add External Secrets Helm repo
|
||||
kubernetes.core.helm_repository:
|
||||
name: external-secrets
|
||||
repo_url: "{{ external_secrets_chart_repo }}"
|
||||
environment:
|
||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
||||
|
||||
- name: Get latest External Secrets chart version
|
||||
ansible.builtin.shell: |
|
||||
helm search repo external-secrets/external-secrets --output json | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin)[0]['version'])"
|
||||
register: _eso_latest_version
|
||||
changed_when: false
|
||||
when: external_secrets_version == ""
|
||||
environment:
|
||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
||||
|
||||
- name: Set External Secrets chart version
|
||||
ansible.builtin.set_fact:
|
||||
_eso_version: "{{ external_secrets_version if external_secrets_version != '' else _eso_latest_version.stdout | trim }}"
|
||||
|
||||
- name: Install External Secrets Operator via Helm
|
||||
kubernetes.core.helm:
|
||||
name: external-secrets
|
||||
chart_ref: external-secrets/external-secrets
|
||||
chart_version: "{{ _eso_version }}"
|
||||
release_namespace: "{{ external_secrets_namespace }}"
|
||||
create_namespace: true
|
||||
wait: true
|
||||
timeout: "5m0s"
|
||||
values:
|
||||
resources:
|
||||
requests:
|
||||
cpu: "{{ external_secrets_resources.requests.cpu }}"
|
||||
memory: "{{ external_secrets_resources.requests.memory }}"
|
||||
limits:
|
||||
cpu: "{{ external_secrets_resources.limits.cpu }}"
|
||||
memory: "{{ external_secrets_resources.limits.memory }}"
|
||||
serviceMonitor:
|
||||
enabled: "{{ external_secrets_metrics_enabled | bool and addon_prometheus_stack | default(false) | bool }}"
|
||||
environment:
|
||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
||||
|
||||
- name: Wait for External Secrets Operator to be ready
|
||||
ansible.builtin.command: >
|
||||
k3s kubectl -n {{ external_secrets_namespace }}
|
||||
rollout status deployment/external-secrets --timeout=120s
|
||||
changed_when: false
|
||||
retries: 3
|
||||
delay: 10
|
||||
|
||||
# ─── ClusterSecretStore → Vault (только если role_id задан) ──────────────────
|
||||
- name: Template ClusterSecretStore for Vault
|
||||
ansible.builtin.template:
|
||||
src: cluster-secret-store.yaml.j2
|
||||
dest: /tmp/vault-cluster-secret-store.yaml
|
||||
mode: '0644'
|
||||
when: external_secrets_vault_role_id != ""
|
||||
|
||||
- name: Apply ClusterSecretStore and AppRole credentials Secret
|
||||
ansible.builtin.command: k3s kubectl apply -f /tmp/vault-cluster-secret-store.yaml
|
||||
changed_when: true
|
||||
when: external_secrets_vault_role_id != ""
|
||||
|
||||
- name: Show External Secrets Operator access info
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "External Secrets Operator установлен в namespace: {{ external_secrets_namespace }}"
|
||||
- ""
|
||||
- "{% if external_secrets_vault_role_id != '' %}ClusterSecretStore '{{ external_secrets_vault_store_name }}' создан → {{ external_secrets_vault_url }}"
|
||||
- "{% else %}ClusterSecretStore НЕ создан — задай external_secrets_vault_role_id"
|
||||
- " Следуй шагам в addons/external-secrets/README.md (раздел 'Настройка AppRole в Vault')"
|
||||
- "{% endif %}"
|
||||
- ""
|
||||
- "Документация: addons/external-secrets/README.md"
|
||||
- ""
|
||||
- "Быстрый пример ExternalSecret:"
|
||||
- " apiVersion: external-secrets.io/v1beta1"
|
||||
- " kind: ExternalSecret"
|
||||
- " metadata:"
|
||||
- " name: my-secret"
|
||||
- " spec:"
|
||||
- " refreshInterval: 1h"
|
||||
- " secretStoreRef:"
|
||||
- " name: {{ external_secrets_vault_store_name }}"
|
||||
- " kind: ClusterSecretStore"
|
||||
- " target:"
|
||||
- " name: my-k8s-secret"
|
||||
- " data:"
|
||||
- " - secretKey: password"
|
||||
- " remoteRef:"
|
||||
- " key: myapp/config"
|
||||
- " property: password"
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
# Kubernetes Secret с AppRole credentials для ESO → Vault
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: vault-approle-credentials
|
||||
namespace: {{ external_secrets_namespace }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
roleId: "{{ external_secrets_vault_role_id }}"
|
||||
secretId: "{{ external_secrets_vault_secret_id }}"
|
||||
---
|
||||
# ClusterSecretStore: глобальная точка доступа к Vault для всех namespace
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: {{ external_secrets_vault_store_name }}
|
||||
spec:
|
||||
provider:
|
||||
vault:
|
||||
server: "{{ external_secrets_vault_url }}"
|
||||
path: "{{ external_secrets_vault_kv_path }}"
|
||||
version: "{{ external_secrets_vault_kv_version }}"
|
||||
auth:
|
||||
appRole:
|
||||
path: "{{ external_secrets_vault_approle_path }}"
|
||||
roleId: "{{ external_secrets_vault_role_id }}"
|
||||
secretRef:
|
||||
name: vault-approle-credentials
|
||||
namespace: {{ external_secrets_namespace }}
|
||||
key: secretId
|
||||
7
addons/smtp-relay/playbook.yml
Normal file
7
addons/smtp-relay/playbook.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Install SMTP Relay (Postfix → Yandex)
|
||||
hosts: k3s_master[0]
|
||||
gather_facts: false
|
||||
become: true
|
||||
roles:
|
||||
- role: "{{ playbook_dir }}/../addons/smtp-relay/role"
|
||||
33
addons/smtp-relay/role/defaults/main.yml
Normal file
33
addons/smtp-relay/role/defaults/main.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
smtp_relay_namespace: "smtp-relay"
|
||||
smtp_relay_chart_repo: "https://bokysan.github.io/docker-postfix/"
|
||||
smtp_relay_version: "" # "" = автоматически последняя версия чарта
|
||||
|
||||
# ── Upstream SMTP сервер ──────────────────────────────────────────────────────
|
||||
smtp_relay_host: "smtp.yandex.ru"
|
||||
smtp_relay_port: 465 # 465 = SMTPS (TLS wrapper), 587 = STARTTLS
|
||||
smtp_relay_username: "sergey@antropoff.ru"
|
||||
# Пароль задаётся в vault.yml: vault_smtp_relay_password
|
||||
smtp_relay_password: "{{ vault_smtp_relay_password | default('') }}"
|
||||
|
||||
# Имя отправителя по умолчанию (for envelope-from rewriting)
|
||||
smtp_relay_from: "vault@antropoff.ru"
|
||||
|
||||
# Разрешённые домены отправителей (через пробел)
|
||||
smtp_relay_allowed_sender_domains: "antropoff.ru"
|
||||
|
||||
# ── Доступ к relay ────────────────────────────────────────────────────────────
|
||||
# Только доверенные сети (pod CIDR + service CIDR + localhost)
|
||||
# Задай значения соответствующие твоему кластеру
|
||||
smtp_relay_mynetworks: "10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 127.0.0.0/8"
|
||||
|
||||
# ── Развёртывание ─────────────────────────────────────────────────────────────
|
||||
smtp_relay_replicas: 1
|
||||
|
||||
smtp_relay_resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
68
addons/smtp-relay/role/tasks/main.yml
Normal file
68
addons/smtp-relay/role/tasks/main.yml
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
- name: Add bokysan Helm repo (docker-postfix SMTP relay)
|
||||
kubernetes.core.helm_repository:
|
||||
name: bokysan
|
||||
repo_url: "{{ smtp_relay_chart_repo }}"
|
||||
environment:
|
||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
||||
|
||||
- name: Get latest smtp-relay chart version
|
||||
ansible.builtin.shell: |
|
||||
helm search repo bokysan/mail --output json | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin)[0]['version'])"
|
||||
register: _smtp_relay_latest_version
|
||||
changed_when: false
|
||||
when: smtp_relay_version == ""
|
||||
environment:
|
||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
||||
|
||||
- name: Set smtp-relay chart version
|
||||
ansible.builtin.set_fact:
|
||||
_smtp_relay_version: "{{ smtp_relay_version if smtp_relay_version != '' else _smtp_relay_latest_version.stdout | trim }}"
|
||||
|
||||
- name: Template SMTP Relay values
|
||||
ansible.builtin.template:
|
||||
src: smtp-relay-values.yaml.j2
|
||||
dest: /tmp/smtp-relay-values.yaml
|
||||
mode: '0600'
|
||||
|
||||
- name: Install SMTP Relay via Helm
|
||||
kubernetes.core.helm:
|
||||
name: smtp-relay
|
||||
chart_ref: bokysan/mail
|
||||
chart_version: "{{ _smtp_relay_version }}"
|
||||
release_namespace: "{{ smtp_relay_namespace }}"
|
||||
create_namespace: true
|
||||
wait: true
|
||||
timeout: "3m0s"
|
||||
values_files:
|
||||
- /tmp/smtp-relay-values.yaml
|
||||
environment:
|
||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
||||
|
||||
- name: Wait for SMTP Relay to be ready
|
||||
ansible.builtin.command: >
|
||||
k3s kubectl -n {{ smtp_relay_namespace }}
|
||||
rollout status deployment/smtp-relay --timeout=120s
|
||||
changed_when: false
|
||||
retries: 3
|
||||
delay: 10
|
||||
|
||||
- name: Show SMTP Relay access info
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "SMTP Relay установлен в namespace: {{ smtp_relay_namespace }}"
|
||||
- "Service: smtp-relay.{{ smtp_relay_namespace }}.svc.cluster.local:25"
|
||||
- "Upstream: {{ smtp_relay_host }}:{{ smtp_relay_port }} (TLS wrappermode)"
|
||||
- "Username: {{ smtp_relay_username }}"
|
||||
- "Trusted networks: {{ smtp_relay_mynetworks }}"
|
||||
- "Allowed sender domains: {{ smtp_relay_allowed_sender_domains }}"
|
||||
- ""
|
||||
- "Использование в подах (пример):"
|
||||
- " SMTP_HOST=smtp-relay.{{ smtp_relay_namespace }}.svc.cluster.local"
|
||||
- " SMTP_PORT=25"
|
||||
- " SMTP_FROM={{ smtp_relay_from }}"
|
||||
- " (без аутентификации — relay принимает из trusted networks)"
|
||||
- ""
|
||||
- "Тест: kubectl run -it --rm smtp-test --image=alpine --restart=Never -- sh"
|
||||
- " apk add mailx && echo 'test' | mail -s 'test' -r {{ smtp_relay_from }} you@example.com -S smtp=smtp://smtp-relay.{{ smtp_relay_namespace }}.svc.cluster.local"
|
||||
33
addons/smtp-relay/role/templates/smtp-relay-values.yaml.j2
Normal file
33
addons/smtp-relay/role/templates/smtp-relay-values.yaml.j2
Normal file
@@ -0,0 +1,33 @@
|
||||
replicaCount: {{ smtp_relay_replicas }}
|
||||
|
||||
config:
|
||||
general:
|
||||
# Upstream relay — скобки отключают MX-lookup, порт явный
|
||||
RELAYHOST: "[{{ smtp_relay_host }}]:{{ smtp_relay_port }}"
|
||||
RELAYHOST_USERNAME: "{{ smtp_relay_username }}"
|
||||
# Только эти сети могут использовать relay
|
||||
MYNETWORKS: "{{ smtp_relay_mynetworks }}"
|
||||
# Разрешённые домены отправителей
|
||||
ALLOWED_SENDER_DOMAINS: "{{ smtp_relay_allowed_sender_domains }}"
|
||||
# TLS: encrypt = обязательный TLS
|
||||
RELAYHOST_TLS_LEVEL: "encrypt"
|
||||
# Port 465 = SMTPS (TLS wrapper mode), требует явного флага
|
||||
POSTFIX_smtp_tls_wrappermode: "yes"
|
||||
# Запрет открытого релея — принимаем только от MYNETWORKS
|
||||
POSTFIX_smtpd_relay_restrictions: "permit_mynetworks reject"
|
||||
POSTFIX_smtpd_recipient_restrictions: "permit_mynetworks reject_unauth_destination"
|
||||
|
||||
secret:
|
||||
RELAYHOST_PASSWORD: "{{ smtp_relay_password }}"
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: "{{ smtp_relay_resources.requests.cpu }}"
|
||||
memory: "{{ smtp_relay_resources.requests.memory }}"
|
||||
limits:
|
||||
cpu: "{{ smtp_relay_resources.limits.cpu }}"
|
||||
memory: "{{ smtp_relay_resources.limits.memory }}"
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 25
|
||||
284
addons/vault/README.md
Normal file
284
addons/vault/README.md
Normal file
@@ -0,0 +1,284 @@
|
||||
# HashiCorp Vault
|
||||
|
||||
Self-hosted менеджер секретов с шифрованием, аудитом и fine-grained политиками доступа.
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
# Standalone (по умолчанию, 1 Pod)
|
||||
make addon-vault
|
||||
|
||||
# HA (3 Pods с Raft, нужны минимум 3 ноды)
|
||||
make addon-vault ARGS="-e vault_mode=ha"
|
||||
|
||||
# С авто-unseal через k8s Secret (homelab/dev)
|
||||
make addon-vault ARGS="-e vault_auto_unseal_type=k8s"
|
||||
|
||||
# С AWS KMS авто-unseal (production)
|
||||
make addon-vault ARGS="-e vault_auto_unseal_type=aws -e vault_aws_kms_region=us-east-1 -e vault_aws_kms_key_id=..."
|
||||
|
||||
# С Ingress
|
||||
make addon-vault ARGS="-e vault_ingress_enabled=true -e vault_ingress_host=vault.example.com"
|
||||
```
|
||||
|
||||
## Первичная инициализация (ручной режим)
|
||||
|
||||
После установки Vault нужно инициализировать (только один раз):
|
||||
|
||||
```bash
|
||||
# Инициализация (генерирует unseal keys + root token)
|
||||
kubectl exec -n vault vault-0 -- vault operator init \
|
||||
-key-shares=5 \
|
||||
-key-threshold=3
|
||||
|
||||
# ВАЖНО: сохрани 5 unseal keys и root token в надёжное место!
|
||||
# Пример вывода:
|
||||
# Unseal Key 1: abc...
|
||||
# Unseal Key 2: def...
|
||||
# ...
|
||||
# Initial Root Token: hvs.xxxxx
|
||||
|
||||
# Unseal (нужно подать 3 из 5 ключей)
|
||||
kubectl exec -n vault vault-0 -- vault operator unseal <key1>
|
||||
kubectl exec -n vault vault-0 -- vault operator unseal <key2>
|
||||
kubectl exec -n vault vault-0 -- vault operator unseal <key3>
|
||||
|
||||
# Проверка
|
||||
kubectl exec -n vault vault-0 -- vault status
|
||||
```
|
||||
|
||||
## Авто-unseal режимы
|
||||
|
||||
### k8s Secret (homelab, не для production)
|
||||
```bash
|
||||
make addon-vault ARGS="-e vault_auto_unseal_type=k8s"
|
||||
```
|
||||
Vault автоматически инициализируется и unseal keys сохраняются в k8s Secret `vault-unseal-keys`.
|
||||
Unsealer Deployment следит за состоянием и unseals при перезапуске.
|
||||
|
||||
**Получить root token:**
|
||||
```bash
|
||||
kubectl get secret vault-unseal-keys -n vault \
|
||||
-o jsonpath='{.data.root_token}' | base64 -d
|
||||
```
|
||||
|
||||
### AWS KMS (production)
|
||||
```yaml
|
||||
# group_vars/all/addons.yml
|
||||
vault_auto_unseal_type: "aws"
|
||||
vault_aws_kms_region: "us-east-1"
|
||||
vault_aws_kms_key_id: "arn:aws:kms:us-east-1:..."
|
||||
|
||||
# group_vars/all/vault.yml
|
||||
vault_aws_kms_access_key: "AKIAIOSFODNN7EXAMPLE"
|
||||
vault_aws_kms_secret_key: "wJalrXUtnFEMI/K7MDENG/..."
|
||||
```
|
||||
|
||||
### Transit Seal (через другой Vault)
|
||||
```yaml
|
||||
vault_auto_unseal_type: "transit"
|
||||
vault_transit_address: "https://vault-primary.example.com"
|
||||
vault_transit_key_name: "autounseal"
|
||||
# vault.yml:
|
||||
vault_transit_seal_token: "hvs.CAESIxxxxxx"
|
||||
```
|
||||
|
||||
## Работа с секретами
|
||||
|
||||
### CLI (локально через port-forward)
|
||||
```bash
|
||||
# Port-forward
|
||||
kubectl port-forward -n vault svc/vault 8200:8200 &
|
||||
|
||||
# Авторизация
|
||||
export VAULT_ADDR=http://localhost:8200
|
||||
vault login <root_token>
|
||||
|
||||
# Включить KV v2 engine
|
||||
vault secrets enable -path=secret kv-v2
|
||||
|
||||
# Записать секрет
|
||||
vault kv put secret/myapp/config \
|
||||
db_password="supersecret" \
|
||||
api_key="abc123"
|
||||
|
||||
# Прочитать секрет
|
||||
vault kv get secret/myapp/config
|
||||
vault kv get -field=db_password secret/myapp/config
|
||||
```
|
||||
|
||||
### CLI (из пода в кластере)
|
||||
```bash
|
||||
kubectl exec -n vault vault-0 -- env VAULT_ADDR=http://localhost:8200 \
|
||||
vault kv put secret/myapp/config db_password="supersecret"
|
||||
```
|
||||
|
||||
## Kubernetes Auth Method
|
||||
|
||||
Позволяет подам авторизоваться через их ServiceAccount токен:
|
||||
|
||||
```bash
|
||||
# Включить kubernetes auth
|
||||
vault auth enable kubernetes
|
||||
|
||||
# Настроить
|
||||
vault write auth/kubernetes/config \
|
||||
kubernetes_host="https://kubernetes.default.svc.cluster.local:443"
|
||||
|
||||
# Создать политику доступа
|
||||
vault policy write myapp-policy - <<EOF
|
||||
path "secret/data/myapp/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
|
||||
# Создать роль (привязка: namespace + serviceaccount → policy)
|
||||
vault write auth/kubernetes/role/myapp-role \
|
||||
bound_service_account_names="myapp-sa" \
|
||||
bound_service_account_namespaces="myapp" \
|
||||
policies="myapp-policy" \
|
||||
ttl="1h"
|
||||
```
|
||||
|
||||
## Vault Agent Injector — секреты в YAML манифестах
|
||||
|
||||
Injector (установлен по умолчанию) автоматически монтирует секреты через sidecar по annotations.
|
||||
|
||||
### Пример Pod с автоинжекцией секретов
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: myapp
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
# Включаем инжектор
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
# Адрес Vault (по умолчанию авто-определяется)
|
||||
vault.hashicorp.com/role: "myapp-role"
|
||||
|
||||
# Инжектировать секрет как файл /vault/secrets/config
|
||||
vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp/config"
|
||||
|
||||
# Шаблон (опционально — форматирует вывод)
|
||||
vault.hashicorp.com/agent-inject-template-config: |
|
||||
{{- with secret "secret/data/myapp/config" -}}
|
||||
DB_PASSWORD={{ .Data.data.db_password }}
|
||||
API_KEY={{ .Data.data.api_key }}
|
||||
{{- end }}
|
||||
spec:
|
||||
serviceAccountName: myapp-sa # должен совпадать с vault role
|
||||
containers:
|
||||
- name: app
|
||||
image: myapp:latest
|
||||
command: ["sh", "-c", "source /vault/secrets/config && ./myapp"]
|
||||
```
|
||||
|
||||
### Создать ServiceAccount для пода
|
||||
```bash
|
||||
kubectl create serviceaccount myapp-sa -n myapp
|
||||
```
|
||||
|
||||
### Инжекция как переменные окружения
|
||||
```yaml
|
||||
annotations:
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/role: "myapp-role"
|
||||
vault.hashicorp.com/agent-inject-secret-env: "secret/data/myapp/config"
|
||||
vault.hashicorp.com/agent-inject-template-env: |
|
||||
{{- with secret "secret/data/myapp/config" -}}
|
||||
export DB_PASSWORD="{{ .Data.data.db_password }}"
|
||||
export API_KEY="{{ .Data.data.api_key }}"
|
||||
{{- end }}
|
||||
```
|
||||
```yaml
|
||||
# В контейнере:
|
||||
command: ["/bin/sh", "-c", "source /vault/secrets/env && exec myapp"]
|
||||
```
|
||||
|
||||
## Vault в Helm чартах
|
||||
|
||||
### Через Vault Agent (annotations в values.yaml)
|
||||
```yaml
|
||||
# values.yaml вашего чарта
|
||||
podAnnotations:
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/role: "myapp-role"
|
||||
vault.hashicorp.com/agent-inject-secret-db: "secret/data/myapp/db"
|
||||
vault.hashicorp.com/agent-inject-template-db: |
|
||||
{{- with secret "secret/data/myapp/db" -}}
|
||||
{{ .Data.data.password }}
|
||||
{{- end }}
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
name: myapp-sa
|
||||
```
|
||||
|
||||
### Через External Secrets Operator (рекомендуется — см. ESO README)
|
||||
```yaml
|
||||
# Helm чарт не меняется — ESO создаёт k8s Secret автоматически
|
||||
# Используй стандартный envFrom/secretRef в values.yaml
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: myapp-secrets # создан ExternalSecret → Vault
|
||||
```
|
||||
|
||||
## AppRole Auth (для сервисов без k8s ServiceAccount)
|
||||
|
||||
```bash
|
||||
# Включить AppRole
|
||||
vault auth enable approle
|
||||
|
||||
# Создать роль
|
||||
vault write auth/approle/role/myservice \
|
||||
secret_id_ttl="720h" \
|
||||
token_ttl="1h" \
|
||||
token_policies="myapp-policy"
|
||||
|
||||
# Получить Role ID (не секретный)
|
||||
vault read auth/approle/role/myservice/role-id
|
||||
|
||||
# Получить Secret ID (секретный, одноразовый)
|
||||
vault write -f auth/approle/role/myservice/secret-id
|
||||
|
||||
# Авторизация через AppRole
|
||||
vault write auth/approle/login \
|
||||
role_id="<role_id>" \
|
||||
secret_id="<secret_id>"
|
||||
```
|
||||
|
||||
## Аудит и мониторинг
|
||||
|
||||
```bash
|
||||
# Включить аудит лог в stdout
|
||||
vault audit enable file file_path=stdout
|
||||
|
||||
# Статус кластера (HA)
|
||||
kubectl exec -n vault vault-0 -- vault operator members
|
||||
|
||||
# Grafana: метрики доступны если addon_prometheus_stack: true
|
||||
# Дашборд: импортируй ID 12904 из Grafana.com
|
||||
```
|
||||
|
||||
## Полезные команды
|
||||
|
||||
```bash
|
||||
# Статус
|
||||
kubectl exec -n vault vault-0 -- vault status
|
||||
|
||||
# Список engines
|
||||
kubectl exec -n vault vault-0 -- vault secrets list
|
||||
|
||||
# Список auth methods
|
||||
kubectl exec -n vault vault-0 -- vault auth list
|
||||
|
||||
# Seal (экстренная блокировка)
|
||||
kubectl exec -n vault vault-0 -- vault operator seal
|
||||
|
||||
# Raft cluster (HA)
|
||||
kubectl exec -n vault vault-0 -- vault operator raft list-peers
|
||||
```
|
||||
7
addons/vault/playbook.yml
Normal file
7
addons/vault/playbook.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Install HashiCorp Vault
|
||||
hosts: k3s_master[0]
|
||||
gather_facts: false
|
||||
become: true
|
||||
roles:
|
||||
- role: "{{ playbook_dir }}/../addons/vault/role"
|
||||
76
addons/vault/role/defaults/main.yml
Normal file
76
addons/vault/role/defaults/main.yml
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
vault_version: "" # "" = автоматически последняя версия чарта
|
||||
vault_namespace: "vault"
|
||||
vault_chart_repo: "https://helm.releases.hashicorp.com"
|
||||
|
||||
# ── Режим развёртывания ───────────────────────────────────────────────────────
|
||||
# standalone — 1 Pod с Raft storage (по умолчанию, подходит для dev/prod single-node)
|
||||
# ha — 3 Pod с Raft (для продакшн HA кластера, требует минимум 3 ноды)
|
||||
vault_mode: "standalone"
|
||||
vault_ha_replicas: 3
|
||||
|
||||
# ── Vault Agent Injector ──────────────────────────────────────────────────────
|
||||
# Инжектирует секреты в поды через sidecar (annotations: vault.hashicorp.com/...)
|
||||
vault_injector_enabled: true
|
||||
|
||||
# ── Auto-unseal ───────────────────────────────────────────────────────────────
|
||||
# none — ручной unseal через CLI/UI (по умолчанию)
|
||||
# k8s — хранит unseal-ключи в k8s Secret + Deployment для авто-unseal (homelab)
|
||||
# aws — AWS KMS (рекомендуется для продакшн)
|
||||
# gcp — GCP Cloud KMS
|
||||
# azure — Azure Key Vault
|
||||
# transit — Vault Transit Seal (другой экземпляр Vault)
|
||||
vault_auto_unseal_type: "none"
|
||||
|
||||
# K8S auto-unseal (vault_auto_unseal_type: k8s)
|
||||
# ВНИМАНИЕ: unseal-ключи хранятся в k8s Secret — используй только в dev/homelab!
|
||||
vault_auto_unseal_k8s_secret: "vault-unseal-keys"
|
||||
vault_auto_unseal_k8s_shares: 5 # общее количество ключей
|
||||
vault_auto_unseal_k8s_threshold: 3 # минимум для unseal
|
||||
|
||||
# AWS KMS auto-unseal (vault_auto_unseal_type: aws)
|
||||
vault_aws_kms_region: ""
|
||||
vault_aws_kms_key_id: ""
|
||||
vault_aws_access_key: "{{ vault_aws_kms_access_key | default('') }}"
|
||||
vault_aws_secret_key: "{{ vault_aws_kms_secret_key | default('') }}"
|
||||
|
||||
# GCP Cloud KMS auto-unseal (vault_auto_unseal_type: gcp)
|
||||
vault_gcp_project: ""
|
||||
vault_gcp_region: ""
|
||||
vault_gcp_key_ring: ""
|
||||
vault_gcp_crypto_key: ""
|
||||
|
||||
# Azure Key Vault auto-unseal (vault_auto_unseal_type: azure)
|
||||
vault_azure_tenant_id: ""
|
||||
vault_azure_client_id: ""
|
||||
vault_azure_client_secret: "{{ vault_azure_kv_client_secret | default('') }}"
|
||||
vault_azure_keyvault_name: ""
|
||||
vault_azure_key_name: ""
|
||||
|
||||
# Vault Transit Seal (vault_auto_unseal_type: transit)
|
||||
vault_transit_address: ""
|
||||
vault_transit_token: "{{ vault_transit_seal_token | default('') }}"
|
||||
vault_transit_key_name: "autounseal"
|
||||
|
||||
# ── Ingress ───────────────────────────────────────────────────────────────────
|
||||
vault_ingress_enabled: false
|
||||
vault_ingress_host: "vault-hc.example.com"
|
||||
vault_ingress_class: "{{ ingress_nginx_class_name | default('nginx') }}"
|
||||
vault_ingress_tls: true
|
||||
vault_ingress_cert_issuer: "{{ cert_manager_default_issuer_name | default('letsencrypt-prod') }}"
|
||||
|
||||
# ── Хранилище ─────────────────────────────────────────────────────────────────
|
||||
vault_storage_size: "10Gi"
|
||||
vault_storage_class: ""
|
||||
|
||||
# ── Метрики ───────────────────────────────────────────────────────────────────
|
||||
vault_metrics_enabled: true
|
||||
|
||||
# ── Ресурсы ───────────────────────────────────────────────────────────────────
|
||||
vault_resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 512Mi
|
||||
130
addons/vault/role/tasks/main.yml
Normal file
130
addons/vault/role/tasks/main.yml
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
- name: Add HashiCorp Helm repo
|
||||
kubernetes.core.helm_repository:
|
||||
name: hashicorp
|
||||
repo_url: "{{ vault_chart_repo }}"
|
||||
environment:
|
||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
||||
|
||||
- name: Get latest Vault chart version
|
||||
ansible.builtin.shell: |
|
||||
helm search repo hashicorp/vault --output json | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin)[0]['version'])"
|
||||
register: _vault_latest_version
|
||||
changed_when: false
|
||||
when: vault_version == ""
|
||||
environment:
|
||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
||||
|
||||
- name: Set Vault chart version
|
||||
ansible.builtin.set_fact:
|
||||
_vault_version: "{{ vault_version if vault_version != '' else _vault_latest_version.stdout | trim }}"
|
||||
|
||||
- name: Template Vault values
|
||||
ansible.builtin.template:
|
||||
src: vault-values.yaml.j2
|
||||
dest: /tmp/vault-values.yaml
|
||||
mode: '0600'
|
||||
|
||||
- name: Install HashiCorp Vault via Helm
|
||||
kubernetes.core.helm:
|
||||
name: vault
|
||||
chart_ref: hashicorp/vault
|
||||
chart_version: "{{ _vault_version }}"
|
||||
release_namespace: "{{ vault_namespace }}"
|
||||
create_namespace: true
|
||||
wait: true
|
||||
timeout: "5m0s"
|
||||
values_files:
|
||||
- /tmp/vault-values.yaml
|
||||
environment:
|
||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
||||
|
||||
- name: Wait for Vault pods to be Running
|
||||
ansible.builtin.command: >
|
||||
k3s kubectl -n {{ vault_namespace }}
|
||||
wait pod -l app.kubernetes.io/name=vault
|
||||
--for=condition=Ready --timeout=120s
|
||||
changed_when: false
|
||||
retries: 5
|
||||
delay: 15
|
||||
failed_when: false
|
||||
|
||||
# ─── K8S AUTO-UNSEAL (только при vault_auto_unseal_type: k8s) ────────────────
|
||||
- name: Template Vault init Job (k8s auto-unseal)
|
||||
ansible.builtin.template:
|
||||
src: vault-init-job.yaml.j2
|
||||
dest: /tmp/vault-init-job.yaml
|
||||
mode: '0644'
|
||||
when: vault_auto_unseal_type == "k8s"
|
||||
|
||||
- name: Delete previous vault-init Job if exists
|
||||
ansible.builtin.command: >
|
||||
k3s kubectl delete job vault-init -n {{ vault_namespace }} --ignore-not-found
|
||||
changed_when: false
|
||||
when: vault_auto_unseal_type == "k8s"
|
||||
|
||||
- name: Apply Vault init Job
|
||||
ansible.builtin.command: k3s kubectl apply -f /tmp/vault-init-job.yaml
|
||||
changed_when: true
|
||||
when: vault_auto_unseal_type == "k8s"
|
||||
|
||||
- name: Wait for Vault init Job to complete
|
||||
ansible.builtin.command: >
|
||||
k3s kubectl -n {{ vault_namespace }}
|
||||
wait job/vault-init --for=condition=complete --timeout=300s
|
||||
changed_when: false
|
||||
retries: 3
|
||||
delay: 10
|
||||
when: vault_auto_unseal_type == "k8s"
|
||||
|
||||
- name: Template Vault unsealer Deployment
|
||||
ansible.builtin.template:
|
||||
src: vault-unsealer.yaml.j2
|
||||
dest: /tmp/vault-unsealer.yaml
|
||||
mode: '0644'
|
||||
when: vault_auto_unseal_type == "k8s"
|
||||
|
||||
- name: Apply Vault unsealer Deployment
|
||||
ansible.builtin.command: k3s kubectl apply -f /tmp/vault-unsealer.yaml
|
||||
changed_when: true
|
||||
when: vault_auto_unseal_type == "k8s"
|
||||
|
||||
# ─── Получаем статус ─────────────────────────────────────────────────────────
|
||||
- name: Check Vault init status
|
||||
ansible.builtin.command: >
|
||||
k3s kubectl -n {{ vault_namespace }}
|
||||
exec vault-0 -- vault status -format=json
|
||||
register: _vault_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Parse Vault status
|
||||
ansible.builtin.set_fact:
|
||||
_vault_initialized: "{{ (_vault_status.stdout | from_json).initialized | default(false) }}"
|
||||
_vault_sealed: "{{ (_vault_status.stdout | from_json).sealed | default(true) }}"
|
||||
when: _vault_status.rc == 0
|
||||
failed_when: false
|
||||
|
||||
- name: Show HashiCorp Vault access info
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "HashiCorp Vault установлен в namespace: {{ vault_namespace }}"
|
||||
- "Режим: {{ vault_mode }}{% if vault_mode == 'ha' %} ({{ vault_ha_replicas }} реплики, Raft){% endif %}"
|
||||
- "Auto-unseal: {{ vault_auto_unseal_type }}"
|
||||
- "Инициализирован: {{ _vault_initialized | default('неизвестно') }}"
|
||||
- "Sealed: {{ _vault_sealed | default('неизвестно') }}"
|
||||
- ""
|
||||
- "{% if vault_ingress_enabled %}UI: https://{{ vault_ingress_host }}{% else %}UI (port-forward): kubectl port-forward -n {{ vault_namespace }} svc/vault 8200:8200{% endif %}"
|
||||
- "Internal API: http://vault.{{ vault_namespace }}.svc.cluster.local:8200"
|
||||
- ""
|
||||
- "{% if vault_auto_unseal_type == 'none' %}=== РУЧНАЯ ИНИЦИАЛИЗАЦИЯ (если ещё не выполнена) ==="
|
||||
- " kubectl exec -n {{ vault_namespace }} vault-0 -- vault operator init"
|
||||
- " # Сохрани unseal keys и root token!"
|
||||
- " kubectl exec -n {{ vault_namespace }} vault-0 -- vault operator unseal <key1>"
|
||||
- " kubectl exec -n {{ vault_namespace }} vault-0 -- vault operator unseal <key2>"
|
||||
- " kubectl exec -n {{ vault_namespace }} vault-0 -- vault operator unseal <key3>"
|
||||
- "{% elif vault_auto_unseal_type == 'k8s' %}Ключи сохранены в Secret: {{ vault_auto_unseal_k8s_secret }} (namespace: {{ vault_namespace }})"
|
||||
- " kubectl get secret {{ vault_auto_unseal_k8s_secret }} -n {{ vault_namespace }} -o jsonpath='{.data.root_token}' | base64 -d"
|
||||
- "{% endif %}"
|
||||
- "Документация: addons/vault/README.md"
|
||||
143
addons/vault/role/templates/vault-init-job.yaml.j2
Normal file
143
addons/vault/role/templates/vault-init-job.yaml.j2
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
# RBAC для Job инициализации и авто-unseal
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: vault-init
|
||||
namespace: {{ vault_namespace }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: vault-init
|
||||
namespace: {{ vault_namespace }}
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "create", "update", "patch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: vault-init
|
||||
namespace: {{ vault_namespace }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: vault-init
|
||||
namespace: {{ vault_namespace }}
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: vault-init
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
# Job: инициализирует Vault и сохраняет ключи в k8s Secret
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: vault-init
|
||||
namespace: {{ vault_namespace }}
|
||||
labels:
|
||||
app: vault-init
|
||||
spec:
|
||||
backoffLimit: 5
|
||||
ttlSecondsAfterFinished: 300
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: vault-init
|
||||
spec:
|
||||
serviceAccountName: vault-init
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: vault-init
|
||||
image: hashicorp/vault:latest
|
||||
env:
|
||||
- name: VAULT_ADDR
|
||||
value: "http://vault.{{ vault_namespace }}.svc.cluster.local:8200"
|
||||
- name: VAULT_NAMESPACE
|
||||
value: "{{ vault_namespace }}"
|
||||
- name: UNSEAL_SECRET_NAME
|
||||
value: "{{ vault_auto_unseal_k8s_secret }}"
|
||||
- name: KEY_SHARES
|
||||
value: "{{ vault_auto_unseal_k8s_shares }}"
|
||||
- name: KEY_THRESHOLD
|
||||
value: "{{ vault_auto_unseal_k8s_threshold }}"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
echo "Waiting for Vault to start..."
|
||||
until vault status 2>&1 | grep -q "Initialized"; do
|
||||
echo " Vault not ready yet, waiting..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# Проверяем: уже инициализирован?
|
||||
INIT_STATUS=$(vault status -format=json 2>/dev/null | grep -o '"initialized":[^,}]*' | grep -o '[^:]*$' | tr -d ' ')
|
||||
if [ "$INIT_STATUS" = "true" ]; then
|
||||
echo "Vault is already initialized."
|
||||
# Проверяем: уже unlocked (секрет существует)?
|
||||
if kubectl get secret "$UNSEAL_SECRET_NAME" -n "$VAULT_NAMESPACE" >/dev/null 2>&1; then
|
||||
echo "Unseal secret already exists. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
echo "WARNING: Vault initialized but unseal keys not found in cluster. Manual unseal required."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Initializing Vault with $KEY_SHARES shares, threshold $KEY_THRESHOLD..."
|
||||
INIT_OUTPUT=$(vault operator init \
|
||||
-key-shares="$KEY_SHARES" \
|
||||
-key-threshold="$KEY_THRESHOLD" \
|
||||
-format=json)
|
||||
|
||||
echo "Saving unseal keys and root token to Secret '$UNSEAL_SECRET_NAME'..."
|
||||
# Формируем Secret с ключами
|
||||
ROOT_TOKEN=$(echo "$INIT_OUTPUT" | grep -o '"root_token":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
# Создаём kubectl secret
|
||||
SECRET_ARGS="--from-literal=root_token=$ROOT_TOKEN"
|
||||
i=1
|
||||
echo "$INIT_OUTPUT" | grep -o '"unseal_keys_b64":\[[^]]*\]' | grep -o '"[A-Za-z0-9+/=]*"' | while read KEY; do
|
||||
KEY_VAL=$(echo "$KEY" | tr -d '"')
|
||||
SECRET_ARGS="$SECRET_ARGS --from-literal=unseal_key_$i=$KEY_VAL"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
# Используем python для надёжного парсинга JSON
|
||||
python3 - <<PYEOF
|
||||
import json, subprocess, sys
|
||||
|
||||
data = json.loads("""$INIT_OUTPUT""")
|
||||
root_token = data['root_token']
|
||||
keys = data['unseal_keys_b64']
|
||||
|
||||
args = ['kubectl', 'create', 'secret', 'generic', '$UNSEAL_SECRET_NAME',
|
||||
'-n', '$VAULT_NAMESPACE',
|
||||
f'--from-literal=root_token={root_token}']
|
||||
for i, key in enumerate(keys, 1):
|
||||
args.append(f'--from-literal=unseal_key_{i}={key}')
|
||||
|
||||
result = subprocess.run(args, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"Error: {result.stderr}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(f"Secret created with {len(keys)} unseal keys.")
|
||||
PYEOF
|
||||
|
||||
echo ""
|
||||
echo "=== Unsealing Vault ==="
|
||||
THRESHOLD={{ vault_auto_unseal_k8s_threshold }}
|
||||
for i in $(seq 1 $THRESHOLD); do
|
||||
KEY=$(kubectl get secret "$UNSEAL_SECRET_NAME" -n "$VAULT_NAMESPACE" \
|
||||
-o jsonpath="{.data.unseal_key_$i}" | base64 -d)
|
||||
vault operator unseal "$KEY"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Vault initialized and unsealed ==="
|
||||
echo "Root token saved in Secret: $UNSEAL_SECRET_NAME (key: root_token)"
|
||||
echo "ВАЖНО: Root token предназначен только для первичной настройки!"
|
||||
echo " Создай AppRole/UserPass и отзови root token: vault token revoke <root_token>"
|
||||
90
addons/vault/role/templates/vault-unsealer.yaml.j2
Normal file
90
addons/vault/role/templates/vault-unsealer.yaml.j2
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
# RBAC для авто-unsealer Deployment
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: vault-unsealer
|
||||
namespace: {{ vault_namespace }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: vault-unsealer
|
||||
namespace: {{ vault_namespace }}
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: vault-unsealer
|
||||
namespace: {{ vault_namespace }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: vault-unsealer
|
||||
namespace: {{ vault_namespace }}
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: vault-unsealer
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
# Deployment: следит за Vault и unseals при перезапуске
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vault-unsealer
|
||||
namespace: {{ vault_namespace }}
|
||||
labels:
|
||||
app: vault-unsealer
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: vault-unsealer
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: vault-unsealer
|
||||
spec:
|
||||
serviceAccountName: vault-unsealer
|
||||
containers:
|
||||
- name: unsealer
|
||||
image: hashicorp/vault:latest
|
||||
env:
|
||||
- name: VAULT_ADDR
|
||||
value: "http://vault.{{ vault_namespace }}.svc.cluster.local:8200"
|
||||
- name: VAULT_NAMESPACE_K8S
|
||||
value: "{{ vault_namespace }}"
|
||||
- name: UNSEAL_SECRET_NAME
|
||||
value: "{{ vault_auto_unseal_k8s_secret }}"
|
||||
- name: KEY_THRESHOLD
|
||||
value: "{{ vault_auto_unseal_k8s_threshold }}"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
while true; do
|
||||
STATUS=$(vault status -format=json 2>/dev/null)
|
||||
if echo "$STATUS" | grep -q '"sealed":true'; then
|
||||
echo "$(date): Vault is sealed — unsealing..."
|
||||
for i in $(seq 1 $KEY_THRESHOLD); do
|
||||
KEY=$(kubectl get secret "$UNSEAL_SECRET_NAME" \
|
||||
-n "$VAULT_NAMESPACE_K8S" \
|
||||
-o "jsonpath={.data.unseal_key_$i}" 2>/dev/null | base64 -d)
|
||||
if [ -n "$KEY" ]; then
|
||||
vault operator unseal "$KEY" >/dev/null
|
||||
fi
|
||||
done
|
||||
echo "$(date): Unseal applied ($KEY_THRESHOLD keys)."
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 64Mi
|
||||
181
addons/vault/role/templates/vault-values.yaml.j2
Normal file
181
addons/vault/role/templates/vault-values.yaml.j2
Normal file
@@ -0,0 +1,181 @@
|
||||
global:
|
||||
enabled: true
|
||||
|
||||
injector:
|
||||
enabled: {{ vault_injector_enabled | lower }}
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 128Mi
|
||||
|
||||
server:
|
||||
resources:
|
||||
requests:
|
||||
cpu: "{{ vault_resources.requests.cpu }}"
|
||||
memory: "{{ vault_resources.requests.memory }}"
|
||||
limits:
|
||||
cpu: "{{ vault_resources.limits.cpu }}"
|
||||
memory: "{{ vault_resources.limits.memory }}"
|
||||
|
||||
dataStorage:
|
||||
enabled: true
|
||||
size: "{{ vault_storage_size }}"
|
||||
{% if vault_storage_class %}
|
||||
storageClass: "{{ vault_storage_class }}"
|
||||
{% endif %}
|
||||
|
||||
serviceMonitor:
|
||||
enabled: {{ (vault_metrics_enabled | bool and addon_prometheus_stack | default(false) | bool) | lower }}
|
||||
|
||||
{% if vault_ingress_enabled | bool %}
|
||||
ingress:
|
||||
enabled: true
|
||||
ingressClassName: "{{ vault_ingress_class }}"
|
||||
hosts:
|
||||
- host: "{{ vault_ingress_host }}"
|
||||
paths:
|
||||
- /
|
||||
{% if vault_ingress_tls | bool %}
|
||||
tls:
|
||||
- secretName: vault-tls
|
||||
hosts:
|
||||
- "{{ vault_ingress_host }}"
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "{{ vault_ingress_cert_issuer }}"
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if vault_auto_unseal_type == "aws" %}
|
||||
extraEnvironmentVars:
|
||||
AWS_ACCESS_KEY_ID: "{{ vault_aws_access_key }}"
|
||||
AWS_SECRET_ACCESS_KEY: "{{ vault_aws_secret_key }}"
|
||||
{% elif vault_auto_unseal_type == "gcp" %}
|
||||
extraVolumes:
|
||||
- type: secret
|
||||
name: vault-gcp-credentials
|
||||
extraEnvironmentVars:
|
||||
GOOGLE_APPLICATION_CREDENTIALS: "/vault/userconfig/vault-gcp-credentials/credentials.json"
|
||||
{% elif vault_auto_unseal_type == "azure" %}
|
||||
extraEnvironmentVars:
|
||||
AZURE_TENANT_ID: "{{ vault_azure_tenant_id }}"
|
||||
AZURE_CLIENT_ID: "{{ vault_azure_client_id }}"
|
||||
AZURE_CLIENT_SECRET: "{{ vault_azure_client_secret }}"
|
||||
{% endif %}
|
||||
|
||||
{% if vault_mode == "standalone" %}
|
||||
standalone:
|
||||
enabled: true
|
||||
config: |
|
||||
ui = true
|
||||
|
||||
listener "tcp" {
|
||||
tls_disable = 1
|
||||
address = "[::]:8200"
|
||||
cluster_address = "[::]:8201"
|
||||
}
|
||||
|
||||
storage "file" {
|
||||
path = "/vault/data"
|
||||
}
|
||||
{% if vault_auto_unseal_type == "aws" %}
|
||||
|
||||
seal "awskms" {
|
||||
region = "{{ vault_aws_kms_region }}"
|
||||
kms_key_id = "{{ vault_aws_kms_key_id }}"
|
||||
}
|
||||
{% elif vault_auto_unseal_type == "gcp" %}
|
||||
|
||||
seal "gcpckms" {
|
||||
project = "{{ vault_gcp_project }}"
|
||||
region = "{{ vault_gcp_region }}"
|
||||
key_ring = "{{ vault_gcp_key_ring }}"
|
||||
crypto_key = "{{ vault_gcp_crypto_key }}"
|
||||
}
|
||||
{% elif vault_auto_unseal_type == "azure" %}
|
||||
|
||||
seal "azurekeyvault" {
|
||||
vault_name = "{{ vault_azure_keyvault_name }}"
|
||||
key_name = "{{ vault_azure_key_name }}"
|
||||
}
|
||||
{% elif vault_auto_unseal_type == "transit" %}
|
||||
|
||||
seal "transit" {
|
||||
address = "{{ vault_transit_address }}"
|
||||
token = "{{ vault_transit_token }}"
|
||||
key_name = "{{ vault_transit_key_name }}"
|
||||
mount_path = "transit/"
|
||||
disable_renewal = "false"
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
ha:
|
||||
enabled: false
|
||||
{% endif %}
|
||||
|
||||
{% if vault_mode == "ha" %}
|
||||
standalone:
|
||||
enabled: false
|
||||
|
||||
ha:
|
||||
enabled: true
|
||||
replicas: {{ vault_ha_replicas }}
|
||||
raft:
|
||||
enabled: true
|
||||
setNodeId: true
|
||||
config: |
|
||||
ui = true
|
||||
|
||||
listener "tcp" {
|
||||
tls_disable = 1
|
||||
address = "[::]:8200"
|
||||
cluster_address = "[::]:8201"
|
||||
}
|
||||
|
||||
storage "raft" {
|
||||
path = "/vault/data"
|
||||
{% for i in range(vault_ha_replicas) %}
|
||||
retry_join {
|
||||
leader_api_addr = "http://vault-{{ i }}.vault-internal:8200"
|
||||
}
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
service_registration "kubernetes" {}
|
||||
{% if vault_auto_unseal_type == "aws" %}
|
||||
|
||||
seal "awskms" {
|
||||
region = "{{ vault_aws_kms_region }}"
|
||||
kms_key_id = "{{ vault_aws_kms_key_id }}"
|
||||
}
|
||||
{% elif vault_auto_unseal_type == "gcp" %}
|
||||
|
||||
seal "gcpckms" {
|
||||
project = "{{ vault_gcp_project }}"
|
||||
region = "{{ vault_gcp_region }}"
|
||||
key_ring = "{{ vault_gcp_key_ring }}"
|
||||
crypto_key = "{{ vault_gcp_crypto_key }}"
|
||||
}
|
||||
{% elif vault_auto_unseal_type == "azure" %}
|
||||
|
||||
seal "azurekeyvault" {
|
||||
vault_name = "{{ vault_azure_keyvault_name }}"
|
||||
key_name = "{{ vault_azure_key_name }}"
|
||||
}
|
||||
{% elif vault_auto_unseal_type == "transit" %}
|
||||
|
||||
seal "transit" {
|
||||
address = "{{ vault_transit_address }}"
|
||||
token = "{{ vault_transit_token }}"
|
||||
key_name = "{{ vault_transit_key_name }}"
|
||||
mount_path = "transit/"
|
||||
disable_renewal = "false"
|
||||
}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
ui:
|
||||
enabled: true
|
||||
serviceType: ClusterIP
|
||||
@@ -33,6 +33,9 @@ addon_csi_s3: false # CSI S3 Driver (объектное хран
|
||||
addon_csi_ceph: false # CSI Ceph / Rook-Ceph (distributed block + filesystem storage)
|
||||
addon_csi_glusterfs: false # CSI GlusterFS Driver (требует внешний GlusterFS + Heketi)
|
||||
addon_vaultwarden: false # Vaultwarden (self-hosted Bitwarden-совместимый менеджер паролей)
|
||||
addon_smtp_relay: false # SMTP Relay (Postfix → Yandex/другой SMTP) — для уведомлений из подов
|
||||
addon_vault: false # HashiCorp Vault (секреты, PKI, динамические credentials)
|
||||
addon_external_secrets: false # External Secrets Operator → Vault/AWS/GCP (k8s Secret sync)
|
||||
|
||||
# ─── NFS Server ───────────────────────────────────────────────────────────────
|
||||
nfs_exports:
|
||||
@@ -287,6 +290,37 @@ minio_api_ingress_host: "s3.example.com"
|
||||
# vaultwarden_smtp_security: "force_tls" # force_tls | starttls | off
|
||||
# vaultwarden_smtp_username: "user@example.com"
|
||||
|
||||
# ─── SMTP Relay ───────────────────────────────────────────────────────────────
|
||||
# Postfix relay для отправки почты из подов через внешний SMTP (Yandex/Gmail/etc).
|
||||
# Пароль задаётся в vault.yml: vault_smtp_relay_password
|
||||
# Использование: SMTP_HOST=smtp-relay.smtp-relay.svc.cluster.local, SMTP_PORT=25
|
||||
# smtp_relay_host: "smtp.yandex.ru"
|
||||
# smtp_relay_port: 465
|
||||
# smtp_relay_username: "sergey@antropoff.ru"
|
||||
# smtp_relay_from: "vault@antropoff.ru"
|
||||
# smtp_relay_allowed_sender_domains: "antropoff.ru"
|
||||
# smtp_relay_mynetworks: "10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 127.0.0.0/8"
|
||||
|
||||
# ─── HashiCorp Vault ──────────────────────────────────────────────────────────
|
||||
# Менеджер секретов. README: addons/vault/README.md
|
||||
# vault_mode: "standalone" # standalone (1 pod) | ha (3 pods Raft)
|
||||
# vault_auto_unseal_type: "none" # none | k8s | aws | gcp | azure | transit
|
||||
# k8s: ключи в Secret (homelab), aws/gcp/azure/transit (production)
|
||||
# vault_injector_enabled: true # Vault Agent Injector (авто-инжекция в поды)
|
||||
# vault_ingress_enabled: false
|
||||
# vault_ingress_host: "vault-hc.example.com"
|
||||
# vault_storage_size: "10Gi"
|
||||
# Для AWS KMS unseal (vault.yml): vault_aws_kms_access_key, vault_aws_kms_secret_key
|
||||
|
||||
# ─── External Secrets Operator ────────────────────────────────────────────────
|
||||
# Синхронизирует секреты из Vault/AWS/GCP в k8s Secrets. README: addons/external-secrets/README.md
|
||||
# Требует предварительно созданного AppRole в Vault (шаги в README).
|
||||
# external_secrets_vault_url: "http://vault.vault.svc.cluster.local:8200"
|
||||
# external_secrets_vault_kv_path: "secret"
|
||||
# external_secrets_vault_role_id: "" # после создания AppRole в Vault
|
||||
# external_secrets_vault_store_name: "vault-backend"
|
||||
# Пароль задаётся в vault.yml: vault_eso_approle_secret_id
|
||||
|
||||
# ─── etcd backup ──────────────────────────────────────────────────────────────
|
||||
etcd_backup_dir: "{{ k3s_data_dir }}/server/db/snapshots"
|
||||
etcd_backup_retention: 5 # сколько снимков хранить
|
||||
|
||||
@@ -69,3 +69,22 @@ vault_vaultwarden_smtp_password: "fntwztnkacanpbwa" # пароль SMTP (Y
|
||||
|
||||
# ─── CSI GlusterFS / Heketi ────────────────────────────────────────────────────
|
||||
vault_glusterfs_heketi_secret: "changeme-heketi" # пароль Heketi admin
|
||||
|
||||
# ─── SMTP Relay ────────────────────────────────────────────────────────────────
|
||||
vault_smtp_relay_password: "fntwztnkacanpbwa" # Yandex App Password для sergey@antropoff.ru
|
||||
|
||||
# ─── HashiCorp Vault (auto-unseal: aws) ────────────────────────────────────────
|
||||
# Используется только при vault_auto_unseal_type: aws
|
||||
vault_aws_kms_access_key: "" # AWS IAM Access Key ID
|
||||
vault_aws_kms_secret_key: "" # AWS IAM Secret Access Key
|
||||
|
||||
# Vault Transit Seal (vault_auto_unseal_type: transit)
|
||||
vault_transit_seal_token: "" # token для доступа к transit engine другого Vault
|
||||
|
||||
# Azure Key Vault unseal (vault_auto_unseal_type: azure)
|
||||
vault_azure_kv_client_secret: ""
|
||||
|
||||
# ─── External Secrets Operator → HashiCorp Vault ───────────────────────────────
|
||||
# Получить после шага 6 в addons/external-secrets/README.md:
|
||||
# vault write -f -field=secret_id auth/approle/role/eso-role/secret-id
|
||||
vault_eso_approle_secret_id: "" # AppRole Secret ID для ESO → Vault
|
||||
|
||||
@@ -231,3 +231,27 @@
|
||||
when: addon_vaultwarden | default(false) | bool
|
||||
roles:
|
||||
- role: "{{ playbook_dir }}/../addons/vaultwarden/role"
|
||||
|
||||
- name: Install SMTP Relay
|
||||
hosts: k3s_master[0]
|
||||
gather_facts: false
|
||||
become: true
|
||||
when: addon_smtp_relay | default(false) | bool
|
||||
roles:
|
||||
- role: "{{ playbook_dir }}/../addons/smtp-relay/role"
|
||||
|
||||
- name: Install HashiCorp Vault
|
||||
hosts: k3s_master[0]
|
||||
gather_facts: false
|
||||
become: true
|
||||
when: addon_vault | default(false) | bool
|
||||
roles:
|
||||
- role: "{{ playbook_dir }}/../addons/vault/role"
|
||||
|
||||
- name: Install External Secrets Operator
|
||||
hosts: k3s_master[0]
|
||||
gather_facts: false
|
||||
become: true
|
||||
when: addon_external_secrets | default(false) | bool
|
||||
roles:
|
||||
- role: "{{ playbook_dir }}/../addons/external-secrets/role"
|
||||
|
||||
Reference in New Issue
Block a user