Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b6a285741 | ||
|
|
7cacd5a03f | ||
|
|
654803c2af | ||
|
|
b9022560f3 | ||
|
|
10c04ba32b | ||
|
|
32e4bc657a | ||
|
|
5e5148e951 | ||
|
|
eb112cd4dd | ||
|
|
56a75bf6a6 | ||
|
|
b7e7fc1c72 | ||
|
|
30f1d32fc5 | ||
|
|
2aaa45bf3c | ||
|
|
2f5e37f598 | ||
|
|
ae1a683ada | ||
|
|
ad7050ca7d | ||
|
|
5525cc33ff | ||
| e4e5e06aec | |||
| 9f2c6932d7 | |||
|
|
cd94931c76 | ||
|
|
104bde49c4 | ||
|
|
b61571a858 | ||
| 13b9ca8a9f | |||
|
|
31bdc74c80 | ||
|
|
9f4801af68 | ||
|
|
63b33aaafb | ||
|
|
fb52d422ca | ||
|
|
deb0d5f0eb | ||
| 1613172dd6 | |||
| b7c36a5348 | |||
| 0037411aae | |||
| c23b51e2a5 | |||
| 448a994fd4 | |||
|
|
bfad13da2f | ||
|
|
f936f5ffb0 | ||
|
|
25b3536124 | ||
|
|
cdc2c34bca | ||
|
|
3b53a99371 | ||
|
|
aa3365cb0d | ||
|
|
7cf8b13f91 | ||
|
|
83bfd4e046 | ||
| 1c3525fc1b | |||
| a3fa7ba4c0 | |||
| 32e4cff59e | |||
| 4a46bb6626 | |||
| 34cbe7c79e | |||
|
|
ae911c7d38 | ||
|
|
bd4b6d4ea6 | ||
|
|
56ade56754 | ||
|
|
8a3540ead7 | ||
|
|
3dadb7a87a | ||
|
|
fc8ddcb511 | ||
| f5a217e793 | |||
| eeb27c6e29 | |||
| 0fb5374e06 | |||
| 0a81938175 | |||
| 84f2555494 | |||
| faa1dd2cac | |||
|
|
f4a19774a2 | ||
|
|
f39a907a87 | ||
| c401cf0dcb | |||
|
|
1d349b1a8c | ||
|
|
f6d4c2face | ||
|
|
6fda7834ce | ||
|
|
0d4befa985 | ||
|
|
9f263a97f3 | ||
| b6f164f85e | |||
| 5e10d54d67 | |||
| f190a6efa5 | |||
| 5bccce5815 | |||
| 0c696d3a3e | |||
|
|
385abf1092 | ||
|
|
3f7628cc54 | ||
| 2182c09031 | |||
|
|
e33eaa1db8 | ||
|
|
cb99195564 | ||
|
|
f5ea728d29 | ||
| f86336741b | |||
| 4448188464 | |||
|
|
af56004006 | ||
| af112cfffe | |||
|
|
03eb83b362 | ||
|
|
a8716d51b3 | ||
|
|
a71b15ae18 | ||
|
|
a2f6a234cb | ||
|
|
6b5e2b7a1f |
@ -4,6 +4,8 @@ include:
|
|||||||
|
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
|
- slices
|
||||||
|
- slices-publish
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
RELEASE_NAME: iac-offline
|
RELEASE_NAME: iac-offline
|
||||||
@ -33,3 +35,31 @@ build_offline_mirror:
|
|||||||
- docker push ${IMAGE_NAME}
|
- docker push ${IMAGE_NAME}
|
||||||
- docker push ${CR}/${RELEASE_NAME}:latest
|
- docker push ${CR}/${RELEASE_NAME}:latest
|
||||||
timeout: 30m
|
timeout: 30m
|
||||||
|
|
||||||
|
generate_slice_jobs:
|
||||||
|
stage: slices
|
||||||
|
image: ${CR}/infra/slice-builder:1.0.0
|
||||||
|
tags:
|
||||||
|
- build
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_TAG =~ /^v?(?:\d+\.){2}\d+$/
|
||||||
|
variables:
|
||||||
|
GIT_DEPTH: "1"
|
||||||
|
script:
|
||||||
|
- python3 ci/publish-slice.py generate --release "$CI_COMMIT_TAG" --commit "$CI_COMMIT_SHA" --out slices-pipeline.yml
|
||||||
|
- cat slices-pipeline.yml
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- slices-pipeline.yml
|
||||||
|
|
||||||
|
publish_contour_slices:
|
||||||
|
stage: slices-publish
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_TAG =~ /^v?(?:\d+\.){2}\d+$/
|
||||||
|
needs:
|
||||||
|
- generate_slice_jobs
|
||||||
|
trigger:
|
||||||
|
include:
|
||||||
|
- artifact: slices-pipeline.yml
|
||||||
|
job: generate_slice_jobs
|
||||||
|
strategy: depend
|
||||||
|
|||||||
38
CLAUDE.md
38
CLAUDE.md
@ -25,11 +25,13 @@
|
|||||||
|
|
||||||
| Путь | Что |
|
| Путь | Что |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `clusters/` | 10 точек входа Flux — **источник истины «что где раскатано»** |
|
| `clusters/` | 17 точек входа Flux + шаблон `contour` — **источник истины «что где раскатано»** |
|
||||||
| `infrastructure/` | 36 платформенных компонентов |
|
| `infrastructure/` | 44 платформенных компонента |
|
||||||
| `apps/` | 37 прикладных сервисов |
|
| `apps/` | 39 прикладных сервисов |
|
||||||
| `docs/apps/` | 35 mermaid-диаграмм по сервисам + `README.md` с доменной группировкой |
|
| `docs/apps/` | 34 mermaid-диаграммы по сервисам + `README.md` с доменной группировкой |
|
||||||
|
| `docs/architecture/` | модель контура в ArchiMate + скрипты-сканеры (`scan_calls.py`, `scan_contour.py`) и сборка (`build_archimate.py`); реальные значения инсталляций сюда не кладутся, только `*.example.json` |
|
||||||
| `docs/closed-contour-deployment.md` | ранбук поднятия закрытого контура |
|
| `docs/closed-contour-deployment.md` | ранбук поднятия закрытого контура |
|
||||||
|
| `ci/`, `.gitlab-ci.yml`, `Dockerfile` | на тег `vX.Y.Z`: образ офлайн-зеркала `iac-offline` и публикация «слайсов» контуров (см. ниже) |
|
||||||
| `README.md` | mermaid-мегакарта платформы, структура репозитория, инструкции по добавлению app / infra / кластера |
|
| `README.md` | mermaid-мегакарта платформы, структура репозитория, инструкции по добавлению app / infra / кластера |
|
||||||
| `IAC-CATALOG.md` | каталог всего `infra/*`; §5 — про этот репозиторий |
|
| `IAC-CATALOG.md` | каталог всего `infra/*`; §5 — про этот репозиторий |
|
||||||
| `inventory.yaml` | сгенерированный снимок прода: 59 namespace → kind → имена |
|
| `inventory.yaml` | сгенерированный снимок прода: 59 namespace → kind → имена |
|
||||||
@ -38,15 +40,18 @@
|
|||||||
|
|
||||||
**Кластер.** `clusters/<cluster>/kustomization.yaml` — плоский список `../../apps/<app>/<cluster>`. Инфраструктура подключается двумя разными способами:
|
**Кластер.** `clusters/<cluster>/kustomization.yaml` — плоский список `../../apps/<app>/<cluster>`. Инфраструктура подключается двумя разными способами:
|
||||||
|
|
||||||
- `d8-ugmk-prod` — прямо в корневом kustomization как `../../infrastructure/<comp>/d8-ugmk-prod`;
|
- `d8-ugmk-prod`, `sarex-contour`, `ugok`, `uralkal`, `vad` — прямо в корневом kustomization как `../../infrastructure/<comp>/<c>`;
|
||||||
- `brusnika-prod`, `brusnika-stage` — через `clusters/<c>/infrastructure/kustomization.yaml`, который ссылается на оверлеи `../../../infrastructure/<comp>/<c>` (`base` + патч HelmRelease + сопутствующие манифесты компонента);
|
- `brusnika-prod`, `brusnika-stage`, `vk-infra`, `vk-prod` — через `clusters/<c>/infrastructure/kustomization.yaml`, который ссылается на оверлеи `../../../infrastructure/<comp>/<c>` (`base` + патч HelmRelease + сопутствующие манифесты компонента);
|
||||||
- `wb` — через `clusters/wb/infrastructure/kustomization.yaml`, который ссылается на `../../../infrastructure/<comp>` (резолвится в `base`), а различия лежат в `clusters/wb/infrastructure/patches/*.yaml`.
|
- `wb`, `yc-cps-prod`, `yc-infra-prod`, `yc-k8s-test-02` — через `clusters/<c>/infrastructure/kustomization.yaml`, который ссылается на `../../../infrastructure/<comp>` (резолвится в `base`), а различия лежат в `clusters/<c>/infrastructure/patches/*.yaml`;
|
||||||
|
- `stand` — послойно: `layers.yaml` объявляет Flux-`Kustomization` `00-vault → 10-mesh → 20-data → 30-platform → 40-routing` (каталоги `clusters/stand/<NN>-<layer>/`). Слои ссылаются на оверлеи `infrastructure/<comp>/contour-template`, а значения контура (`${STORAGE_CLASS}`, `${HOST_*}`, `${GATEWAY_REPLICAS}`) Flux подставляет через `postBuild.substituteFrom` из ConfigMap `contour-vars`.
|
||||||
|
|
||||||
Кластеры: `brusnika-prod`, `brusnika-stage`, `d8-ugmk-prod`, `wb`, `yc-cps-prod`, `yc-ecp`, `yc-infra-prod`, `yc-k8s-test`, `yc-k8s-test-02`, `contour`.
|
Кластеры с приложениями: `asterus`, `brusnika-prod`, `brusnika-stage`, `d8-ugmk-prod`, `sarex-contour`, `ugok`, `uralkal`, `vad`, `wb`, `yc-ecp`, `yc-k8s-test`. Только инфраструктура: `stand`, `vk-infra`, `vk-prod`, `yc-cps-prod`, `yc-infra-prod`, `yc-k8s-test-02`. Шаблон: `contour`.
|
||||||
|
|
||||||
**Приложение.** `apps/<app>/base/` — по одному `.yaml` на процесс (`backend.yaml`, `celery.yaml`, `frontend.yaml`) плюс `namespace.yaml`; оверлеи в `apps/<app>/<cluster>/`. Namespace = имя приложения. Рядом с манифестами лежит документация: `CONFIGURATION.md` (все env-переменные и откуда берутся), `ENDPOINTS.md` (исходящие HTTP-вызовы), `openapi.yaml` (входящий контракт), `.env.example`. Если процессов несколько — файлы с префиксом процесса: `api.ENDPOINTS.md`, `pdm.CONFIGURATION.md`, `frontend.CONFIGURATION.md`.
|
**Приложение.** `apps/<app>/base/` — по одному `.yaml` на процесс (`backend.yaml`, `celery.yaml`, `frontend.yaml`) плюс `namespace.yaml`; оверлеи в `apps/<app>/<cluster>/`. Namespace = имя приложения. Рядом с манифестами лежит документация: `CONFIGURATION.md` (все env-переменные и откуда берутся), `ENDPOINTS.md` (исходящие HTTP-вызовы), `openapi.yaml` (входящий контракт), `.env.example`. Если процессов несколько — файлы с префиксом процесса: `api.ENDPOINTS.md`, `pdm.CONFIGURATION.md`, `frontend.CONFIGURATION.md`.
|
||||||
|
|
||||||
**Инфра-компонент.** `infrastructure/<comp>/base/` + оверлеи `<comp>/<cluster>/`. Корневой `infrastructure/<comp>/kustomization.yaml` всегда указывает просто на `base`. Чарты идут с суффиксом контура: `vault-contour`, `keycloak-contour`, `postgresql-contour` и — внимание — `idp-contour` это Zitadel.
|
**Инфра-компонент.** `infrastructure/<comp>/base/` + оверлеи `<comp>/<cluster>/`. Корневой `infrastructure/<comp>/kustomization.yaml` всегда указывает просто на `base`. Оверлей `contour-template` (есть у vault, istio-*, cert-manager, postgresql, kafka, rabbitmq, zitadel, camunda, istio-config) — параметризованный `${VAR}`, а не привязанный к конкретному кластеру. Чарты идут с суффиксом контура: `vault-contour`, `keycloak-contour`, `postgresql-contour` и — внимание — `idp-contour` это Zitadel.
|
||||||
|
|
||||||
|
**Слайсы.** На тег `vX.Y.Z` `ci/publish-slice.py generate` создаёт child-pipeline с отдельной джобой на каждый `clusters/*/kustomization.yaml`. Джоба вызывает `ci/build-slice.py`: тот обходит дерево Kustomize от `clusters/<c>` и упаковывает только нужные файлы в tar. Результат публикуется как OCI-артефакт `cr.yandex/crpct2b2lsmao4nm0cjk/<c>-iac:<tag>` (media type `application/vnd.sarex.slice.config.v1+json`). Так закрытый контур получает только свой срез. Тесты — `ci/tests/test_build_slice.py`.
|
||||||
|
|
||||||
## Где искать конкретное
|
## Где искать конкретное
|
||||||
|
|
||||||
@ -62,18 +67,23 @@
|
|||||||
| Тег образа в контуре | `apps/<app>/<cluster>/*.yaml`, поле `image.tag` |
|
| Тег образа в контуре | `apps/<app>/<cluster>/*.yaml`, поле `image.tag` |
|
||||||
| Что реально в проде по namespace | `inventory.yaml` |
|
| Что реально в проде по namespace | `inventory.yaml` |
|
||||||
| Где лежит исходный код приложения | `IAC-CATALOG.md` §5.3.8 |
|
| Где лежит исходный код приложения | `IAC-CATALOG.md` §5.3.8 |
|
||||||
|
| Что попадёт в слайс контура | `python3 ci/build-slice.py` по `clusters/<c>` (логика сбора — там же) |
|
||||||
|
| Переменные шаблонного контура | `${...}` в `infrastructure/*/contour-template`; значения — ConfigMap `contour-vars` в кластере, не в репозитории |
|
||||||
|
|
||||||
## Ловушки
|
## Ловушки
|
||||||
|
|
||||||
- **`dsinv/`** — оверлей есть у 25 приложений, но `clusters/dsinv/` не существует. Это материализованный снимок живого контура WB, а не источник деплоя. Развёрнутым не считать.
|
- **`dsinv/`** — оверлей есть у 25 приложений, но `clusters/dsinv/` не существует. Это материализованный снимок живого контура WB, а не источник деплоя. Развёрнутым не считать.
|
||||||
- **Две несовместимые конвенции оверлеев** (`IAC-CATALOG.md` §5.3.4). `d8-ugmk-prod`, `yc-k8s-test`, `dsinv` наследуют `base` и патчат его. `brusnika-prod`, `brusnika-stage`, `yc-ecp` — полные копии HelmRelease, которые молча дрейфуют от base. Правка `base` на них не влияет.
|
- **Две несовместимые конвенции оверлеев** (`IAC-CATALOG.md` §5.3.4). `d8-ugmk-prod`, `yc-k8s-test`, `vad`, `uralkal`, `sarex-contour`, `dsinv` наследуют `base` и патчат его. `brusnika-prod`, `brusnika-stage`, `yc-ecp`, `asterus` — полные копии HelmRelease, которые молча дрейфуют от base. Правка `base` на них не влияет. **`wb` (4 из 27) и `ugok` (9 из 33) смешанные** — проверяйте `resources:` конкретного оверлея; в `ugok` причина обычно указана комментарием в его `kustomization.yaml`.
|
||||||
|
- **`asterus` подключён почти пустым**: оверлеи есть у 26 приложений, но в `clusters/asterus/kustomization.yaml` активен только `auth-flow`, остальные закомментированы. Инфраструктуры в кластере нет.
|
||||||
|
- **Слайс публикуется для каждого `clusters/*/kustomization.yaml`** — в том числе для тестовых и инфраструктурных кластеров. Новая папка в `clusters/` с этим файлом автоматически станет слайсом на следующем теге. У `contour` такого файла нет, поэтому для него слайс не собирается.
|
||||||
- **`IAC-CATALOG.md` — снимок на 05.08.2026** и по составу приложений на контур уже расходится с реальностью (там у `wb` ноль приложений, фактически 26 оверлеев). Всегда сверяться с `clusters/*/kustomization.yaml`.
|
- **`IAC-CATALOG.md` — снимок на 05.08.2026** и по составу приложений на контур уже расходится с реальностью (там у `wb` ноль приложений, фактически 26 оверлеев). Всегда сверяться с `clusters/*/kustomization.yaml`.
|
||||||
- **Мегадиаграмма в `README.md` идеализирована** — рисована руками, показывает все приложения на одной Postgres и все фронтенды за `control-interface`. Проверяемые источники — env-переменные и istio-config.
|
- **Мегадиаграмма в `README.md` идеализирована** — рисована руками, показывает все приложения на одной Postgres и все фронтенды за `control-interface`. Проверяемые источники — env-переменные и istio-config.
|
||||||
- **`clusters/contour/` — не кластер, а шаблон**: в нём нет `flux-system`, только `apps.yaml`, `infrastructure.yaml`, `helm-repositories.yaml`.
|
- **`clusters/contour/` — не кластер, а шаблон**: в нём нет `flux-system`, только `apps.yaml`, `infrastructure.yaml`, `helm-repositories.yaml`.
|
||||||
- **Path-роутинг виден в репозитории только у `d8-ugmk-prod` и `yc-ecp`.** В Brusnika и WB весь прикладной трафик уходит одним правилом в `nginx-service.global-ingress` / `yet-another-nginx-service.global-ingress`, а разводка по путям происходит вне репозитория. Через Istio там публикуются только платформенные UI (Gitea, Grafana, OpenObserve, Superset, Vault, Zitadel, Camunda, MinIO).
|
- **Path-роутинг лежит в `infrastructure/istio-config/<c>/` у большинства контуров, но не у WB.** В `clusters/wb/infrastructure/patches/istio-config.yaml` через Istio публикуются только платформенные UI (Gitea, Grafana, OpenObserve, Superset, Vault, GoAlert), а прикладной трафик разводится вне репозитория. В Brusnika схема смешанная: часть путей идёт напрямую в сервисы, остальное — одним правилом в `yet-another-nginx-service.global-ingress`.
|
||||||
- **Имена namespace расходятся между источниками**: `inventory.yaml` использует суффикс (`flows-prod`), `apps/` — голое имя (`flows`).
|
- **`vk-prod` маршрутизирует в namespace с суффиксом `-prod`** (`eav-prod`, `pm-prod`, `inspections-prod`), а приложений из `apps/` в этом кластере нет: сервисы раскатываются не этим репозиторием.
|
||||||
- **17 оверлеев приложений не подключены ни к одному кластеру** — мёртвый код.
|
- **Имена namespace расходятся между источниками**: `inventory.yaml` и `vk-prod` используют суффикс (`flows-prod`), `apps/` — голое имя (`flows`).
|
||||||
- **Нет диаграмм** в `docs/apps/` для `iam`, `auth-flow`, `ams-sync`.
|
- **66 оверлеев приложений не подключены ни к одному кластеру**: 25 — `dsinv`, 25 — закомментированные в `asterus`, остальные 16 — мёртвый код (в основном `brusnika-prod`, плюс `bi`/`iam` в `wb` и `yc-k8s-test`).
|
||||||
|
- **Нет диаграмм** в `docs/apps/` для `ams-sync`, `auth-flow`, `bi`, `iam`, `premises`.
|
||||||
- **Чистые фронтенды без бэкенда в этом репозитории**: `auth-flow`, `control-interface`, `cross-section`, `document-link`, `prescriptions`, `projects`, `remarks`, `reviews`, `stamp-verification`. Бэкенд `projects` живёт в `planning/projects-backend`.
|
- **Чистые фронтенды без бэкенда в этом репозитории**: `auth-flow`, `control-interface`, `cross-section`, `document-link`, `prescriptions`, `projects`, `remarks`, `reviews`, `stamp-verification`. Бэкенд `projects` живёт в `planning/projects-backend`.
|
||||||
- Самые центральные сервисы по числу входящих ссылок: `documentations` (~85), `processing` / `workflow` (~76), `django` (~53), `eav` (~47), `resources` (~44), `bim` (~39), `flows` (~38). Менять их конфигурацию — дороже всего.
|
- Самые центральные сервисы по числу входящих ссылок: `documentations` (~85), `processing` / `workflow` (~76), `django` (~53), `eav` (~47), `resources` (~44), `bim` (~39), `flows` (~38). Менять их конфигурацию — дороже всего.
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: debug
|
- name: debug
|
||||||
image: cr.yandex/crp3ccidau046kdj8g9q/mc-files:dev9
|
image: cr.yandex/crp3ccidau046kdj8g9q/mc-files:dev5
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command: ["/bin/sh", "-c", "while true; do sleep 3600; done"]
|
command: ["/bin/sh", "-c", "while true; do sleep 3600; done"]
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
|
|||||||
@ -8,6 +8,9 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/cross-section-app:contour_c5e5d476@sha256:04e0eb23df7138eb6c0dad6d790c7229607cb70eb7a4d7448aa70a236caa7d52
|
||||||
deployment:
|
deployment:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|||||||
@ -8,6 +8,9 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
|
deployment:
|
||||||
|
replicaCount:
|
||||||
|
_default: 2
|
||||||
envs:
|
envs:
|
||||||
- name: ALLOWED_HOSTS
|
- name: ALLOWED_HOSTS
|
||||||
value:
|
value:
|
||||||
@ -129,10 +132,6 @@ spec:
|
|||||||
- name: ZITADEL_HOST
|
- name: ZITADEL_HOST
|
||||||
value:
|
value:
|
||||||
_default: https://sarex-login.uralmine.com
|
_default: https://sarex-login.uralmine.com
|
||||||
# Включено вместе с message-hub: он консьюмер топика message-hub-stage
|
|
||||||
# (SETTINGS_TOPICS в apps/message-hub/d8-ugmk-prod/message-hub.yaml),
|
|
||||||
# а писать в этот топик должен django. Vault-шаблон django-kafka
|
|
||||||
# и KAFKA_SSL_CAFILE уже есть в base — здесь только флаг.
|
|
||||||
- name: SERVER_KAFKA_ENABLED
|
- name: SERVER_KAFKA_ENABLED
|
||||||
value:
|
value:
|
||||||
_default: "True"
|
_default: "True"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -36,7 +36,7 @@ spec:
|
|||||||
|
|
||||||
image:
|
image:
|
||||||
name:
|
name:
|
||||||
_default: cr.yandex/crp3ccidau046kdj8g9q/pdf-markings-amqp:prod_3ab263be
|
_default: cr.yandex/crp3ccidau046kdj8g9q/pdf-markings-amqp:prod_9dea5b64
|
||||||
pullPolicy:
|
pullPolicy:
|
||||||
_default: IfNotPresent
|
_default: IfNotPresent
|
||||||
|
|
||||||
|
|||||||
@ -65,7 +65,7 @@ spec:
|
|||||||
enabled: false
|
enabled: false
|
||||||
image:
|
image:
|
||||||
name:
|
name:
|
||||||
_default: cr.yandex/crp3ccidau046kdj8g9q/documentations:prod_179e518c
|
_default: cr.yandex/crp3ccidau046kdj8g9q/documentations:prod_89c8039a
|
||||||
pullPolicy:
|
pullPolicy:
|
||||||
_default: IfNotPresent
|
_default: IfNotPresent
|
||||||
service:
|
service:
|
||||||
@ -89,6 +89,9 @@ spec:
|
|||||||
- name: POSTGRES_POOL_SIZE
|
- name: POSTGRES_POOL_SIZE
|
||||||
value:
|
value:
|
||||||
_default: "20"
|
_default: "20"
|
||||||
|
- name: CDE_API_ENABLED
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
- name: USE_LEGACY_BIM_FLOW
|
- name: USE_LEGACY_BIM_FLOW
|
||||||
value:
|
value:
|
||||||
_default: "true"
|
_default: "true"
|
||||||
@ -236,6 +239,9 @@ spec:
|
|||||||
- name: VALKEY_PORT
|
- name: VALKEY_PORT
|
||||||
value:
|
value:
|
||||||
_default: "6379"
|
_default: "6379"
|
||||||
|
- name: CDE_API_ENABLED
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
podAnnotations:
|
podAnnotations:
|
||||||
_default:
|
_default:
|
||||||
traffic.sidecar.istio.io/excludeOutboundPorts: "8200"
|
traffic.sidecar.istio.io/excludeOutboundPorts: "8200"
|
||||||
|
|||||||
@ -97,6 +97,9 @@ spec:
|
|||||||
- name: PUBLIC_LINK_FOLDER_CONNECTOR_ENABLED
|
- name: PUBLIC_LINK_FOLDER_CONNECTOR_ENABLED
|
||||||
value:
|
value:
|
||||||
_default: "true"
|
_default: "true"
|
||||||
|
- name: CDE_API_ENABLED
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
- name: USE_ZITADEL
|
- name: USE_ZITADEL
|
||||||
value:
|
value:
|
||||||
_default: "1"
|
_default: "1"
|
||||||
@ -223,6 +226,9 @@ spec:
|
|||||||
- name: VALKEY_PORT
|
- name: VALKEY_PORT
|
||||||
value:
|
value:
|
||||||
_default: "6379"
|
_default: "6379"
|
||||||
|
- name: CDE_API_ENABLED
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
podAnnotations:
|
podAnnotations:
|
||||||
_default:
|
_default:
|
||||||
traffic.sidecar.istio.io/excludeOutboundPorts: "8200"
|
traffic.sidecar.istio.io/excludeOutboundPorts: "8200"
|
||||||
|
|||||||
@ -37,7 +37,7 @@ spec:
|
|||||||
name:
|
name:
|
||||||
_default: pdf-markings-amqp
|
_default: pdf-markings-amqp
|
||||||
replicaCount:
|
replicaCount:
|
||||||
_default: 1
|
_default: 2
|
||||||
port:
|
port:
|
||||||
_default: 8000
|
_default: 8000
|
||||||
command:
|
command:
|
||||||
@ -64,7 +64,7 @@ spec:
|
|||||||
enabled: false
|
enabled: false
|
||||||
image:
|
image:
|
||||||
name:
|
name:
|
||||||
_default: cr.yandex/crp3ccidau046kdj8g9q/pdf-markings-amqp:prod_3ab263be
|
_default: cr.yandex/crp3ccidau046kdj8g9q/pdf-markings-amqp:prod_9dea5b64
|
||||||
pullPolicy:
|
pullPolicy:
|
||||||
_default: IfNotPresent
|
_default: IfNotPresent
|
||||||
service:
|
service:
|
||||||
|
|||||||
@ -156,3 +156,6 @@ spec:
|
|||||||
- name: VALKEY_PORT
|
- name: VALKEY_PORT
|
||||||
value:
|
value:
|
||||||
_default: "6379"
|
_default: "6379"
|
||||||
|
- name: CDE_API_ENABLED
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
|
|||||||
@ -144,3 +144,6 @@ spec:
|
|||||||
- name: VALKEY_PORT
|
- name: VALKEY_PORT
|
||||||
value:
|
value:
|
||||||
_default: "6379"
|
_default: "6379"
|
||||||
|
- name: CDE_API_ENABLED
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
|
|||||||
@ -8,6 +8,10 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/pdf-markings:contour_ce9ed6d7
|
||||||
|
|
||||||
envs:
|
envs:
|
||||||
- name: MARKS_APP__LOG_LEVEL
|
- name: MARKS_APP__LOG_LEVEL
|
||||||
value:
|
value:
|
||||||
@ -33,8 +37,6 @@ spec:
|
|||||||
value:
|
value:
|
||||||
_default: "true"
|
_default: "true"
|
||||||
|
|
||||||
# Временно: в образе нет CA УГМК для s3.uralmine.com (INFRA-3034).
|
|
||||||
# Вернуть "true" после монтирования CA и AWS_CA_BUNDLE.
|
|
||||||
- name: MARKS_S3__SSL_VERIFY
|
- name: MARKS_S3__SSL_VERIFY
|
||||||
value:
|
value:
|
||||||
_default: "false"
|
_default: "false"
|
||||||
|
|||||||
@ -36,7 +36,7 @@ spec:
|
|||||||
|
|
||||||
image:
|
image:
|
||||||
name:
|
name:
|
||||||
_default: cr.yandex/crp3ccidau046kdj8g9q/documentations:prod_98a23ef5
|
_default: cr.yandex/crp3ccidau046kdj8g9q/documentations:prod_0be18f51
|
||||||
pullPolicy:
|
pullPolicy:
|
||||||
_default: IfNotPresent
|
_default: IfNotPresent
|
||||||
|
|
||||||
@ -134,6 +134,10 @@ spec:
|
|||||||
- name: POSTGRES_POOL_SIZE
|
- name: POSTGRES_POOL_SIZE
|
||||||
value:
|
value:
|
||||||
_default: "20"
|
_default: "20"
|
||||||
|
|
||||||
|
- name: CDE_API_ENABLED
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
|
|
||||||
- name: ZITADEL_ACCOUNT
|
- name: ZITADEL_ACCOUNT
|
||||||
value:
|
value:
|
||||||
|
|||||||
@ -36,7 +36,7 @@ spec:
|
|||||||
|
|
||||||
image:
|
image:
|
||||||
name:
|
name:
|
||||||
_default: cr.yandex/crp3ccidau046kdj8g9q/documentations-api-files:prod_3f19a21d
|
_default: cr.yandex/crp3ccidau046kdj8g9q/documentations-api-files:prod_fef3add2
|
||||||
pullPolicy:
|
pullPolicy:
|
||||||
_default: IfNotPresent
|
_default: IfNotPresent
|
||||||
|
|
||||||
@ -137,6 +137,10 @@ spec:
|
|||||||
- name: API_ADDRESS
|
- name: API_ADDRESS
|
||||||
value:
|
value:
|
||||||
_default: "0.0.0.0:8080"
|
_default: "0.0.0.0:8080"
|
||||||
|
|
||||||
|
- name: CDE_API_ENABLED
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
|
|
||||||
- name: API_ADDRESS_FILE
|
- name: API_ADDRESS_FILE
|
||||||
value:
|
value:
|
||||||
|
|||||||
@ -36,7 +36,7 @@ spec:
|
|||||||
|
|
||||||
image:
|
image:
|
||||||
name:
|
name:
|
||||||
_default: cr.yandex/crp3ccidau046kdj8g9q/pdf-markings-amqp:prod_3ab263be
|
_default: cr.yandex/crp3ccidau046kdj8g9q/pdf-markings-amqp:prod_9dea5b64
|
||||||
pullPolicy:
|
pullPolicy:
|
||||||
_default: IfNotPresent
|
_default: IfNotPresent
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,9 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/eav:prod_0dcebba6@sha256:2630ad45e4704881ac834f48a70ea837fa46620a5339c3e462282fd16399cc9c
|
||||||
deployment:
|
deployment:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|||||||
@ -18,7 +18,7 @@ data:
|
|||||||
|
|
||||||
# DEBUG SETTINGS START
|
# DEBUG SETTINGS START
|
||||||
# ---------------------------------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
DEBUG = True
|
DEBUG = False
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
# ---------------------------------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
# DEBUG SETTINGS END
|
# DEBUG SETTINGS END
|
||||||
|
|||||||
@ -9,6 +9,8 @@ spec:
|
|||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
deployment:
|
deployment:
|
||||||
|
replicaCount:
|
||||||
|
_default: 2
|
||||||
args:
|
args:
|
||||||
_default:
|
_default:
|
||||||
- |
|
- |
|
||||||
|
|||||||
@ -8,6 +8,9 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/iams:contour_29754513@sha256:a12cf64b59da50cf99011ee4fc8559e61af637887265f1377a448ee321de2250
|
||||||
envs:
|
envs:
|
||||||
- name: ENVIRONMENT
|
- name: ENVIRONMENT
|
||||||
value:
|
value:
|
||||||
|
|||||||
@ -1,33 +1,5 @@
|
|||||||
---
|
---
|
||||||
# ВНИМАНИЕ: data["production.py"] — одна строка, kustomize заменяет её ЦЕЛИКОМ,
|
|
||||||
# поэтому здесь продублировано всё содержимое из
|
|
||||||
# apps/issues/base/production-configmap.yaml (та же ловушка, что с envs).
|
|
||||||
#
|
|
||||||
# Зачем нужен этот патч:
|
|
||||||
# production.py делает `from .base import *` (там всё через os.getenv), а затем
|
|
||||||
# переприсваивает часть значений ХАРДКОДОМ — поэтому для этих переменных
|
|
||||||
# ConfigMap ПОБЕЖДАЕТ env из HelmRelease, и правка env в оверлее
|
|
||||||
# backend-s3.yaml/celery-s3.yaml сама по себе ни на что не влияет.
|
|
||||||
#
|
|
||||||
# INFRA-3005 (основная причина зависания экспорта в Excel):
|
|
||||||
# в base хардкодом WORKFLOWS_HOST/WORKFLOWS_URL = workflows-api-service.workflow
|
|
||||||
# — такого сервиса в УГМК нет. celery собирает xlsx, затем
|
|
||||||
# ExportFinishedNotification → issues/mailing.py:122 делает POST на
|
|
||||||
# {WORKFLOWS_HOST}/internal/v1/companies/{id}/workflows и падает с
|
|
||||||
# ConnectionError (Name or service not known) — задача фейлится, фронт не
|
|
||||||
# получает завершения и крутит загрузку бесконечно. Тем же путём с 26.08
|
|
||||||
# падали ВСЕ уведомления (send_notification_on_create/on_update/
|
|
||||||
# to_new_responsible/on_bulk_update, ~1770 ошибок).
|
|
||||||
#
|
|
||||||
# Отличия от base:
|
|
||||||
# - WORKFLOWS_HOST / WORKFLOWS_URL → backend-svc.processing (через os.getenv,
|
|
||||||
# чтобы env из оверлея снова начал работать)
|
|
||||||
# - SAREX_API → https://sarex.uralmine.com (в base зашит https://srx.wb.ru)
|
|
||||||
# - CORS/CSRF origins → https://sarex.uralmine.com (в base только lk.srx.wb.ru)
|
|
||||||
# - убраны хардкоды KAFKA_USERNAME/KAFKA_PASSWORD/DJANGO_TOKEN и чужой
|
|
||||||
# дефолт KAFKA_HOST: в УГМК всё это приходит из Vault
|
|
||||||
# (см. podAnnotations в backend-s3.yaml / celery-s3.yaml / kafka-app.yaml)
|
|
||||||
# - BASE_AERO_URL оставлен внутренним — он server-to-server
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
@ -146,9 +118,9 @@ data:
|
|||||||
|
|
||||||
CORS_ALLOW_ALL_ORIGINS = True
|
CORS_ALLOW_ALL_ORIGINS = True
|
||||||
ENABLE_MAILGUN=False
|
ENABLE_MAILGUN=False
|
||||||
SMTP_PORT=465
|
SMTP_PORT=587
|
||||||
SMTP_HOST="mail.rwb.ru"
|
SMTP_HOST="post.uralmine.com"
|
||||||
EMAIL_FROM="sarex@rwb.ru"
|
EMAIL_FROM="sarex@uralmine.com"
|
||||||
|
|
||||||
CORS_ALLOW_METHODS = [
|
CORS_ALLOW_METHODS = [
|
||||||
"DELETE",
|
"DELETE",
|
||||||
|
|||||||
@ -12,7 +12,6 @@ data:
|
|||||||
|
|
||||||
# DEBUG SETTINGS START
|
# DEBUG SETTINGS START
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
DEBUG = True
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST_MODE = False
|
TEST_MODE = False
|
||||||
@ -40,7 +39,7 @@ data:
|
|||||||
|
|
||||||
# DEBUG SETTINGS START
|
# DEBUG SETTINGS START
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
DEBUG = True
|
DEBUG = False
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
REVIEW_HOST='http://backend-service.flows.svc.cluster.local:8000'
|
REVIEW_HOST='http://backend-service.flows.svc.cluster.local:8000'
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
---
|
---
|
||||||
# Патч backend для vad.
|
|
||||||
#
|
|
||||||
# ВНИМАНИЕ: args — список, kustomize заменяет его ЦЕЛИКОМ (JSON merge patch),
|
|
||||||
# поэтому здесь продублирован весь скрипт из apps/pm/base/backend.yaml.
|
|
||||||
# При правке args в base их нужно продублировать и сюда.
|
|
||||||
# podAnnotations — map, мержится по ключам, поэтому переопределён только
|
|
||||||
# один ключ agent-inject-template-pm-s3; остальные vault-аннотации берутся из base.
|
|
||||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
kind: HelmRelease
|
kind: HelmRelease
|
||||||
metadata:
|
metadata:
|
||||||
@ -15,16 +9,15 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
deployment:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: null
|
|
||||||
memory: null
|
|
||||||
image:
|
image:
|
||||||
name:
|
name:
|
||||||
_default: cr.yandex/crp3ccidau046kdj8g9q/pm-backend:production_fa0f1551
|
_default: cr.yandex/crp3ccidau046kdj8g9q/pm-backend:production_fa0f1551
|
||||||
|
|
||||||
deployment:
|
deployment:
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: null
|
||||||
|
memory: null
|
||||||
args:
|
args:
|
||||||
_default:
|
_default:
|
||||||
- |
|
- |
|
||||||
@ -34,7 +27,6 @@ spec:
|
|||||||
[ -f /vault/secrets/pm-s3 ] && . /vault/secrets/pm-s3
|
[ -f /vault/secrets/pm-s3 ] && . /vault/secrets/pm-s3
|
||||||
[ -f /vault/secrets/pm-kafka ] && . /vault/secrets/pm-kafka
|
[ -f /vault/secrets/pm-kafka ] && . /vault/secrets/pm-kafka
|
||||||
set +a
|
set +a
|
||||||
python manage.py makemigrations pm --noinput
|
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
exec /opt/sarex/entrypoint.sh
|
exec /opt/sarex/entrypoint.sh
|
||||||
envs:
|
envs:
|
||||||
@ -61,6 +53,10 @@ spec:
|
|||||||
- name: EAV_API_PREFIX_V1
|
- name: EAV_API_PREFIX_V1
|
||||||
value:
|
value:
|
||||||
_default: "/api/v1"
|
_default: "/api/v1"
|
||||||
|
|
||||||
|
- name: SETTINGS_VERIFY_SSL
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
|
|
||||||
- name: TRACING_INSECURE
|
- name: TRACING_INSECURE
|
||||||
value:
|
value:
|
||||||
|
|||||||
@ -15,11 +15,6 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
celery:
|
celery:
|
||||||
deployment:
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: null
|
|
||||||
memory: null
|
|
||||||
# Образ общий с backend — держать в одной версии обязательно,
|
# Образ общий с backend — держать в одной версии обязательно,
|
||||||
# иначе воркер разъедется с API по схеме задач и моделям.
|
# иначе воркер разъедется с API по схеме задач и моделям.
|
||||||
image:
|
image:
|
||||||
@ -29,6 +24,10 @@ spec:
|
|||||||
# base не переопределяет args, а нам нужно добавить sourcing pm-kafka —
|
# base не переопределяет args, а нам нужно добавить sourcing pm-kafka —
|
||||||
# дублируем скрипт целиком (то же самое, что и в backend.yaml).
|
# дублируем скрипт целиком (то же самое, что и в backend.yaml).
|
||||||
deployment:
|
deployment:
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: null
|
||||||
|
memory: null
|
||||||
args:
|
args:
|
||||||
_default:
|
_default:
|
||||||
- |
|
- |
|
||||||
|
|||||||
@ -147,6 +147,28 @@ spec:
|
|||||||
value:
|
value:
|
||||||
_default: "/api/v1"
|
_default: "/api/v1"
|
||||||
|
|
||||||
|
- name: KAFKA_ENABLE
|
||||||
|
value:
|
||||||
|
_default: "True"
|
||||||
|
|
||||||
|
- name: KAFKA_BOOTSTRAP_SERVERS
|
||||||
|
value:
|
||||||
|
_default: '["wb-prod-kafka-bootstrap.kafka.svc.cluster.local:9093"]'
|
||||||
|
|
||||||
|
- name: KAFKA_SECURITY_PROTOCOL
|
||||||
|
value:
|
||||||
|
_default: "SSL"
|
||||||
|
|
||||||
|
- name: KAFKA_SSL_CAFILE
|
||||||
|
value:
|
||||||
|
_default: "/usr/local/share/ca-certificates/kafka.crt"
|
||||||
|
|
||||||
|
# Обязан совпадать с SETTINGS_TOPICS["planning"] в
|
||||||
|
# apps/message-hub/wb/message-hub.yaml
|
||||||
|
- name: KAFKA_TOPICS
|
||||||
|
value:
|
||||||
|
_default: '{"planning": "message-hub-prod"}'
|
||||||
|
|
||||||
commitSha: ""
|
commitSha: ""
|
||||||
gitlabUri: ""
|
gitlabUri: ""
|
||||||
gitlabJobUrl: ""
|
gitlabJobUrl: ""
|
||||||
|
|||||||
@ -141,6 +141,26 @@ spec:
|
|||||||
value:
|
value:
|
||||||
_default: "/api/v1"
|
_default: "/api/v1"
|
||||||
|
|
||||||
|
- name: KAFKA_ENABLE
|
||||||
|
value:
|
||||||
|
_default: "True"
|
||||||
|
|
||||||
|
- name: KAFKA_BOOTSTRAP_SERVERS
|
||||||
|
value:
|
||||||
|
_default: '["wb-prod-kafka-bootstrap.kafka.svc.cluster.local:9093"]'
|
||||||
|
|
||||||
|
- name: KAFKA_SECURITY_PROTOCOL
|
||||||
|
value:
|
||||||
|
_default: "SSL"
|
||||||
|
|
||||||
|
- name: KAFKA_SSL_CAFILE
|
||||||
|
value:
|
||||||
|
_default: "/usr/local/share/ca-certificates/kafka.crt"
|
||||||
|
|
||||||
|
- name: KAFKA_TOPICS
|
||||||
|
value:
|
||||||
|
_default: '{"planning": "message-hub-prod"}'
|
||||||
|
|
||||||
commitSha: ""
|
commitSha: ""
|
||||||
gitlabUri: ""
|
gitlabUri: ""
|
||||||
gitlabJobUrl: ""
|
gitlabJobUrl: ""
|
||||||
|
|||||||
6
apps/postgres-tls-test/uralkal/kustomization.yaml
Normal file
6
apps/postgres-tls-test/uralkal/kustomization.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- postgresql.yaml
|
||||||
|
- notes-tls.yaml
|
||||||
5
apps/postgres-tls-test/uralkal/namespace.yaml
Normal file
5
apps/postgres-tls-test/uralkal/namespace.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: postgres-tls-test
|
||||||
119
apps/postgres-tls-test/uralkal/notes-tls.yaml
Normal file
119
apps/postgres-tls-test/uralkal/notes-tls.yaml
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: notes-tls
|
||||||
|
namespace: postgres-tls-test
|
||||||
|
spec:
|
||||||
|
interval: 10m
|
||||||
|
dependsOn:
|
||||||
|
- name: pg-tls
|
||||||
|
chart:
|
||||||
|
spec:
|
||||||
|
chart: universal-chart
|
||||||
|
version: "0.1.9"
|
||||||
|
sourceRef:
|
||||||
|
kind: HelmRepository
|
||||||
|
name: yc-oci-charts
|
||||||
|
namespace: flux-system
|
||||||
|
interval: 10m
|
||||||
|
install:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
upgrade:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
env: _default
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
enabled: true
|
||||||
|
deployment:
|
||||||
|
enabled: true
|
||||||
|
name:
|
||||||
|
_default: notes-tls
|
||||||
|
replicaCount:
|
||||||
|
_default: 1
|
||||||
|
port:
|
||||||
|
_default: 8000
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu:
|
||||||
|
_default: 25m
|
||||||
|
memory:
|
||||||
|
_default: 128Mi
|
||||||
|
probes:
|
||||||
|
liveness:
|
||||||
|
enabled: false
|
||||||
|
readiness:
|
||||||
|
enabled: false
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/notes-backend:preprod_c00faf6e
|
||||||
|
pullPolicy:
|
||||||
|
_default: IfNotPresent
|
||||||
|
service:
|
||||||
|
enabled: true
|
||||||
|
name:
|
||||||
|
_default: notes-tls
|
||||||
|
type:
|
||||||
|
_default: ClusterIP
|
||||||
|
port:
|
||||||
|
_default: 8000
|
||||||
|
targetPort:
|
||||||
|
_default: 8000
|
||||||
|
portName:
|
||||||
|
_default: http
|
||||||
|
imagePullSecrets:
|
||||||
|
enabled:
|
||||||
|
_default: true
|
||||||
|
name:
|
||||||
|
_default: regcred
|
||||||
|
envs:
|
||||||
|
- name: PG_HOST
|
||||||
|
value:
|
||||||
|
_default: pg-tls.postgres-tls-test.svc.cluster.local
|
||||||
|
- name: PG_PORT
|
||||||
|
value:
|
||||||
|
_default: "5432"
|
||||||
|
- name: PG_DB
|
||||||
|
value:
|
||||||
|
_default: notes
|
||||||
|
- name: PG_LOGIN
|
||||||
|
value:
|
||||||
|
_default: notes
|
||||||
|
- name: PG_SSL
|
||||||
|
value:
|
||||||
|
_default: "true"
|
||||||
|
- name: PG_SSL_MODE
|
||||||
|
value:
|
||||||
|
_default: verify-full
|
||||||
|
- name: PGSSLMODE
|
||||||
|
value:
|
||||||
|
_default: verify-full
|
||||||
|
- name: PGSSLROOTCERT
|
||||||
|
value:
|
||||||
|
_default: /etc/pg-ca/ca.crt
|
||||||
|
- name: DJANGO_USE
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
|
- name: TIMEOUT
|
||||||
|
value:
|
||||||
|
_default: "120"
|
||||||
|
secretEnvs:
|
||||||
|
- name: PG_PASSWORD
|
||||||
|
secretName:
|
||||||
|
_default: pg-tls
|
||||||
|
secretKey:
|
||||||
|
_default: password
|
||||||
|
volumes:
|
||||||
|
_default:
|
||||||
|
- name: pg-ca
|
||||||
|
mountPath: /etc/pg-ca
|
||||||
|
readOnly: true
|
||||||
|
secret:
|
||||||
|
secretName: pg-tls-crt
|
||||||
|
items:
|
||||||
|
- key: ca.crt
|
||||||
|
path: ca.crt
|
||||||
88
apps/postgres-tls-test/uralkal/postgresql.yaml
Normal file
88
apps/postgres-tls-test/uralkal/postgresql.yaml
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: pg-tls
|
||||||
|
namespace: postgres-tls-test
|
||||||
|
spec:
|
||||||
|
interval: 10m
|
||||||
|
timeout: 15m
|
||||||
|
chart:
|
||||||
|
spec:
|
||||||
|
chart: postgresql-contour
|
||||||
|
version: "17.0.7"
|
||||||
|
sourceRef:
|
||||||
|
kind: HelmRepository
|
||||||
|
name: yc-oci-charts
|
||||||
|
namespace: flux-system
|
||||||
|
interval: 10m
|
||||||
|
install:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
upgrade:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
values:
|
||||||
|
fullnameOverride: pg-tls
|
||||||
|
global:
|
||||||
|
security:
|
||||||
|
allowInsecureImages: true
|
||||||
|
defaultStorageClass: local-path
|
||||||
|
imagePullSecrets:
|
||||||
|
- regcred
|
||||||
|
image:
|
||||||
|
registry: cr.yandex/crp3ccidau046kdj8g9q
|
||||||
|
repository: contour/postgresql
|
||||||
|
tag: 17.0.7
|
||||||
|
auth:
|
||||||
|
username: notes
|
||||||
|
database: notes
|
||||||
|
metrics:
|
||||||
|
enabled: false
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
autoGenerated: true
|
||||||
|
primary:
|
||||||
|
persistence:
|
||||||
|
storageClass: local-path
|
||||||
|
size: 1Gi
|
||||||
|
nodeSelector:
|
||||||
|
dedicated: generic
|
||||||
|
containerSecurityContext:
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
args:
|
||||||
|
- postgres
|
||||||
|
- -c
|
||||||
|
- ssl=on
|
||||||
|
- -c
|
||||||
|
- ssl_cert_file=/opt/bitnami/postgresql/certs/tls.crt
|
||||||
|
- -c
|
||||||
|
- ssl_key_file=/opt/bitnami/postgresql/certs/tls.key
|
||||||
|
- -c
|
||||||
|
- ssl_min_protocol_version=TLSv1.2
|
||||||
|
- -c
|
||||||
|
- hba_file=/bitnami/postgresql/conf/pg_hba.conf
|
||||||
|
customLivenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- exec pg_isready -U notes -d notes -h 127.0.0.1 -p 5432
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 6
|
||||||
|
customReadinessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- exec pg_isready -U notes -d notes -h 127.0.0.1 -p 5432
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 6
|
||||||
|
pgHbaConfiguration: |
|
||||||
|
local all all scram-sha-256
|
||||||
|
hostssl all all 0.0.0.0/0 scram-sha-256
|
||||||
|
hostssl all all ::/0 scram-sha-256
|
||||||
@ -8,6 +8,9 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/prescriptions-frontend:production_d48699e6@sha256:1e6925d1a41d91878013eb02e4463c30efa0479c25260575ad4fd3ee5471575d
|
||||||
deployment:
|
deployment:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|||||||
@ -42,7 +42,7 @@ spec:
|
|||||||
|
|
||||||
image:
|
image:
|
||||||
name:
|
name:
|
||||||
_default: cr.yandex/crp3ccidau046kdj8g9q/workflows-engine:contour_89322f69
|
_default: cr.yandex/crp3ccidau046kdj8g9q/workflows-engine:contour_835b57c1
|
||||||
|
|
||||||
pullPolicy:
|
pullPolicy:
|
||||||
_default: IfNotPresent
|
_default: IfNotPresent
|
||||||
@ -131,6 +131,20 @@ spec:
|
|||||||
- name: API_ADDRESS
|
- name: API_ADDRESS
|
||||||
value:
|
value:
|
||||||
_default: "0.0.0.0:8000"
|
_default: "0.0.0.0:8000"
|
||||||
|
|
||||||
|
- name: ENABLE_CROSS_SECTION_API_DB
|
||||||
|
value:
|
||||||
|
_default: "1"
|
||||||
|
- name: CROSS_SECTION_API_DB
|
||||||
|
value:
|
||||||
|
_default: "/etc/cross_section/cross-section-api-db-prod.json"
|
||||||
|
- name: CROSS_SECTION_API_DEBUG
|
||||||
|
value:
|
||||||
|
_default: "0"
|
||||||
|
|
||||||
|
- name: IGNORE_RESOURCES
|
||||||
|
value:
|
||||||
|
_default: "True"
|
||||||
|
|
||||||
- name: DJANGO_HOST
|
- name: DJANGO_HOST
|
||||||
value:
|
value:
|
||||||
|
|||||||
@ -8,6 +8,9 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/workflows-engine:contour_7d2cd329
|
||||||
deployment:
|
deployment:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|||||||
@ -8,6 +8,9 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/project-frontend-app:wb_fd49eb19@sha256:09968df4919be603200ccf39662bab15efdc1db902459abf912c6c533c896a11
|
||||||
deployment:
|
deployment:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|||||||
@ -8,6 +8,9 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/remarks-frontend:contour_013c5091@sha256:05b8667b934ac785a48a41b20fcbd7e4e5b6f377f29a6e47add9c23c474a42c7
|
||||||
deployment:
|
deployment:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|||||||
@ -8,6 +8,9 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/reviews-frontend:contour_c5a6d3b4@sha256:4333dbd7a7a68dcf4465563d8e079ee29fd52750c2bc841f92e745fb19e84591
|
||||||
deployment:
|
deployment:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|||||||
@ -29,7 +29,7 @@ spec:
|
|||||||
enabled: true
|
enabled: true
|
||||||
image:
|
image:
|
||||||
name:
|
name:
|
||||||
_default: cr.yandex/crp3ccidau046kdj8g9q/workspaces-v2-frontend:contour_a72e3265
|
_default: cr.yandex/crp3ccidau046kdj8g9q/workspaces-v2-frontend:contour_2da2a00e
|
||||||
pullPolicy:
|
pullPolicy:
|
||||||
_default: IfNotPresent
|
_default: IfNotPresent
|
||||||
deployment:
|
deployment:
|
||||||
|
|||||||
@ -8,6 +8,9 @@ spec:
|
|||||||
values:
|
values:
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
_default: cr.yandex/crp3ccidau046kdj8g9q/workspaces-v2-frontend:contour_1b5d64e4
|
||||||
deployment:
|
deployment:
|
||||||
replicaCount:
|
replicaCount:
|
||||||
_default: 1
|
_default: 1
|
||||||
@ -36,7 +36,7 @@ spec:
|
|||||||
|
|
||||||
image:
|
image:
|
||||||
name:
|
name:
|
||||||
_default: 10.4.10.187:80/library/workspaces-v2-frontend:contour_07373a45
|
_default: 10.4.10.187:80/library/workspaces-v2-frontend:contour_1b5d64e4
|
||||||
pullPolicy:
|
pullPolicy:
|
||||||
_default: IfNotPresent
|
_default: IfNotPresent
|
||||||
|
|
||||||
|
|||||||
163
ci/build-slice.py
Normal file
163
ci/build-slice.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import io
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
from pathlib import Path, PurePosixPath
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
FLUX_KUSTOMIZATION = "kustomize.toolkit.fluxcd.io"
|
||||||
|
KUSTOMIZATION_FILES = ("kustomization.yaml", "kustomization.yml", "Kustomization")
|
||||||
|
|
||||||
|
|
||||||
|
class SliceError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def is_remote(ref: str) -> bool:
|
||||||
|
return "://" in ref or ref.startswith(("git@", "github.com/", "gitlab.com/"))
|
||||||
|
|
||||||
|
|
||||||
|
def kustomization_file(directory: Path) -> Path | None:
|
||||||
|
for name in KUSTOMIZATION_FILES:
|
||||||
|
candidate = directory / name
|
||||||
|
if candidate.is_file():
|
||||||
|
return candidate
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def local_refs(doc: dict) -> list[str]:
|
||||||
|
refs = []
|
||||||
|
refs += doc.get("resources") or []
|
||||||
|
refs += doc.get("components") or []
|
||||||
|
refs += doc.get("patchesStrategicMerge") or []
|
||||||
|
refs += [p["path"] for p in doc.get("patches") or [] if isinstance(p, dict) and "path" in p]
|
||||||
|
for key in ("configMapGenerator", "secretGenerator"):
|
||||||
|
for gen in doc.get(key) or []:
|
||||||
|
for entry in (gen.get("files") or []) + (gen.get("envs") or []):
|
||||||
|
refs.append(entry.split("=", 1)[-1])
|
||||||
|
if gen.get("env"):
|
||||||
|
refs.append(gen["env"])
|
||||||
|
return [r for r in refs if isinstance(r, str)]
|
||||||
|
|
||||||
|
|
||||||
|
def flux_paths(path: Path) -> list[str]:
|
||||||
|
try:
|
||||||
|
docs = list(yaml.safe_load_all(path.read_text()))
|
||||||
|
except yaml.YAMLError:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
d["spec"]["path"]
|
||||||
|
for d in docs
|
||||||
|
if isinstance(d, dict)
|
||||||
|
and str(d.get("apiVersion", "")).startswith(FLUX_KUSTOMIZATION)
|
||||||
|
and d.get("kind") == "Kustomization"
|
||||||
|
and d.get("spec", {}).get("path")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def collect(repo: Path, start: str) -> tuple[set[PurePosixPath], list[str]]:
|
||||||
|
repo = repo.resolve()
|
||||||
|
files: set[PurePosixPath] = set()
|
||||||
|
entries: list[str] = []
|
||||||
|
seen_dirs: set[Path] = set()
|
||||||
|
|
||||||
|
def rel(p: Path) -> PurePosixPath:
|
||||||
|
try:
|
||||||
|
return PurePosixPath(p.resolve().relative_to(repo).as_posix())
|
||||||
|
except ValueError:
|
||||||
|
raise SliceError(f"reference outside the repository: {p}")
|
||||||
|
|
||||||
|
def add_file(p: Path):
|
||||||
|
r = rel(p)
|
||||||
|
if r in files:
|
||||||
|
return
|
||||||
|
files.add(r)
|
||||||
|
if p.suffix in (".yaml", ".yml"):
|
||||||
|
for fp in flux_paths(p):
|
||||||
|
visit_dir(repo / fp, entry=True)
|
||||||
|
|
||||||
|
def visit_dir(d: Path, entry: bool = False):
|
||||||
|
d = d.resolve()
|
||||||
|
if d in seen_dirs:
|
||||||
|
return
|
||||||
|
seen_dirs.add(d)
|
||||||
|
kfile = kustomization_file(d)
|
||||||
|
if kfile is None:
|
||||||
|
raise SliceError(f"no kustomization.yaml in {rel(d)}")
|
||||||
|
if entry:
|
||||||
|
entries.append(str(rel(d)))
|
||||||
|
add_file(kfile)
|
||||||
|
doc = yaml.safe_load(kfile.read_text()) or {}
|
||||||
|
for ref in local_refs(doc):
|
||||||
|
if is_remote(ref):
|
||||||
|
raise SliceError(f"remote reference {ref} in {rel(kfile)}")
|
||||||
|
target = (d / ref)
|
||||||
|
if target.is_dir():
|
||||||
|
visit_dir(target)
|
||||||
|
elif target.is_file():
|
||||||
|
add_file(target)
|
||||||
|
else:
|
||||||
|
raise SliceError(f"reference {ref} in {rel(kfile)} does not exist")
|
||||||
|
|
||||||
|
visit_dir(repo / start, entry=True)
|
||||||
|
return files, sorted(set(entries))
|
||||||
|
|
||||||
|
|
||||||
|
def write_tar(repo: Path, files: set[PurePosixPath], out: Path):
|
||||||
|
buf = io.BytesIO()
|
||||||
|
with tarfile.open(fileobj=buf, mode="w", format=tarfile.PAX_FORMAT) as tar:
|
||||||
|
for f in sorted(files):
|
||||||
|
data = (repo / f).read_bytes()
|
||||||
|
info = tarfile.TarInfo(str(f))
|
||||||
|
info.size = len(data)
|
||||||
|
info.mode = 0o644
|
||||||
|
info.mtime = 0
|
||||||
|
info.uid = info.gid = 0
|
||||||
|
info.uname = info.gname = ""
|
||||||
|
tar.addfile(info, io.BytesIO(data))
|
||||||
|
out.write_bytes(buf.getvalue())
|
||||||
|
|
||||||
|
|
||||||
|
def kustomize_build(path: Path) -> bytes:
|
||||||
|
p = subprocess.run(["kubectl", "kustomize", str(path)], capture_output=True)
|
||||||
|
if p.returncode != 0:
|
||||||
|
raise SliceError(f"kustomize build {path} failed: {p.stderr.decode().strip()}")
|
||||||
|
return p.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def verify(repo: Path, out: Path, entries: list[str]):
|
||||||
|
import tempfile
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
with tarfile.open(out) as tar:
|
||||||
|
tar.extractall(tmp, filter="data")
|
||||||
|
for e in entries:
|
||||||
|
if kustomize_build(repo / e) != kustomize_build(Path(tmp) / e):
|
||||||
|
raise SliceError(f"kustomize build of {e} differs between slice and full repository")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("contour")
|
||||||
|
ap.add_argument("--repo", default=".")
|
||||||
|
ap.add_argument("--out", required=True)
|
||||||
|
ap.add_argument("--no-verify", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
repo = Path(args.repo).resolve()
|
||||||
|
out = Path(args.out)
|
||||||
|
try:
|
||||||
|
files, entries = collect(repo, f"clusters/{args.contour}")
|
||||||
|
write_tar(repo, files, out)
|
||||||
|
if not args.no_verify:
|
||||||
|
verify(repo, out, entries)
|
||||||
|
except SliceError as e:
|
||||||
|
print(f"slice {args.contour}: {e}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
print(f"slice {args.contour}: {len(files)} files, entry points: {', '.join(entries)}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
217
ci/publish-slice.py
Normal file
217
ci/publish-slice.py
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location("build_slice", Path(__file__).with_name("build-slice.py"))
|
||||||
|
bs = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(bs)
|
||||||
|
|
||||||
|
REGISTRY_HOST = "cr.yandex"
|
||||||
|
SLICE_REGISTRY = "crpct2b2lsmao4nm0cjk"
|
||||||
|
CONFIG_TYPE = "application/vnd.sarex.slice.config.v1+json"
|
||||||
|
LAYER_TYPE = "application/vnd.oci.image.layer.v1.tar"
|
||||||
|
MANIFEST_TYPE = "application/vnd.oci.image.manifest.v1+json"
|
||||||
|
JOB_IMAGE = "${CR}/infra/slice-builder:1.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
class PublishError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def digest(data: bytes) -> str:
|
||||||
|
return "sha256:" + hashlib.sha256(data).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def list_contours(repo: Path) -> list[str]:
|
||||||
|
return sorted(p.parent.name for p in repo.glob("clusters/*/kustomization.yaml"))
|
||||||
|
|
||||||
|
|
||||||
|
def child_pipeline(contours: list[str], release: str, commit: str) -> str:
|
||||||
|
jobs: dict = {"stages": ["slices"]}
|
||||||
|
for c in contours:
|
||||||
|
jobs[f"slice {c}"] = {
|
||||||
|
"stage": "slices",
|
||||||
|
"image": JOB_IMAGE,
|
||||||
|
"tags": ["build"],
|
||||||
|
"allow_failure": True,
|
||||||
|
"variables": {"GIT_DEPTH": "1"},
|
||||||
|
"script": [
|
||||||
|
"printf '%s' \"$SLICES_PUSHER_KEY\" | base64 -d > /tmp/slices-key.json",
|
||||||
|
f"python3 ci/publish-slice.py publish --contour {c} --release {release} --commit {commit} --key-file /tmp/slices-key.json",
|
||||||
|
],
|
||||||
|
"after_script": ["rm -f /tmp/slices-key.json"],
|
||||||
|
}
|
||||||
|
if not contours:
|
||||||
|
jobs["no contours"] = {"stage": "slices", "image": JOB_IMAGE, "tags": ["build"], "script": ["echo 'no clusters/*/kustomization.yaml found'"]}
|
||||||
|
return yaml.safe_dump(jobs, sort_keys=False, allow_unicode=False)
|
||||||
|
|
||||||
|
|
||||||
|
def build_manifest(layer: bytes, annotations: dict[str, str]) -> tuple[bytes, bytes]:
|
||||||
|
config = b"{}"
|
||||||
|
manifest = {
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"mediaType": MANIFEST_TYPE,
|
||||||
|
"config": {"mediaType": CONFIG_TYPE, "digest": digest(config), "size": len(config)},
|
||||||
|
"layers": [{
|
||||||
|
"mediaType": LAYER_TYPE,
|
||||||
|
"digest": digest(layer),
|
||||||
|
"size": len(layer),
|
||||||
|
"annotations": {"org.opencontainers.image.title": "slice.tar"},
|
||||||
|
}],
|
||||||
|
"annotations": dict(sorted(annotations.items())),
|
||||||
|
}
|
||||||
|
return config, json.dumps(manifest, sort_keys=True, separators=(",", ":")).encode()
|
||||||
|
|
||||||
|
|
||||||
|
class Registry:
|
||||||
|
def __init__(self, username: str, password: str):
|
||||||
|
self.base = f"https://{REGISTRY_HOST}"
|
||||||
|
self.basic = "Basic " + base64.b64encode(f"{username}:{password}".encode()).decode()
|
||||||
|
self.tokens: dict[str, str] = {}
|
||||||
|
|
||||||
|
def _send(self, method, url, data=None, headers=None):
|
||||||
|
req = urllib.request.Request(url, data=data, method=method, headers=headers or {})
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=60) as r:
|
||||||
|
return r.status, dict(r.headers), r.read()
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
return e.code, dict(e.headers), e.read()
|
||||||
|
|
||||||
|
def _token(self, challenge: str, scope: str) -> str:
|
||||||
|
params = dict(re.findall(r'(\w+)="([^"]*)"', challenge))
|
||||||
|
realm = params.pop("realm")
|
||||||
|
params["scope"] = scope
|
||||||
|
status, _, body = self._send("GET", f"{realm}?{urllib.parse.urlencode(params)}", headers={"Authorization": self.basic})
|
||||||
|
if status != 200:
|
||||||
|
raise PublishError(f"registry auth failed: HTTP {status}")
|
||||||
|
payload = json.loads(body)
|
||||||
|
return payload.get("token") or payload.get("access_token")
|
||||||
|
|
||||||
|
def request(self, method, repo, path, data=None, headers=None):
|
||||||
|
scope = f"repository:{repo}:pull,push"
|
||||||
|
url = path if path.startswith("http") else f"{self.base}/v2/{repo}/{path}"
|
||||||
|
headers = dict(headers or {})
|
||||||
|
if scope in self.tokens:
|
||||||
|
headers["Authorization"] = f"Bearer {self.tokens[scope]}"
|
||||||
|
status, h, body = self._send(method, url, data, headers)
|
||||||
|
if status == 401:
|
||||||
|
challenge = {k.lower(): v for k, v in h.items()}.get("www-authenticate", "")
|
||||||
|
self.tokens[scope] = self._token(challenge, scope)
|
||||||
|
headers["Authorization"] = f"Bearer {self.tokens[scope]}"
|
||||||
|
status, h, body = self._send(method, url, data, headers)
|
||||||
|
return status, {k.lower(): v for k, v in h.items()}, body
|
||||||
|
|
||||||
|
def current_layer(self, repo: str) -> str | None:
|
||||||
|
status, _, body = self.request("GET", repo, "manifests/current", headers={"Accept": MANIFEST_TYPE})
|
||||||
|
if status == 404:
|
||||||
|
return None
|
||||||
|
if status != 200:
|
||||||
|
raise PublishError(f"{repo}:current: HTTP {status}")
|
||||||
|
return json.loads(body)["layers"][0]["digest"]
|
||||||
|
|
||||||
|
def put_blob(self, repo: str, data: bytes) -> bool:
|
||||||
|
d = digest(data)
|
||||||
|
if self.request("HEAD", repo, f"blobs/{d}")[0] == 200:
|
||||||
|
return False
|
||||||
|
status, h, _ = self.request("POST", repo, "blobs/uploads/")
|
||||||
|
if status != 202:
|
||||||
|
raise PublishError(f"blob upload to {repo}: HTTP {status}")
|
||||||
|
location = h["location"]
|
||||||
|
if location.startswith("/"):
|
||||||
|
location = self.base + location
|
||||||
|
sep = "&" if "?" in location else "?"
|
||||||
|
status, _, _ = self.request("PUT", repo, f"{location}{sep}digest={d}", data, {"Content-Type": "application/octet-stream"})
|
||||||
|
if status != 201:
|
||||||
|
raise PublishError(f"blob upload to {repo}: HTTP {status}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def push(self, repo: str, tags: list[str], layer: bytes, annotations: dict[str, str]) -> bool:
|
||||||
|
config, manifest = build_manifest(layer, annotations)
|
||||||
|
self.put_blob(repo, config)
|
||||||
|
uploaded = self.put_blob(repo, layer)
|
||||||
|
for tag in tags:
|
||||||
|
status, _, body = self.request("PUT", repo, f"manifests/{tag}", manifest, {"Content-Type": MANIFEST_TYPE})
|
||||||
|
if status != 201:
|
||||||
|
raise PublishError(f"{repo}:{tag}: HTTP {status} {body[:200]!r}")
|
||||||
|
return uploaded
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg: str):
|
||||||
|
print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def publish(repo: Path, contour: str, release: str, commit: str, key_file: Path) -> int:
|
||||||
|
target = f"{SLICE_REGISTRY}/{contour}-iac"
|
||||||
|
log(f"contour {contour}, source iac@{commit[:8]}, release {release}")
|
||||||
|
try:
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
out = Path(tmp) / "slice.tar"
|
||||||
|
files, entries = bs.collect(repo, f"clusters/{contour}")
|
||||||
|
log(f"collected {len(files)} files, entry points: {', '.join(entries)}")
|
||||||
|
bs.write_tar(repo, files, out)
|
||||||
|
bs.verify(repo, out, entries)
|
||||||
|
log(f"verified: kustomize build of {len(entries)} entry point(s) identical to full repository")
|
||||||
|
layer = out.read_bytes()
|
||||||
|
new = digest(layer)
|
||||||
|
log(f"slice size {len(layer)} bytes, digest {new}")
|
||||||
|
registry = Registry("json_key", key_file.read_text())
|
||||||
|
current = registry.current_layer(f"{target}")
|
||||||
|
changed = current != new
|
||||||
|
log(f"registry {REGISTRY_HOST}/{target}, current " + (current or "absent") + (", content changed" if changed else ", content unchanged"))
|
||||||
|
tags = [commit[:8], release] + (["current"] if changed else [])
|
||||||
|
uploaded = registry.push(target, tags, layer, {
|
||||||
|
"org.opencontainers.image.source": "iac",
|
||||||
|
"org.opencontainers.image.revision": commit,
|
||||||
|
"org.opencontainers.image.version": release,
|
||||||
|
"org.opencontainers.image.created": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||||
|
"io.sarex.slice.contour": contour,
|
||||||
|
"io.sarex.slice.kind": "iac",
|
||||||
|
})
|
||||||
|
log(f"layer {'uploaded' if uploaded else 'already in registry'}, tags pushed: {', '.join(tags)}")
|
||||||
|
except (bs.SliceError, PublishError) as e:
|
||||||
|
log(f"FAILED: {e}")
|
||||||
|
return 1
|
||||||
|
log("done")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||||
|
gen = sub.add_parser("generate")
|
||||||
|
gen.add_argument("--repo", default=".")
|
||||||
|
gen.add_argument("--release", required=True)
|
||||||
|
gen.add_argument("--commit", required=True)
|
||||||
|
gen.add_argument("--out", required=True)
|
||||||
|
pub = sub.add_parser("publish")
|
||||||
|
pub.add_argument("--repo", default=".")
|
||||||
|
pub.add_argument("--contour", required=True)
|
||||||
|
pub.add_argument("--release", required=True)
|
||||||
|
pub.add_argument("--commit", required=True)
|
||||||
|
pub.add_argument("--key-file", required=True)
|
||||||
|
args = ap.parse_args()
|
||||||
|
repo = Path(args.repo).resolve()
|
||||||
|
if args.cmd == "generate":
|
||||||
|
contours = list_contours(repo)
|
||||||
|
Path(args.out).write_text(child_pipeline(contours, args.release, args.commit))
|
||||||
|
log(f"{len(contours)} contours: {', '.join(contours)}")
|
||||||
|
return 0
|
||||||
|
return publish(repo, args.contour, args.release, args.commit, Path(args.key_file))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
93
ci/tests/test_build_slice.py
Normal file
93
ci/tests/test_build_slice.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import importlib.util
|
||||||
|
import tarfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location("build_slice", Path(__file__).parents[1] / "build-slice.py")
|
||||||
|
bs = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(bs)
|
||||||
|
|
||||||
|
|
||||||
|
def put(root: Path, rel: str, text: str):
|
||||||
|
p = root / rel
|
||||||
|
p.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
p.write_text(text)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def repo(tmp_path):
|
||||||
|
put(tmp_path, "clusters/a/kustomization.yaml", "resources:\n - ./flux-system\n - ./layers.yaml\n")
|
||||||
|
put(tmp_path, "clusters/a/flux-system/kustomization.yaml", "resources:\n - gotk-sync.yaml\n")
|
||||||
|
put(tmp_path, "clusters/a/flux-system/gotk-sync.yaml",
|
||||||
|
"apiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nspec:\n path: ./clusters/a\n")
|
||||||
|
put(tmp_path, "clusters/a/layers.yaml",
|
||||||
|
"apiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nspec:\n path: ./layers/a/00-vault\n")
|
||||||
|
put(tmp_path, "layers/a/00-vault/kustomization.yaml", "resources:\n - ../../../infrastructure/vault/contour-template\n")
|
||||||
|
put(tmp_path, "infrastructure/vault/contour-template/kustomization.yaml",
|
||||||
|
"resources:\n - ../base\npatches:\n - path: vault.yaml\n")
|
||||||
|
put(tmp_path, "infrastructure/vault/contour-template/vault.yaml", "kind: HelmRelease\n")
|
||||||
|
put(tmp_path, "infrastructure/vault/base/kustomization.yaml", "resources:\n - helmrelease.yaml\n")
|
||||||
|
put(tmp_path, "infrastructure/vault/base/helmrelease.yaml", "kind: HelmRelease\n")
|
||||||
|
put(tmp_path, "infrastructure/vault/base/unused.yaml", "kind: ConfigMap\n")
|
||||||
|
put(tmp_path, "infrastructure/vault/uralkal/kustomization.yaml", "resources:\n - ../base\n")
|
||||||
|
put(tmp_path, "clusters/b/kustomization.yaml", "resources:\n - ../../infrastructure/vault/uralkal\n")
|
||||||
|
return tmp_path
|
||||||
|
|
||||||
|
|
||||||
|
def test_slice_follows_flux_paths_and_skips_foreign(repo):
|
||||||
|
files, entries = bs.collect(repo, "clusters/a")
|
||||||
|
names = {str(f) for f in files}
|
||||||
|
assert "infrastructure/vault/base/helmrelease.yaml" in names
|
||||||
|
assert "infrastructure/vault/contour-template/vault.yaml" in names
|
||||||
|
assert "layers/a/00-vault/kustomization.yaml" in names
|
||||||
|
assert not any("uralkal" in n or n.startswith("clusters/b") for n in names)
|
||||||
|
assert "infrastructure/vault/base/unused.yaml" not in names
|
||||||
|
assert entries == ["clusters/a", "layers/a/00-vault"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_remote_reference_stops_build(repo):
|
||||||
|
put(repo, "clusters/a/kustomization.yaml", "resources:\n - https://github.com/x/y//z\n")
|
||||||
|
with pytest.raises(bs.SliceError, match="remote reference"):
|
||||||
|
bs.collect(repo, "clusters/a")
|
||||||
|
|
||||||
|
|
||||||
|
def test_dangling_reference_stops_build(repo):
|
||||||
|
put(repo, "clusters/a/kustomization.yaml", "resources:\n - ./missing.yaml\n")
|
||||||
|
with pytest.raises(bs.SliceError, match="does not exist"):
|
||||||
|
bs.collect(repo, "clusters/a")
|
||||||
|
|
||||||
|
|
||||||
|
def test_tar_is_byte_identical_across_runs(repo, tmp_path_factory):
|
||||||
|
files, _ = bs.collect(repo, "clusters/a")
|
||||||
|
out1 = tmp_path_factory.mktemp("o") / "s.tar"
|
||||||
|
out2 = tmp_path_factory.mktemp("o") / "s.tar"
|
||||||
|
bs.write_tar(repo, files, out1)
|
||||||
|
(repo / "clusters/a/layers.yaml").touch()
|
||||||
|
bs.write_tar(repo, files, out2)
|
||||||
|
assert out1.read_bytes() == out2.read_bytes()
|
||||||
|
with tarfile.open(out1) as t:
|
||||||
|
assert all(m.mtime == 0 for m in t.getmembers())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pspec = importlib.util.spec_from_file_location("publish_slice", Path(__file__).parents[1] / "publish-slice.py")
|
||||||
|
ps = importlib.util.module_from_spec(pspec)
|
||||||
|
pspec.loader.exec_module(ps)
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_cluster_with_kustomization_is_a_contour(tmp_path):
|
||||||
|
put(tmp_path, "clusters/stand/kustomization.yaml", "resources: []\n")
|
||||||
|
put(tmp_path, "clusters/vad/kustomization.yaml", "resources: []\n")
|
||||||
|
put(tmp_path, "clusters/contour/apps.yaml", "kind: Kustomization\n")
|
||||||
|
assert ps.list_contours(tmp_path) == ["stand", "vad"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_child_pipeline_has_one_job_per_contour():
|
||||||
|
doc = yaml.safe_load(ps.child_pipeline(["stand", "vad"], "3.9.68", "abcdef1234"))
|
||||||
|
jobs = {k: v for k, v in doc.items() if k != "stages"}
|
||||||
|
assert sorted(jobs) == ["slice stand", "slice vad"]
|
||||||
|
assert all(j["allow_failure"] for j in jobs.values())
|
||||||
|
assert "--contour vad --release 3.9.68 --commit abcdef1234" in jobs["slice vad"]["script"][1]
|
||||||
|
assert all(ord(ch) < 128 for ch in ps.child_pipeline(["stand"], "1.0.0", "a" * 40))
|
||||||
14
clusters/stand/00-vault/helm-repositories.yaml
Normal file
14
clusters/stand/00-vault/helm-repositories.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
apiVersion: source.toolkit.fluxcd.io/v1
|
||||||
|
kind: HelmRepository
|
||||||
|
metadata:
|
||||||
|
name: yc-oci-charts
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
type: oci
|
||||||
|
interval: 10m0s
|
||||||
|
url: ${CHART_REPO:=oci://cr.yandex/crp3ccidau046kdj8g9q/charts}
|
||||||
|
secretRef:
|
||||||
|
name: ${CHART_REPO_SECRET:=yc-cr-auth}
|
||||||
|
certSecretRef:
|
||||||
|
name: chart-repo-ca
|
||||||
5
clusters/stand/00-vault/kustomization.yaml
Normal file
5
clusters/stand/00-vault/kustomization.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ./helm-repositories.yaml
|
||||||
|
- ../../../infrastructure/vault/contour-template
|
||||||
7
clusters/stand/10-mesh/kustomization.yaml
Normal file
7
clusters/stand/10-mesh/kustomization.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../../../infrastructure/istio-base/contour-template
|
||||||
|
- ../../../infrastructure/istio-pilot/contour-template
|
||||||
|
- ../../../infrastructure/istio-gateway/contour-template
|
||||||
|
- ../../../infrastructure/cert-manager/contour-template
|
||||||
6
clusters/stand/20-data/kustomization.yaml
Normal file
6
clusters/stand/20-data/kustomization.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../../../infrastructure/postgresql/contour-template
|
||||||
|
- ../../../infrastructure/kafka/contour-template
|
||||||
|
- ../../../infrastructure/rabbitmq/contour-template
|
||||||
5
clusters/stand/30-platform/kustomization.yaml
Normal file
5
clusters/stand/30-platform/kustomization.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../../../infrastructure/zitadel/contour-template
|
||||||
|
- ../../../infrastructure/camunda/contour-template
|
||||||
4
clusters/stand/40-routing/kustomization.yaml
Normal file
4
clusters/stand/40-routing/kustomization.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../../../infrastructure/istio-config/contour-template
|
||||||
6440
clusters/stand/flux-system/gotk-components.yaml
Normal file
6440
clusters/stand/flux-system/gotk-components.yaml
Normal file
File diff suppressed because it is too large
Load Diff
25
clusters/stand/flux-system/gotk-sync.yaml
Normal file
25
clusters/stand/flux-system/gotk-sync.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: source.toolkit.fluxcd.io/v1
|
||||||
|
kind: GitRepository
|
||||||
|
metadata:
|
||||||
|
name: flux-system
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 1m0s
|
||||||
|
ref:
|
||||||
|
branch: main
|
||||||
|
url: http://gitea.gitea.svc:3000/infra/iac.git
|
||||||
|
secretRef:
|
||||||
|
name: flux-system
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: flux-system
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 10m0s
|
||||||
|
path: ./clusters/stand
|
||||||
|
prune: true
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
13
clusters/stand/flux-system/kustomization.yaml
Normal file
13
clusters/stand/flux-system/kustomization.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- gotk-components.yaml
|
||||||
|
- gotk-sync.yaml
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
kind: Deployment
|
||||||
|
name: kustomize-controller
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/containers/0/args/-
|
||||||
|
value: --feature-gates=CancelHealthCheckOnNewRevision=true
|
||||||
5
clusters/stand/kustomization.yaml
Normal file
5
clusters/stand/kustomization.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ./flux-system
|
||||||
|
- ./layers.yaml
|
||||||
93
clusters/stand/layers.yaml
Normal file
93
clusters/stand/layers.yaml
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: 00-vault
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 10m0s
|
||||||
|
timeout: 10m0s
|
||||||
|
path: ./clusters/stand/00-vault
|
||||||
|
prune: true
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
|
postBuild:
|
||||||
|
substituteFrom:
|
||||||
|
- kind: ConfigMap
|
||||||
|
name: contour-vars
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: 10-mesh
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 10m0s
|
||||||
|
timeout: 15m0s
|
||||||
|
path: ./clusters/stand/10-mesh
|
||||||
|
prune: true
|
||||||
|
wait: true
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
|
postBuild:
|
||||||
|
substituteFrom:
|
||||||
|
- kind: ConfigMap
|
||||||
|
name: contour-vars
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: 20-data
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 10m0s
|
||||||
|
timeout: 20m0s
|
||||||
|
path: ./clusters/stand/20-data
|
||||||
|
prune: true
|
||||||
|
wait: true
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
|
postBuild:
|
||||||
|
substituteFrom:
|
||||||
|
- kind: ConfigMap
|
||||||
|
name: contour-vars
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: 30-platform
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 10m0s
|
||||||
|
timeout: 35m0s
|
||||||
|
path: ./clusters/stand/30-platform
|
||||||
|
prune: true
|
||||||
|
wait: true
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
|
postBuild:
|
||||||
|
substituteFrom:
|
||||||
|
- kind: ConfigMap
|
||||||
|
name: contour-vars
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: 40-routing
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 10m0s
|
||||||
|
timeout: 10m0s
|
||||||
|
path: ./clusters/stand/40-routing
|
||||||
|
prune: true
|
||||||
|
wait: true
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
|
postBuild:
|
||||||
|
substituteFrom:
|
||||||
|
- kind: ConfigMap
|
||||||
|
name: contour-vars
|
||||||
@ -54,4 +54,31 @@ resources:
|
|||||||
- ../../apps/pm/uralkal
|
- ../../apps/pm/uralkal
|
||||||
- ../../apps/message-hub/uralkal
|
- ../../apps/message-hub/uralkal
|
||||||
- ../../apps/cde/uralkal
|
- ../../apps/cde/uralkal
|
||||||
|
- ../../apps/postgres-tls-test/uralkal
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
kind: HelmRelease
|
||||||
|
patch: |
|
||||||
|
- op: add
|
||||||
|
path: /spec/driftDetection
|
||||||
|
value:
|
||||||
|
mode: enabled
|
||||||
|
ignore:
|
||||||
|
- paths: ["/webhooks"]
|
||||||
|
target:
|
||||||
|
kind: MutatingWebhookConfiguration
|
||||||
|
- paths: ["/webhooks"]
|
||||||
|
target:
|
||||||
|
kind: ValidatingWebhookConfiguration
|
||||||
|
- target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: istiod
|
||||||
|
patch: |
|
||||||
|
- op: add
|
||||||
|
path: /spec/driftDetection/ignore/-
|
||||||
|
value:
|
||||||
|
paths: ["/spec/replicas"]
|
||||||
|
target:
|
||||||
|
kind: Deployment
|
||||||
|
|
||||||
|
|||||||
@ -12,3 +12,4 @@ resources:
|
|||||||
- ../../../infrastructure/kb/vk-infra
|
- ../../../infrastructure/kb/vk-infra
|
||||||
- ../../../infrastructure/gitlab-runner/vk-infra
|
- ../../../infrastructure/gitlab-runner/vk-infra
|
||||||
- ../../../infrastructure/nexus/vk-infra
|
- ../../../infrastructure/nexus/vk-infra
|
||||||
|
- ../../../infrastructure/outline/vk-infra
|
||||||
|
|||||||
354
infrastructure/camunda/contour-template/camunda.yaml
Normal file
354
infrastructure/camunda/contour-template/camunda.yaml
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: camunda
|
||||||
|
namespace: camunda
|
||||||
|
spec:
|
||||||
|
dependsOn: []
|
||||||
|
interval: 5m
|
||||||
|
timeout: 30m
|
||||||
|
waitStrategy:
|
||||||
|
name: legacy
|
||||||
|
postRenderers:
|
||||||
|
- kustomize:
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: Deployment
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: processing
|
||||||
|
effect: NoSchedule
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: StatefulSet
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: processing
|
||||||
|
effect: NoSchedule
|
||||||
|
- target:
|
||||||
|
group: batch
|
||||||
|
version: v1
|
||||||
|
kind: Job
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: processing
|
||||||
|
effect: NoSchedule
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: Deployment
|
||||||
|
name: camunda-connectors
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: processing
|
||||||
|
effect: NoSchedule
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: Deployment
|
||||||
|
name: camunda-identity
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: processing
|
||||||
|
effect: NoSchedule
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: Deployment
|
||||||
|
name: camunda-operate
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: processing
|
||||||
|
effect: NoSchedule
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: Deployment
|
||||||
|
name: camunda-optimize
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: processing
|
||||||
|
effect: NoSchedule
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: Deployment
|
||||||
|
name: camunda-tasklist
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: processing
|
||||||
|
effect: NoSchedule
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: Deployment
|
||||||
|
name: camunda-zeebe-gateway
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: processing
|
||||||
|
effect: NoSchedule
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: StatefulSet
|
||||||
|
name: camunda-zeebe
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: processing
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: dedicated
|
||||||
|
operator: Equal
|
||||||
|
value: processing
|
||||||
|
effect: NoSchedule
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
vault:
|
||||||
|
enabled: true
|
||||||
|
role: camunda-contour
|
||||||
|
authPath: auth/kubernetes
|
||||||
|
secrets:
|
||||||
|
postgresql:
|
||||||
|
path: secrets/data/camunda/identity-db
|
||||||
|
keys:
|
||||||
|
password: password
|
||||||
|
postgresPassword: password
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
identity:
|
||||||
|
auth:
|
||||||
|
publicIssuerUrl: "https://${HOST_CAMUNDA_KEYCLOAK}/auth/realms/camunda-platform"
|
||||||
|
identity:
|
||||||
|
redirectUrl: "https://${HOST_CAMUNDA_IDENTITY}"
|
||||||
|
operate:
|
||||||
|
redirectUrl: "https://${HOST_CAMUNDA}"
|
||||||
|
tasklist:
|
||||||
|
redirectUrl: "https://${HOST_CAMUNDA_TASKLIST}"
|
||||||
|
optimize:
|
||||||
|
redirectUrl: "https://${HOST_CAMUNDA_OPTIMIZE}"
|
||||||
|
identityPostgresql:
|
||||||
|
enabled: false
|
||||||
|
auth:
|
||||||
|
usePasswordFiles: true
|
||||||
|
primary:
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
persistence:
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: ${STORAGE_CLASS}
|
||||||
|
identityKeycloak:
|
||||||
|
externalDatabase:
|
||||||
|
host: pg-host.storage.svc.cluster.local
|
||||||
|
port: 5432
|
||||||
|
user: bn_keycloak
|
||||||
|
database: bitnami_keycloak
|
||||||
|
password: ""
|
||||||
|
existingSecret: ""
|
||||||
|
existingSecretPasswordKey: password
|
||||||
|
postgresql:
|
||||||
|
enabled: false
|
||||||
|
auth:
|
||||||
|
usePasswordFiles: true
|
||||||
|
primary:
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
persistence:
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: ${STORAGE_CLASS}
|
||||||
|
vaultEnv:
|
||||||
|
enabled: true
|
||||||
|
role: camunda-contour
|
||||||
|
authPath: auth/kubernetes
|
||||||
|
envFiles:
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD:
|
||||||
|
path: secrets/data/camunda/keycloak-admin
|
||||||
|
key: admin-password
|
||||||
|
KEYCLOAK_PASSWORD:
|
||||||
|
path: secrets/data/camunda/keycloak-admin
|
||||||
|
key: admin-password
|
||||||
|
KEYCLOAK_DATABASE_PASSWORD:
|
||||||
|
path: secrets/data/camunda/keycloak-db
|
||||||
|
key: password
|
||||||
|
global:
|
||||||
|
storageClass: ${STORAGE_CLASS}
|
||||||
|
tolerations: []
|
||||||
|
elasticsearch:
|
||||||
|
sysctlImage:
|
||||||
|
registry: cr.yandex
|
||||||
|
repository: crp3ccidau046kdj8g9q/contour/camunda/os-shell
|
||||||
|
tag: 12-debian-12-r32
|
||||||
|
pullSecrets:
|
||||||
|
- regcred
|
||||||
|
master:
|
||||||
|
replicaCount: 1
|
||||||
|
podAntiAffinityPreset: soft
|
||||||
|
persistence:
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: ${STORAGE_CLASS}
|
||||||
|
tolerations: []
|
||||||
|
metrics:
|
||||||
|
enabled: false
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: false
|
||||||
|
prometheusRule:
|
||||||
|
enabled: false
|
||||||
|
tolerations: []
|
||||||
|
camundaCanary:
|
||||||
|
enabled: false
|
||||||
|
prometheusServiceMonitor:
|
||||||
|
enabled: false
|
||||||
|
console:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
tolerations: []
|
||||||
|
zeebe:
|
||||||
|
clusterSize: "1"
|
||||||
|
partitionCount: "1"
|
||||||
|
replicationFactor: "1"
|
||||||
|
pvcStorageClassName: ${STORAGE_CLASS}
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
tolerations: []
|
||||||
|
zeebeGateway:
|
||||||
|
replicas: 1
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
tolerations: []
|
||||||
|
identity:
|
||||||
|
fullURL: "https://${HOST_CAMUNDA_IDENTITY}"
|
||||||
|
externalDatabase:
|
||||||
|
enabled: true
|
||||||
|
host: pg-host.storage.svc.cluster.local
|
||||||
|
port: 5432
|
||||||
|
username: identity
|
||||||
|
database: identity
|
||||||
|
password: ""
|
||||||
|
existingSecret: ""
|
||||||
|
existingSecretPasswordKey: password
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
tolerations: []
|
||||||
|
operate:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
serviceAccount:
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
tolerations: []
|
||||||
|
tasklist:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
serviceAccount:
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
tolerations: []
|
||||||
|
optimize:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
serviceAccount:
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
tolerations: []
|
||||||
|
executionIdentity:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
tolerations: []
|
||||||
|
webModeler:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
restapi:
|
||||||
|
tolerations: []
|
||||||
|
webapp:
|
||||||
|
tolerations: []
|
||||||
|
websockets:
|
||||||
|
tolerations: []
|
||||||
|
connectors:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
serviceAccount:
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
tolerations: []
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: camunda.yaml
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: cert-manager
|
||||||
|
namespace: cert-manager
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
values:
|
||||||
|
controller:
|
||||||
|
image:
|
||||||
|
registry: cr.yandex/crp3ccidau046kdj8g9q/cert-manager
|
||||||
|
repository: cert-manager
|
||||||
|
acmesolver:
|
||||||
|
image:
|
||||||
|
registry: cr.yandex/crp3ccidau046kdj8g9q/cert-manager
|
||||||
|
repository: acmesolver
|
||||||
|
webhook:
|
||||||
|
image:
|
||||||
|
registry: cr.yandex/crp3ccidau046kdj8g9q/cert-manager
|
||||||
|
repository: cert-manager-webhook
|
||||||
|
cainjector:
|
||||||
|
image:
|
||||||
|
registry: cr.yandex/crp3ccidau046kdj8g9q/cert-manager
|
||||||
|
repository: cainjector
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: cert-manager.yaml
|
||||||
|
- target:
|
||||||
|
group: cert-manager.io
|
||||||
|
version: v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
name: letsencrypt-issuer
|
||||||
|
patch: |-
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-issuer
|
||||||
|
- target:
|
||||||
|
group: cert-manager.io
|
||||||
|
version: v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
|
patch: |-
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
117
infrastructure/istio-config/contour-template/istio-config.yaml
Normal file
117
infrastructure/istio-config/contour-template/istio-config.yaml
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: istio-config
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
dependsOn: []
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
env: contour
|
||||||
|
environments:
|
||||||
|
contour:
|
||||||
|
istio:
|
||||||
|
gateways:
|
||||||
|
platform:
|
||||||
|
name: platform-gateway
|
||||||
|
namespace: default
|
||||||
|
selector:
|
||||||
|
istio: ingressgateway
|
||||||
|
servers:
|
||||||
|
- hosts:
|
||||||
|
- ${HOST_LOGIN}
|
||||||
|
- ${HOST_CAMUNDA}
|
||||||
|
- ${HOST_CAMUNDA_IDENTITY}
|
||||||
|
- ${HOST_CAMUNDA_KEYCLOAK}
|
||||||
|
- ${HOST_CAMUNDA_OPTIMIZE}
|
||||||
|
- ${HOST_CAMUNDA_TASKLIST}
|
||||||
|
- ${HOST_RABBITMQ}
|
||||||
|
tls:
|
||||||
|
credentialName: istio-ingress-tls
|
||||||
|
virtualServices:
|
||||||
|
zitadel:
|
||||||
|
name: zitadel-virt-service
|
||||||
|
namespace: default
|
||||||
|
hosts:
|
||||||
|
- ${HOST_LOGIN}
|
||||||
|
gateways:
|
||||||
|
- default/platform-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: zitadel-idp-contour.zitadel.svc.cluster.local
|
||||||
|
port: 8080
|
||||||
|
camunda-operate:
|
||||||
|
name: camunda-operate-virt-service
|
||||||
|
namespace: default
|
||||||
|
hosts:
|
||||||
|
- ${HOST_CAMUNDA}
|
||||||
|
gateways:
|
||||||
|
- default/platform-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: camunda-operate.camunda.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
camunda-identity:
|
||||||
|
name: camunda-identity-virt-service
|
||||||
|
namespace: default
|
||||||
|
hosts:
|
||||||
|
- ${HOST_CAMUNDA_IDENTITY}
|
||||||
|
gateways:
|
||||||
|
- default/platform-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: camunda-identity.camunda.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
camunda-keycloak:
|
||||||
|
name: camunda-keycloak-virt-service
|
||||||
|
namespace: default
|
||||||
|
hosts:
|
||||||
|
- ${HOST_CAMUNDA_KEYCLOAK}
|
||||||
|
gateways:
|
||||||
|
- default/platform-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: camunda-keycloak.camunda.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
camunda-optimize:
|
||||||
|
name: camunda-optimize-virt-service
|
||||||
|
namespace: default
|
||||||
|
hosts:
|
||||||
|
- ${HOST_CAMUNDA_OPTIMIZE}
|
||||||
|
gateways:
|
||||||
|
- default/platform-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: camunda-optimize.camunda.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
camunda-tasklist:
|
||||||
|
name: camunda-tasklist-virt-service
|
||||||
|
namespace: default
|
||||||
|
hosts:
|
||||||
|
- ${HOST_CAMUNDA_TASKLIST}
|
||||||
|
gateways:
|
||||||
|
- default/platform-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: camunda-tasklist.camunda.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
rabbitmq:
|
||||||
|
name: rabbitmq-virt-service
|
||||||
|
namespace: default
|
||||||
|
hosts:
|
||||||
|
- ${HOST_RABBITMQ}
|
||||||
|
gateways:
|
||||||
|
- default/platform-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: rabbitmq.rabbitmq.svc.cluster.local
|
||||||
|
port: 15672
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: istio-config.yaml
|
||||||
@ -58,6 +58,18 @@ spec:
|
|||||||
renewBefore: 360h0m0s
|
renewBefore: 360h0m0s
|
||||||
privateKey:
|
privateKey:
|
||||||
rotationPolicy: Always
|
rotationPolicy: Always
|
||||||
|
srx-ui-kit-tls:
|
||||||
|
dnsNames:
|
||||||
|
- srx-ui-kit.infra.sarex.tech
|
||||||
|
- srx-ui-kit-stage.infra.sarex.tech
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
|
kind: ClusterIssuer
|
||||||
|
spec:
|
||||||
|
duration: 2160h0m0s
|
||||||
|
renewBefore: 360h0m0s
|
||||||
|
privateKey:
|
||||||
|
rotationPolicy: Always
|
||||||
nexus-tls:
|
nexus-tls:
|
||||||
dnsNames:
|
dnsNames:
|
||||||
- nexus.infra.sarex.tech
|
- nexus.infra.sarex.tech
|
||||||
@ -80,6 +92,28 @@ spec:
|
|||||||
renewBefore: 360h0m0s
|
renewBefore: 360h0m0s
|
||||||
privateKey:
|
privateKey:
|
||||||
rotationPolicy: Always
|
rotationPolicy: Always
|
||||||
|
wiki-tls:
|
||||||
|
dnsNames:
|
||||||
|
- wiki.sarex.space
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
|
kind: ClusterIssuer
|
||||||
|
spec:
|
||||||
|
duration: 2160h0m0s
|
||||||
|
renewBefore: 360h0m0s
|
||||||
|
privateKey:
|
||||||
|
rotationPolicy: Always
|
||||||
|
goalert-tls:
|
||||||
|
dnsNames:
|
||||||
|
- goalert.infra.sarex.io
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
|
kind: ClusterIssuer
|
||||||
|
spec:
|
||||||
|
duration: 2160h0m0s
|
||||||
|
renewBefore: 360h0m0s
|
||||||
|
privateKey:
|
||||||
|
rotationPolicy: Always
|
||||||
istio:
|
istio:
|
||||||
gateways:
|
gateways:
|
||||||
vault:
|
vault:
|
||||||
@ -104,6 +138,18 @@ spec:
|
|||||||
tls:
|
tls:
|
||||||
credentialName: kb-tls
|
credentialName: kb-tls
|
||||||
mode: SIMPLE
|
mode: SIMPLE
|
||||||
|
srx-ui-kit:
|
||||||
|
name: srx-ui-kit-gateway
|
||||||
|
namespace: istio-system
|
||||||
|
selector:
|
||||||
|
istio: ingressgateway
|
||||||
|
servers:
|
||||||
|
- hosts:
|
||||||
|
- srx-ui-kit.infra.sarex.tech
|
||||||
|
- srx-ui-kit-stage.infra.sarex.tech
|
||||||
|
tls:
|
||||||
|
credentialName: srx-ui-kit-tls
|
||||||
|
mode: SIMPLE
|
||||||
nexus:
|
nexus:
|
||||||
name: nexus-gateway
|
name: nexus-gateway
|
||||||
namespace: istio-system
|
namespace: istio-system
|
||||||
@ -126,6 +172,28 @@ spec:
|
|||||||
tls:
|
tls:
|
||||||
credentialName: idp-tls
|
credentialName: idp-tls
|
||||||
mode: SIMPLE
|
mode: SIMPLE
|
||||||
|
wiki:
|
||||||
|
name: wiki-gateway
|
||||||
|
namespace: istio-system
|
||||||
|
selector:
|
||||||
|
istio: ingressgateway
|
||||||
|
servers:
|
||||||
|
- hosts:
|
||||||
|
- wiki.sarex.space
|
||||||
|
tls:
|
||||||
|
credentialName: wiki-tls
|
||||||
|
mode: SIMPLE
|
||||||
|
goalert:
|
||||||
|
name: goalert-gateway
|
||||||
|
namespace: istio-system
|
||||||
|
selector:
|
||||||
|
istio: ingressgateway
|
||||||
|
servers:
|
||||||
|
- hosts:
|
||||||
|
- goalert.infra.sarex.io
|
||||||
|
tls:
|
||||||
|
credentialName: goalert-tls
|
||||||
|
mode: SIMPLE
|
||||||
virtualServices:
|
virtualServices:
|
||||||
vault-virt-service:
|
vault-virt-service:
|
||||||
namespace: istio-system
|
namespace: istio-system
|
||||||
@ -149,6 +217,28 @@ spec:
|
|||||||
prefix: /
|
prefix: /
|
||||||
service: kb-service.kb.svc.cluster.local
|
service: kb-service.kb.svc.cluster.local
|
||||||
port: 80
|
port: 80
|
||||||
|
srx-ui-kit-virt-service:
|
||||||
|
namespace: istio-system
|
||||||
|
hosts:
|
||||||
|
- srx-ui-kit.infra.sarex.tech
|
||||||
|
gateways:
|
||||||
|
- istio-system/srx-ui-kit-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: universal-static-site-srx-ui-kit-pages.gitlab-pages-srx-ui-kit.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
srx-ui-kit-stage-virt-service:
|
||||||
|
namespace: istio-system
|
||||||
|
hosts:
|
||||||
|
- srx-ui-kit-stage.infra.sarex.tech
|
||||||
|
gateways:
|
||||||
|
- istio-system/srx-ui-kit-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: universal-static-site-srx-ui-kit-stage-pages.gitlab-pages-srx-ui-kit-stage.svc.cluster.local
|
||||||
|
port: 80
|
||||||
nexus-virt-service:
|
nexus-virt-service:
|
||||||
namespace: istio-system
|
namespace: istio-system
|
||||||
hosts:
|
hosts:
|
||||||
@ -171,3 +261,25 @@ spec:
|
|||||||
prefix: /
|
prefix: /
|
||||||
service: idp.zitadel.svc.cluster.local
|
service: idp.zitadel.svc.cluster.local
|
||||||
port: 8080
|
port: 8080
|
||||||
|
wiki-virt-service:
|
||||||
|
namespace: istio-system
|
||||||
|
hosts:
|
||||||
|
- wiki.sarex.space
|
||||||
|
gateways:
|
||||||
|
- istio-system/wiki-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: outline.outline.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
goalert-virt-service:
|
||||||
|
namespace: istio-system
|
||||||
|
hosts:
|
||||||
|
- goalert.infra.sarex.io
|
||||||
|
gateways:
|
||||||
|
- istio-system/goalert-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: goalert.goalert.svc.cluster.local
|
||||||
|
port: 8081
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: ingressgateway
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
dependsOn:
|
||||||
|
- name: istio-base
|
||||||
|
namespace: istio-system
|
||||||
|
- name: istiod
|
||||||
|
namespace: istio-system
|
||||||
|
values:
|
||||||
|
_internal_defaults_do_not_set:
|
||||||
|
replicaCount: ${GATEWAY_REPLICAS}
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
nodeSelector:
|
||||||
|
sarex.io/gateway: "true"
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
istio: ingressgateway
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/control-plane
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: istio-gateway.yaml
|
||||||
12
infrastructure/istio-pilot/contour-template/istio-pilot.yaml
Normal file
12
infrastructure/istio-pilot/contour-template/istio-pilot.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: istiod
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
imagePullSecrets:
|
||||||
|
- regcred
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: istio-pilot.yaml
|
||||||
@ -13,3 +13,34 @@ spec:
|
|||||||
global:
|
global:
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- regcred
|
- regcred
|
||||||
|
# Провайдер журнала доступа для security-audit (access-collector). Сам по себе ничего не включает:
|
||||||
|
# работает только для workload'ов, на которые ссылается Telemetry (infrastructure/security-audit/mesh).
|
||||||
|
meshConfig:
|
||||||
|
extensionProviders:
|
||||||
|
- name: audit-access
|
||||||
|
envoyOtelAls:
|
||||||
|
service: access-collector.security-audit.svc.cluster.local
|
||||||
|
port: 4317
|
||||||
|
logFormat:
|
||||||
|
labels:
|
||||||
|
sarex_kind: "mesh_access"
|
||||||
|
start_time: "%START_TIME%"
|
||||||
|
request_id: "%REQ(X-REQUEST-ID)%"
|
||||||
|
trace_id: "%TRACE_ID%"
|
||||||
|
authority: "%REQ(:AUTHORITY)%"
|
||||||
|
method: "%REQ(:METHOD)%"
|
||||||
|
path: "%REQ_WITHOUT_QUERY(:PATH)%"
|
||||||
|
protocol: "%PROTOCOL%"
|
||||||
|
response_code: "%RESPONSE_CODE%"
|
||||||
|
response_flags: "%RESPONSE_FLAGS%"
|
||||||
|
duration_ms: "%DURATION%"
|
||||||
|
bytes_in: "%BYTES_RECEIVED%"
|
||||||
|
bytes_out: "%BYTES_SENT%"
|
||||||
|
x_forwarded_for: "%REQ(X-FORWARDED-FOR)%"
|
||||||
|
downstream_ip: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
|
||||||
|
user_sub: "%REQ(X-SAREX-AUDIT-SUB)%"
|
||||||
|
user_org: "%REQ(X-SAREX-AUDIT-ORG)%"
|
||||||
|
user_agent: "%REQ(USER-AGENT)%"
|
||||||
|
route: "%ROUTE_NAME%"
|
||||||
|
upstream_cluster: "%UPSTREAM_CLUSTER%"
|
||||||
|
# Authorization, Cookie, Set-Cookie и query-строка не логируются (п.23)
|
||||||
|
|||||||
73
infrastructure/kafka/contour-template/kafka.yaml
Normal file
73
infrastructure/kafka/contour-template/kafka.yaml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: kafka
|
||||||
|
namespace: kafka
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
postRenderers:
|
||||||
|
- kustomize:
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: StatefulSet
|
||||||
|
namespace: kafka
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: generic
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value: []
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
imagePullSecrets:
|
||||||
|
- regcred
|
||||||
|
defaultStorageClass: ${STORAGE_CLASS}
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- regcred
|
||||||
|
controller:
|
||||||
|
replicaCount: 1
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
persistence:
|
||||||
|
size: 8Gi
|
||||||
|
storageClass: ${STORAGE_CLASS}
|
||||||
|
overrideConfiguration:
|
||||||
|
offsets.topic.replication.factor: 1
|
||||||
|
transaction.state.log.replication.factor: 1
|
||||||
|
transaction.state.log.min.isr: 1
|
||||||
|
default.replication.factor: 1
|
||||||
|
min.insync.replicas: 1
|
||||||
|
broker:
|
||||||
|
replicaCount: 0
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
listeners:
|
||||||
|
client:
|
||||||
|
protocol: SASL_PLAINTEXT
|
||||||
|
sslClientAuth: "none"
|
||||||
|
provisioning:
|
||||||
|
enabled: false
|
||||||
|
sasl:
|
||||||
|
managedExistingSecret:
|
||||||
|
enabled: false
|
||||||
|
existingSecret: ""
|
||||||
|
enabledMechanisms: PLAIN,SCRAM-SHA-512
|
||||||
|
interBrokerMechanism: PLAIN
|
||||||
|
controllerMechanism: PLAIN
|
||||||
|
client:
|
||||||
|
users: []
|
||||||
|
passwords: ""
|
||||||
|
tls:
|
||||||
|
type: PEM
|
||||||
|
vault:
|
||||||
|
enabled: true
|
||||||
|
role: kafka
|
||||||
|
authPath: auth/kubernetes
|
||||||
|
secretPath: secrets/data/kafka/bootstrap
|
||||||
|
clusterIdKey: clusterId
|
||||||
|
interBrokerPasswordKey: interBrokerPassword
|
||||||
|
controllerPasswordKey: controllerPassword
|
||||||
6
infrastructure/kafka/contour-template/kustomization.yaml
Normal file
6
infrastructure/kafka/contour-template/kustomization.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: kafka.yaml
|
||||||
113
infrastructure/kafka/uralkal/kafka-scram-users-job.yaml
Normal file
113
infrastructure/kafka/uralkal/kafka-scram-users-job.yaml
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: kafka-scram-users
|
||||||
|
namespace: kafka
|
||||||
|
annotations:
|
||||||
|
kustomize.toolkit.fluxcd.io/force: enabled
|
||||||
|
spec:
|
||||||
|
backoffLimit: 2
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
sidecar.istio.io/inject: "false"
|
||||||
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||||
|
vault.hashicorp.com/auth-path: auth/kubernetes
|
||||||
|
vault.hashicorp.com/role: kafka
|
||||||
|
vault.hashicorp.com/agent-inject-secret-admin: secrets/data/kafka/bootstrap
|
||||||
|
vault.hashicorp.com/agent-inject-template-admin: |
|
||||||
|
{{- with secret "secrets/data/kafka/bootstrap" -}}{{ index .Data.data "interBrokerPassword" }}{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-pm-user: secrets/data/kafka/apps/pm
|
||||||
|
vault.hashicorp.com/agent-inject-template-pm-user: |
|
||||||
|
{{- with secret "secrets/data/kafka/apps/pm" -}}{{ index .Data.data "username" }}{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-pm-pass: secrets/data/kafka/apps/pm
|
||||||
|
vault.hashicorp.com/agent-inject-template-pm-pass: |
|
||||||
|
{{- with secret "secrets/data/kafka/apps/pm" -}}{{ index .Data.data "password" }}{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-issues-user: secrets/data/kafka/apps/issues
|
||||||
|
vault.hashicorp.com/agent-inject-template-issues-user: |
|
||||||
|
{{- with secret "secrets/data/kafka/apps/issues" -}}{{ index .Data.data "username" }}{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-issues-pass: secrets/data/kafka/apps/issues
|
||||||
|
vault.hashicorp.com/agent-inject-template-issues-pass: |
|
||||||
|
{{- with secret "secrets/data/kafka/apps/issues" -}}{{ index .Data.data "password" }}{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-flows-user: secrets/data/kafka/apps/flows
|
||||||
|
vault.hashicorp.com/agent-inject-template-flows-user: |
|
||||||
|
{{- with secret "secrets/data/kafka/apps/flows" -}}{{ index .Data.data "username" }}{{- end -}}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-flows-pass: secrets/data/kafka/apps/flows
|
||||||
|
vault.hashicorp.com/agent-inject-template-flows-pass: |
|
||||||
|
{{- with secret "secrets/data/kafka/apps/flows" -}}{{ index .Data.data "password" }}{{- end -}}
|
||||||
|
spec:
|
||||||
|
serviceAccountName: kafka-kafka-contour
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
restartPolicy: Never
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
nodeSelector:
|
||||||
|
dedicated: generic
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 1001
|
||||||
|
runAsNonRoot: true
|
||||||
|
fsGroup: 1001
|
||||||
|
containers:
|
||||||
|
- name: kafka-scram-users
|
||||||
|
image: cr.yandex/crp3ccidau046kdj8g9q/contour/kafka-nn/kafka:4.0.0-debian-12-r10
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: KAFKA_HEAP_OPTS
|
||||||
|
value: -Xmx256m
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -ec
|
||||||
|
- |
|
||||||
|
K=/opt/bitnami/kafka/bin
|
||||||
|
ADMIN=kafka-kafka-contour-controller-0.kafka-kafka-contour-controller-headless.kafka.svc.cluster.local:9094
|
||||||
|
CLIENT=kafka-kafka-contour.kafka.svc.cluster.local:9092
|
||||||
|
S=/vault/secrets
|
||||||
|
rc=0
|
||||||
|
printf 'security.protocol=SASL_PLAINTEXT\nsasl.mechanism=PLAIN\nsasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="inter_broker_user" password="%s";\n' "$(cat $S/admin)" > /tmp/admin.conf
|
||||||
|
for app in pm issues flows; do
|
||||||
|
u=$(cat $S/$app-user); p=$(cat $S/$app-pass)
|
||||||
|
if [ -z "$u" ] || [ -z "$p" ]; then
|
||||||
|
echo "$app: empty username or password in kafka/apps/$app"; rc=1; continue
|
||||||
|
fi
|
||||||
|
if [ -f "/tmp/pw-$u" ]; then
|
||||||
|
if [ "$(cat /tmp/pw-$u)" != "$p" ]; then
|
||||||
|
echo "$app: user $u already set from another app with a different password, skipped"; rc=1
|
||||||
|
else
|
||||||
|
echo "$app: user $u already set, same password"
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf '%s' "$p" > "/tmp/pw-$u"
|
||||||
|
$K/kafka-configs.sh --bootstrap-server $ADMIN --command-config /tmp/admin.conf --alter --add-config "SCRAM-SHA-512=[iterations=4096,password=$p]" --entity-type users --entity-name "$u" > /dev/null
|
||||||
|
echo "$app: SCRAM-SHA-512 set for user $u"
|
||||||
|
done
|
||||||
|
sleep 5
|
||||||
|
for app in pm issues flows; do
|
||||||
|
u=$(cat $S/$app-user); p=$(cat $S/$app-pass)
|
||||||
|
printf 'security.protocol=SASL_SSL\nsasl.mechanism=SCRAM-SHA-512\nssl.truststore.type=PEM\nssl.truststore.location=/kafka-ca/ca.crt\nsasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="%s" password="%s";\n' "$u" "$p" > /tmp/$app.conf
|
||||||
|
if $K/kafka-topics.sh --bootstrap-server $CLIENT --command-config /tmp/$app.conf --list > /dev/null; then
|
||||||
|
echo "$app: login as $u over SASL_SSL ok"
|
||||||
|
else
|
||||||
|
echo "$app: login as $u over SASL_SSL FAILED"; rc=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
$K/kafka-configs.sh --bootstrap-server $ADMIN --command-config /tmp/admin.conf --describe --entity-type users
|
||||||
|
rm -f /tmp/*.conf /tmp/pw-*
|
||||||
|
exit $rc
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 384Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: kafka-ca
|
||||||
|
mountPath: /kafka-ca
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: kafka-ca
|
||||||
|
secret:
|
||||||
|
secretName: kafka-kafka-contour-tls
|
||||||
|
items:
|
||||||
|
- key: ca.crt
|
||||||
|
path: ca.crt
|
||||||
@ -2,5 +2,6 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
|||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
resources:
|
resources:
|
||||||
- ../base
|
- ../base
|
||||||
|
- kafka-scram-users-job.yaml
|
||||||
patches:
|
patches:
|
||||||
- path: kafka.yaml
|
- path: kafka.yaml
|
||||||
|
|||||||
6
infrastructure/outline/vk-infra/kustomization.yaml
Normal file
6
infrastructure/outline/vk-infra/kustomization.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- valkey.yaml
|
||||||
|
- outline.yaml
|
||||||
6
infrastructure/outline/vk-infra/namespace.yaml
Normal file
6
infrastructure/outline/vk-infra/namespace.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: outline
|
||||||
|
labels:
|
||||||
|
istio-injection: enabled
|
||||||
139
infrastructure/outline/vk-infra/outline.yaml
Normal file
139
infrastructure/outline/vk-infra/outline.yaml
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: outline
|
||||||
|
namespace: outline
|
||||||
|
spec:
|
||||||
|
interval: 10m
|
||||||
|
timeout: 10m
|
||||||
|
chart:
|
||||||
|
spec:
|
||||||
|
chart: outline
|
||||||
|
version: "0.1.0"
|
||||||
|
sourceRef:
|
||||||
|
kind: HelmRepository
|
||||||
|
name: yc-oci-charts
|
||||||
|
namespace: flux-system
|
||||||
|
interval: 10m
|
||||||
|
install:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
upgrade:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
dependsOn:
|
||||||
|
- name: istiod
|
||||||
|
namespace: istio-system
|
||||||
|
- name: outline-valkey
|
||||||
|
namespace: outline
|
||||||
|
values:
|
||||||
|
universal-chart:
|
||||||
|
global:
|
||||||
|
env: infra
|
||||||
|
image:
|
||||||
|
name:
|
||||||
|
infra: cr.yandex/crp3ccidau046kdj8g9q/outlinewiki/outline:1.12.0
|
||||||
|
envs:
|
||||||
|
- name: NODE_ENV
|
||||||
|
value:
|
||||||
|
_default: production
|
||||||
|
- name: URL
|
||||||
|
value:
|
||||||
|
_default: https://wiki.sarex.space
|
||||||
|
- name: PORT
|
||||||
|
value:
|
||||||
|
_default: "3000"
|
||||||
|
- name: WEB_CONCURRENCY
|
||||||
|
value:
|
||||||
|
_default: "1"
|
||||||
|
# TLS терминируется на istio ingressgateway
|
||||||
|
- name: FORCE_HTTPS
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
|
- name: ENABLE_UPDATES
|
||||||
|
value:
|
||||||
|
_default: "false"
|
||||||
|
- name: LOG_LEVEL
|
||||||
|
value:
|
||||||
|
_default: info
|
||||||
|
- name: PGSSLMODE
|
||||||
|
value:
|
||||||
|
_default: disable
|
||||||
|
- name: FILE_STORAGE
|
||||||
|
value:
|
||||||
|
_default: s3
|
||||||
|
- name: AWS_REGION
|
||||||
|
value:
|
||||||
|
_default: ru-msk
|
||||||
|
- name: AWS_S3_UPLOAD_BUCKET_URL
|
||||||
|
value:
|
||||||
|
_default: https://hb.ru-msk.vkcloud-storage.ru
|
||||||
|
- name: AWS_S3_UPLOAD_BUCKET_NAME
|
||||||
|
value:
|
||||||
|
_default: sarex-wiki
|
||||||
|
- name: AWS_S3_FORCE_PATH_STYLE
|
||||||
|
value:
|
||||||
|
_default: "true"
|
||||||
|
- name: AWS_S3_ACL
|
||||||
|
value:
|
||||||
|
_default: private
|
||||||
|
# VK Object Storage документирует presigned PUT; presigned POST не описан
|
||||||
|
- name: AWS_S3_UPLOAD_METHOD
|
||||||
|
value:
|
||||||
|
_default: put
|
||||||
|
- name: OIDC_DISPLAY_NAME
|
||||||
|
value:
|
||||||
|
_default: Sarex IAM
|
||||||
|
- name: OIDC_AUTH_URI
|
||||||
|
value:
|
||||||
|
_default: https://iam.sarex.io/oauth/v2/authorize
|
||||||
|
- name: OIDC_TOKEN_URI
|
||||||
|
value:
|
||||||
|
_default: https://iam.sarex.io/oauth/v2/token
|
||||||
|
- name: OIDC_USERINFO_URI
|
||||||
|
value:
|
||||||
|
_default: https://iam.sarex.io/oidc/v1/userinfo
|
||||||
|
- name: OIDC_LOGOUT_URI
|
||||||
|
value:
|
||||||
|
_default: https://iam.sarex.io/oidc/v1/end_session
|
||||||
|
- name: OIDC_USERNAME_CLAIM
|
||||||
|
value:
|
||||||
|
_default: preferred_username
|
||||||
|
- name: OIDC_SCOPES
|
||||||
|
value:
|
||||||
|
_default: openid profile email offline_access
|
||||||
|
# SMTP подключается отдельным шагом: добавить SMTP_HOST, SMTP_PORT,
|
||||||
|
# SMTP_FROM_EMAIL сюда и SMTP_USERNAME, SMTP_PASSWORD в secretEnvs.
|
||||||
|
secretEnvs:
|
||||||
|
- name: SECRET_KEY
|
||||||
|
secretName:
|
||||||
|
_default: outline
|
||||||
|
secretKey: SECRET_KEY
|
||||||
|
- name: UTILS_SECRET
|
||||||
|
secretName:
|
||||||
|
_default: outline
|
||||||
|
secretKey: UTILS_SECRET
|
||||||
|
- name: DATABASE_URL
|
||||||
|
secretName:
|
||||||
|
_default: outline-postgresql
|
||||||
|
secretKey: url
|
||||||
|
- name: REDIS_URL
|
||||||
|
secretName:
|
||||||
|
_default: outline
|
||||||
|
secretKey: REDIS_URL
|
||||||
|
- name: AWS_ACCESS_KEY_ID
|
||||||
|
secretName:
|
||||||
|
_default: outline
|
||||||
|
secretKey: AWS_ACCESS_KEY_ID
|
||||||
|
- name: AWS_SECRET_ACCESS_KEY
|
||||||
|
secretName:
|
||||||
|
_default: outline
|
||||||
|
secretKey: AWS_SECRET_ACCESS_KEY
|
||||||
|
- name: OIDC_CLIENT_ID
|
||||||
|
secretName:
|
||||||
|
_default: outline
|
||||||
|
secretKey: OIDC_CLIENT_ID
|
||||||
|
- name: OIDC_CLIENT_SECRET
|
||||||
|
secretName:
|
||||||
|
_default: outline
|
||||||
|
secretKey: OIDC_CLIENT_SECRET
|
||||||
62
infrastructure/outline/vk-infra/valkey.yaml
Normal file
62
infrastructure/outline/vk-infra/valkey.yaml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: outline-valkey
|
||||||
|
namespace: outline
|
||||||
|
spec:
|
||||||
|
interval: 10m
|
||||||
|
timeout: 10m
|
||||||
|
chart:
|
||||||
|
spec:
|
||||||
|
chart: valkey
|
||||||
|
version: "0.1.0"
|
||||||
|
sourceRef:
|
||||||
|
kind: HelmRepository
|
||||||
|
name: yc-oci-charts
|
||||||
|
namespace: flux-system
|
||||||
|
interval: 10m
|
||||||
|
install:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
upgrade:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
values:
|
||||||
|
fullnameOverride: outline-valkey
|
||||||
|
image:
|
||||||
|
registry: cr.yandex
|
||||||
|
repository: crp3ccidau046kdj8g9q/valkey/valkey
|
||||||
|
tag: 8.0.2-debian-12-r6
|
||||||
|
global:
|
||||||
|
imagePullSecrets:
|
||||||
|
- istio-registry-secret
|
||||||
|
security:
|
||||||
|
allowInsecureImages: true
|
||||||
|
architecture: standalone
|
||||||
|
auth:
|
||||||
|
enabled: true
|
||||||
|
sentinel: false
|
||||||
|
existingSecret: outline-valkey
|
||||||
|
existingSecretPasswordKey: valkey-password
|
||||||
|
# Outline хранит в Valkey очереди фоновых задач и состояние websocket.
|
||||||
|
# Персистентность не нужна, вытеснение ключей недопустимо для очередей.
|
||||||
|
commonConfiguration: |-
|
||||||
|
appendonly no
|
||||||
|
save ""
|
||||||
|
maxmemory 256mb
|
||||||
|
maxmemory-policy noeviction
|
||||||
|
primary:
|
||||||
|
podAnnotations:
|
||||||
|
sidecar.istio.io/inject: "false"
|
||||||
|
nodeSelector:
|
||||||
|
sarex.io/pool: svc
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
memory: 384Mi
|
||||||
|
persistence:
|
||||||
|
enabled: false
|
||||||
|
metrics:
|
||||||
|
enabled: false
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: postgresql.yaml
|
||||||
104
infrastructure/postgresql/contour-template/postgresql.yaml
Normal file
104
infrastructure/postgresql/contour-template/postgresql.yaml
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: postgresql
|
||||||
|
namespace: postgresql
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 2h
|
||||||
|
|
||||||
|
install:
|
||||||
|
timeout: 2h
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
upgrade:
|
||||||
|
timeout: 2h
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
security:
|
||||||
|
allowInsecureImages: true
|
||||||
|
defaultStorageClass: ${STORAGE_CLASS}
|
||||||
|
postgresql:
|
||||||
|
auth:
|
||||||
|
username: ""
|
||||||
|
database: ""
|
||||||
|
secretKeys:
|
||||||
|
userPasswordKey: "postgres-password"
|
||||||
|
auth:
|
||||||
|
username: ""
|
||||||
|
database: ""
|
||||||
|
secretKeys:
|
||||||
|
userPasswordKey: "postgres-password"
|
||||||
|
image:
|
||||||
|
registry: cr.yandex/crp3ccidau046kdj8g9q
|
||||||
|
repository: contour/postgresql
|
||||||
|
tag: 17.0.7
|
||||||
|
pullPolicy: Always
|
||||||
|
metrics:
|
||||||
|
enabled: false
|
||||||
|
prometheusRule:
|
||||||
|
enabled: false
|
||||||
|
primary:
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
containerSecurityContext:
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
persistence:
|
||||||
|
storageClass: ${STORAGE_CLASS}
|
||||||
|
size: 20Gi
|
||||||
|
customLivenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- exec pg_isready -U "postgres" -d postgres -h 127.0.0.1 -p 5432
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 6
|
||||||
|
customReadinessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- exec pg_isready -U "postgres" -d postgres -h 127.0.0.1 -p 5432
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 6
|
||||||
|
customStartupProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- exec pg_isready -U "postgres" -d postgres -h 127.0.0.1 -p 5432
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 6
|
||||||
|
nodeSelector:
|
||||||
|
dedicated: generic
|
||||||
|
tolerations: []
|
||||||
|
contour:
|
||||||
|
enabled: true
|
||||||
|
adminUser: "postgres"
|
||||||
|
vault:
|
||||||
|
enabled: true
|
||||||
|
role: postgresql-zitadel
|
||||||
|
authPath: auth/kubernetes
|
||||||
|
secretPath: secrets/data/postgresql-zitadel/admin
|
||||||
|
secretKey: postgres-password
|
||||||
|
usersSecretPath: secrets/data/zitadel/postgresql
|
||||||
|
sharedPreloadLibraries: "timescaledb,pg_stat_statements"
|
||||||
|
databases:
|
||||||
|
- name: zitadel
|
||||||
|
user: zitadel
|
||||||
|
passwordKey: password
|
||||||
|
extensions: []
|
||||||
|
restoreFromDump: false
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: rabbitmq.yaml
|
||||||
104
infrastructure/rabbitmq/contour-template/rabbitmq.yaml
Normal file
104
infrastructure/rabbitmq/contour-template/rabbitmq.yaml
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: rabbitmq
|
||||||
|
namespace: rabbitmq
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
postRenderers:
|
||||||
|
- kustomize:
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: StatefulSet
|
||||||
|
namespace: rabbitmq
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
dedicated: generic
|
||||||
|
- op: add
|
||||||
|
path: /spec/template/spec/tolerations
|
||||||
|
value: []
|
||||||
|
- target:
|
||||||
|
group: cert-manager.io
|
||||||
|
version: v1
|
||||||
|
kind: Certificate
|
||||||
|
namespace: gateway
|
||||||
|
name: rmq-tls
|
||||||
|
patch: |-
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: rmq-tls
|
||||||
|
namespace: gateway
|
||||||
|
- target:
|
||||||
|
group: networking.istio.io
|
||||||
|
version: v1beta1
|
||||||
|
kind: Gateway
|
||||||
|
namespace: gateway
|
||||||
|
name: rmq-gateway
|
||||||
|
patch: |-
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: networking.istio.io/v1beta1
|
||||||
|
kind: Gateway
|
||||||
|
metadata:
|
||||||
|
name: rmq-gateway
|
||||||
|
namespace: gateway
|
||||||
|
- target:
|
||||||
|
group: networking.istio.io
|
||||||
|
version: v1
|
||||||
|
kind: VirtualService
|
||||||
|
namespace: rabbitmq
|
||||||
|
name: rmq-virt-service
|
||||||
|
patch: |-
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: networking.istio.io/v1
|
||||||
|
kind: VirtualService
|
||||||
|
metadata:
|
||||||
|
name: rmq-virt-service
|
||||||
|
namespace: rabbitmq
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
security:
|
||||||
|
allowInsecureImages: true
|
||||||
|
virtualService: null
|
||||||
|
gateway: null
|
||||||
|
certificate: null
|
||||||
|
metrics:
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: false
|
||||||
|
default:
|
||||||
|
enabled: false
|
||||||
|
perObject:
|
||||||
|
enabled: false
|
||||||
|
detailed:
|
||||||
|
enabled: false
|
||||||
|
extraServiceMonitors: []
|
||||||
|
replicaCount: 1
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 1Gi
|
||||||
|
persistence:
|
||||||
|
storageClass: ${STORAGE_CLASS}
|
||||||
|
size: 10Gi
|
||||||
|
auth:
|
||||||
|
securePassword: true
|
||||||
|
enableLoopbackUser: false
|
||||||
|
existingPasswordSecret: ""
|
||||||
|
vault:
|
||||||
|
enabled: true
|
||||||
|
role: rabbitmq
|
||||||
|
authPath: auth/kubernetes
|
||||||
|
secretPath: secrets/data/rabbitmq/auth
|
||||||
|
usernameKey: username
|
||||||
|
passwordKey: password
|
||||||
|
advancedConfiguration: |-
|
||||||
|
[
|
||||||
|
{rabbit, [
|
||||||
|
{loopback_users, []}
|
||||||
|
]}
|
||||||
|
].
|
||||||
690
infrastructure/security-audit/README.md
Normal file
690
infrastructure/security-audit/README.md
Normal file
@ -0,0 +1,690 @@
|
|||||||
|
# security-audit
|
||||||
|
|
||||||
|
Поток ИБ в Kafka, четыре источника:
|
||||||
|
|
||||||
|
| Источник | Коллектор | Топик |
|
||||||
|
|---|---|---|
|
||||||
|
| журнал аудита kube-apiserver (мастера) | DaemonSet `audit-collector` | `security.k8s-audit` |
|
||||||
|
| журнал доступа ingress-шлюза Istio | Deployment `access-collector`, pipeline `logs/access` | `security.mesh-access` |
|
||||||
|
| события Zitadel (Actions v2) | Deployment `access-collector`, pipeline `logs/idp` | `security.idp-events` |
|
||||||
|
| журнал аудита Vault | sidecar `audit-shipper` в подах Vault → `access-collector`, pipeline `logs/vault` | `security.vault-audit` |
|
||||||
|
|
||||||
|
Одно сообщение — одна запись в JSON (`encoding: raw`, без OTLP-обёртки), обогащённая полями `sarex_*`.
|
||||||
|
Дальнейшие этапы (processor → MinIO/Parquet) — отдельными компонентами.
|
||||||
|
|
||||||
|
С observability-коллектором (`infrastructure/opentelemetry-collector`) не связан: отдельный релиз, namespace и конфиг.
|
||||||
|
|
||||||
|
| Каталог | Что | Namespace |
|
||||||
|
|---|---|---|
|
||||||
|
| `base/` | оба коллектора, NetworkPolicy, AuthorizationPolicy, Namespace | `security-audit` |
|
||||||
|
| `base/idp-actions-bootstrap/` | Job настройки Target/Execution'ов в Zitadel, скрипт и список событий | `security-audit` |
|
||||||
|
| `mesh/` | EnvoyFilter и Telemetry шлюза; без `namespace` в kustomization, селектор шлюза — заглушка | `istio-system` |
|
||||||
|
| `uralkal/` | оверлей: `base` + `mesh`, селектор шлюза, Kafka, `dependsOn` | — |
|
||||||
|
| `../vault/uralkal-audit/` | Vault-часть журнала аудита Vault: оверлей поверх `vault/uralkal` (не подключён) | `vault` |
|
||||||
|
|
||||||
|
| Оверлей | Статус |
|
||||||
|
|---|---|
|
||||||
|
| `uralkal` | манифесты готовы, **в `clusters/uralkal` не подключён** |
|
||||||
|
|
||||||
|
## Журнал аудита kube-apiserver
|
||||||
|
|
||||||
|
DaemonSet `audit-collector-agent` на узлах `node-role.kubernetes.io/control-plane`. Он читает
|
||||||
|
`/var/log/audit/kube-apiserver-audit*.log` (текущий и ротированные файлы), позиции хранит в `/var/lib/otelcol-audit`
|
||||||
|
(hostPath), туда же складывает дисковую очередь отправки.
|
||||||
|
|
||||||
|
Обработка (`transform/audit`):
|
||||||
|
|
||||||
|
- **вырезание шума:** `metadata.managedFields` и аннотация `kubectl.kubernetes.io/last-applied-configuration`
|
||||||
|
в `requestObject` / `responseObject`;
|
||||||
|
- **маскирование:** значения параметров `password|passwd|pwd|token|secret|api_key|api-key` в `requestURI`
|
||||||
|
(`token=abc` → `token=***`), сама команда `exec` не маскируется;
|
||||||
|
- **классификация** — поля ниже. Правила применяются по порядку, последнее подходящее побеждает.
|
||||||
|
|
||||||
|
| Поле | Значение | Условие |
|
||||||
|
|---|---|---|
|
||||||
|
| `sarex_contour` | имя контура | env `CONTOUR` из оверлея |
|
||||||
|
| `sarex_dry_run` | `true` / `false` | `requestURI` содержит `dryRun=All` |
|
||||||
|
| `sarex_action` | `read` | `verb` ∈ `get`, `list` |
|
||||||
|
| | `mutate` | `verb` ∈ `create`, `update`, `patch`, `delete`, `deletecollection` |
|
||||||
|
| | `status` | `objectRef.subresource == status` |
|
||||||
|
| | `interactive` | `objectRef.subresource` ∈ `exec`, `attach`, `portforward` |
|
||||||
|
| `sarex_outcome` | `applied` | код 2xx, не dry-run |
|
||||||
|
| | `dry_run` | код 2xx, dry-run |
|
||||||
|
| | `denied_authn` | 401 |
|
||||||
|
| | `denied_authz` | 403 и `authorization.k8s.io/decision=forbid` |
|
||||||
|
| | `denied_admission` | 4xx при `decision=allow`, кроме 404/409/422 |
|
||||||
|
| | `invalid` | 422 |
|
||||||
|
| | `conflict` | 409 |
|
||||||
|
| | `not_found` | 404 |
|
||||||
|
| | `error` | 5xx |
|
||||||
|
| `sarex_actor_type` | `human` | по умолчанию |
|
||||||
|
| | `serviceaccount` | `system:serviceaccount:*` |
|
||||||
|
| | `controller` | `system:serviceaccount:kube-system:*`, `system:kube-*`, `system:apiserver` |
|
||||||
|
| | `node` | `system:node:*` |
|
||||||
|
| | `gitops` | `system:serviceaccount:flux-system:*` |
|
||||||
|
|
||||||
|
`watch` не попадает ни в одну категорию `sarex_action`: поле не выставляется.
|
||||||
|
|
||||||
|
Надёжность доставки: `required_acks: -1`, дисковая очередь (`sending_queue.storage`), бесконечные ретраи. При
|
||||||
|
заполненной очереди срабатывает backpressure на `filelog`, чтение файла приостанавливается. Данные теряются,
|
||||||
|
только если файл успеет уйти из ротации (10 × 100 МБ) раньше, чем Kafka вернётся.
|
||||||
|
|
||||||
|
## Предусловия подключения в контуре
|
||||||
|
|
||||||
|
1. **Аудит apiserver включён** (INFRA-3057): флаги `--audit-policy-file`, `--audit-log-path=/var/log/audit/kube-apiserver-audit.log`,
|
||||||
|
`--audit-log-maxsize=100`, `--audit-log-maxbackup=10`. Каталог `/var/log/audit` есть на всех мастерах.
|
||||||
|
2. **Kafka:** топик и SCRAM-пользователь.
|
||||||
|
- Топик `security.k8s-audit`: 1 партиция, `retention.ms=259200000` (3 суток), `max.message.bytes=5242880`,
|
||||||
|
RF по контуру (в `uralkal` один controller → RF 1).
|
||||||
|
- Пользователь `security-audit` (SCRAM-SHA-512). ACL не нужны.
|
||||||
|
|
||||||
|
Заводится terraform'ом: `infra/terraform`, ветка `contour`, модуль `kafka-topics-incluster`, блок
|
||||||
|
`environments.<контур>` в `infrastructure.yaml`. Если нужно вручную — из пода контроллера, через внутренний
|
||||||
|
листенер (`INTERNAL`, 9094, `SASL_PLAINTEXT`/`PLAIN`, `inter_broker_user`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n kafka exec -it kafka-kafka-contour-controller-0 -c kafka -- bash
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > /tmp/admin.properties <<EOF
|
||||||
|
security.protocol=SASL_PLAINTEXT
|
||||||
|
sasl.mechanism=PLAIN
|
||||||
|
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="inter_broker_user" password="$(cat /vault/secrets/kafka-inter-broker-password)";
|
||||||
|
EOF
|
||||||
|
B=localhost:9094
|
||||||
|
|
||||||
|
kafka-topics.sh --bootstrap-server $B --command-config /tmp/admin.properties --create \
|
||||||
|
--topic security.k8s-audit --partitions 1 --replication-factor 1 \
|
||||||
|
--config retention.ms=259200000 --config max.message.bytes=5242880
|
||||||
|
|
||||||
|
kafka-configs.sh --bootstrap-server $B --command-config /tmp/admin.properties --alter \
|
||||||
|
--entity-type users --entity-name security-audit \
|
||||||
|
--add-config 'SCRAM-SHA-512=[iterations=4096,password=<пароль>]'
|
||||||
|
|
||||||
|
rm /tmp/admin.properties
|
||||||
|
```
|
||||||
|
3. **Vault** (`secrets/` — KV v2):
|
||||||
|
- секрет `secrets/data/security-audit/kafka` с ключами `username`, `password`, `ca_crt`;
|
||||||
|
`ca_crt` — содержимое `ca.crt` из Secret `kafka/kafka-kafka-contour-tls` (CA генерирует чарт `kafka-contour`,
|
||||||
|
срок 365 дней; при перевыпуске обновить и здесь);
|
||||||
|
- политика `read` на `secrets/data/security-audit/kafka`;
|
||||||
|
- роль `security-audit` в `auth/kubernetes`: bound SA `audit-collector` **и** `access-collector`, namespace
|
||||||
|
`security-audit`, политика выше.
|
||||||
|
|
||||||
|
Заводится в `infra/terraform`, ветка `contour`, модуль `vault-platform-secrets` (роли `vault_kubernetes_auth_backend_role`,
|
||||||
|
KV-секреты).
|
||||||
|
4. **Образ** `cr.yandex/crp3ccidau046kdj8g9q/opentelemetry-collector/opentelemetry-collector-contrib:0.121.0-amd64`
|
||||||
|
доступен в registry контура (тот же, что дефолт чарта `opentelemetry-collector-prod` 0.117.1), `regcred` есть в
|
||||||
|
namespace `security-audit`.
|
||||||
|
|
||||||
|
## Подключение
|
||||||
|
|
||||||
|
Отдельным MR после выполнения предусловий — добавить в `clusters/uralkal/kustomization.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- ../../infrastructure/security-audit/uralkal
|
||||||
|
```
|
||||||
|
|
||||||
|
`dependsOn` в оверлее ждёт HelmRelease `kafka/kafka` и `vault/vault`.
|
||||||
|
|
||||||
|
Порядок: сначала должен раскататься провайдер `audit-access` в istiod (`infrastructure/istio-pilot/uralkal/istio-pilot.yaml`,
|
||||||
|
применяется сразу, как только попадёт в master: кластер его уже подключает). Telemetry из `mesh/` ссылается на этот провайдер по имени. Проверка:
|
||||||
|
`kubectl -n istio-system get cm istio -o jsonpath='{.data.mesh}' | grep -A2 audit-access`.
|
||||||
|
|
||||||
|
## Проверка после подключения
|
||||||
|
|
||||||
|
- DaemonSet `security-audit/audit-collector-agent` только на мастерах, все поды `Ready`:
|
||||||
|
`kubectl -n security-audit get pods -o wide`.
|
||||||
|
- В логах коллектора нет ошибок: `kubectl -n security-audit logs ds/audit-collector-agent -c opentelemetry-collector-prod`.
|
||||||
|
- События в топике (из пода контроллера Kafka, с `admin.properties` выше):
|
||||||
|
`kafka-console-consumer.sh --bootstrap-server localhost:9094 --consumer.config /tmp/admin.properties --topic security.k8s-audit --max-messages 5`.
|
||||||
|
- `kubectl create secret generic audit-probe --from-literal=k=v` → событие с `objectRef.resource=secrets` уровня
|
||||||
|
`Metadata`, без `requestObject`/`responseObject`.
|
||||||
|
- Рестарт пода коллектора (`kubectl delete pod`) → нет дублей и пропусков: сверить `auditID` в топике с файлом журнала
|
||||||
|
на мастере за интервал рестарта.
|
||||||
|
|
||||||
|
## Известное ограничение
|
||||||
|
|
||||||
|
Значения env с паролями внутри `requestObject` / `responseObject` workload-ресурсов на уровне `RequestResponse`
|
||||||
|
коллектор не маскирует: OTTL плохо работает с массивами (`spec.containers[].env[]`). Маскирование будет в processor'е.
|
||||||
|
До его готовности не повышать уровень для workload-ресурсов выше `Request` в контурах, где пароли могут быть в env.
|
||||||
|
|
||||||
|
## Журнал доступа шлюза
|
||||||
|
|
||||||
|
Каждый внешний HTTP-запрос через ingress-шлюз (`istio-system`, `istio: ingressgateway`): кто, что, когда, результат,
|
||||||
|
IP, `x-request-id`, `trace_id`. Поток ИБ отдельно от прикладных журналов (п.20); IP источника для событий приложений
|
||||||
|
находится по `request_id` (п.15).
|
||||||
|
|
||||||
|
```
|
||||||
|
ingress-шлюз (Envoy)
|
||||||
|
└─ Telemetry gateway-audit-access ─► провайдер audit-access (envoyOtelAls, meshConfig istiod)
|
||||||
|
└─ OTLP gRPC, mTLS ─► access-collector.security-audit:4317 (2 реплики, sidecar Istio)
|
||||||
|
└─ transform/access ─► Kafka security.mesh-access ─► processor (infra/k8s-audit, отдельная задача)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Поля записи
|
||||||
|
|
||||||
|
| Поле | Источник Envoy | Примечание |
|
||||||
|
|---|---|---|
|
||||||
|
| `sarex_kind` | — | всегда `mesh_access` |
|
||||||
|
| `start_time` | `%START_TIME%` | |
|
||||||
|
| `request_id` | `%REQ(X-REQUEST-ID)%` | ключ связи с журналами приложений (п.15) |
|
||||||
|
| `trace_id` | `%TRACE_ID%` | пусто, пока в mesh выключен трейсинг |
|
||||||
|
| `authority`, `method`, `protocol` | `:authority`, `:method`, `%PROTOCOL%` | |
|
||||||
|
| `path` | `%REQ_WITHOUT_QUERY(:PATH)%` | без query; маскирование — ниже |
|
||||||
|
| `response_code`, `response_flags` | `%RESPONSE_CODE%`, `%RESPONSE_FLAGS%` | `0` — ответа не было |
|
||||||
|
| `duration_ms`, `bytes_in`, `bytes_out` | `%DURATION%`, `%BYTES_RECEIVED%`, `%BYTES_SENT%` | |
|
||||||
|
| `x_forwarded_for`, `downstream_ip` | `X-Forwarded-For`, `%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%` | за NodePort — см. открытые вопросы |
|
||||||
|
| `user_sub`, `user_org` | `x-sarex-audit-sub`, `x-sarex-audit-org` | заполняет RequestAuthentication из JWT; **пока её нет — пусто** |
|
||||||
|
| `user_agent`, `route`, `upstream_cluster` | `User-Agent`, `%ROUTE_NAME%`, `%UPSTREAM_CLUSTER%` | |
|
||||||
|
| `sarex_contour` | env `CONTOUR` | |
|
||||||
|
| `sarex_action` | по `method` | `read`: GET/HEAD/OPTIONS; `mutate`: POST/PUT/PATCH/DELETE |
|
||||||
|
| `sarex_outcome` | по `response_code` | `applied` 2xx–3xx, `denied_authn` 401, `denied_authz` 403, `invalid` прочие 4xx (включая 404), `error` 5xx и `0` |
|
||||||
|
| `sarex_actor_type` | по `user_sub` | `human`, если есть, иначе `anonymous` |
|
||||||
|
|
||||||
|
**Не логируется (п.23):**
|
||||||
|
- `Authorization`, `Cookie`, `Set-Cookie` — это сами учётные данные: журнал с ними позволяет войти от имени пользователя;
|
||||||
|
- query-строка — в ней бывают токены публичных ссылок, `token=`, подписи S3 (`X-Amz-Signature`).
|
||||||
|
|
||||||
|
Маскирование в пути:
|
||||||
|
- сегмент после `token|secret|key|signature/` → `***`;
|
||||||
|
- JWT-подобные строки → `***jwt***`.
|
||||||
|
|
||||||
|
Защита от подмены: EnvoyFilter `strip-audit-headers` первым HTTP-фильтром удаляет входящие `x-sarex-audit-sub/org`.
|
||||||
|
Без него клиент мог бы записать в журнал любого пользователя.
|
||||||
|
|
||||||
|
### Пользователь в журнале: RequestAuthentication — не создана (блокер)
|
||||||
|
|
||||||
|
`user_sub`/`user_org` должна заполнять `RequestAuthentication` на шлюзе (`outputClaimToHeaders`). Но она отклоняет
|
||||||
|
**любой** запрос с JWT издателя, которого нет в `jwtRules` (`Jwt issuer is not configured`, 401), на всех хостах шлюза.
|
||||||
|
А издатели в Уралкалии подтверждаются по репозиторию не все:
|
||||||
|
|
||||||
|
| Издатель | Заголовок | `issuer` / JWKS | Статус |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Zitadel | `Authorization: Bearer`, `Identity` | `https://login.sarex.local.uralkali.com` (`ExternalDomain` + `ExternalSecure: true` в `infrastructure/zitadel/uralkal`; `ExternalPort` не задан — проверить, что в `/.well-known/openid-configuration` нет `:8080`) | issuer — `TODO(verify)`; префикс заголовка `Identity` не подтверждён |
|
||||||
|
| django (SimpleJWT, RS512) | `Authorization: Bearer` (режим `sarex`) | в коде `SIMPLE_JWT_ISSUER` по умолчанию `default_issuer` (`apps/django/base/django-configmap.yaml`), в `CONFIGURATION.md` — `django`; в `uralkal` не задан. Ключ — `secrets/data/vault/common/rsa_keys`, в репозитории нет | **не подтверждён** |
|
||||||
|
| Camunda Keycloak | `Authorization: Bearer` на `camunda-*` | `https://camunda-keycloak.sarex.local.uralkali.com/auth/realms/camunda-platform` (`infrastructure/camunda/uralkal`), JWKS `…/protocol/openid-connect/certs` | подтверждён |
|
||||||
|
| document-link | `Authorization: Bearer` (зашитый/сервисный JWT) | неизвестен | **не подтверждён** |
|
||||||
|
|
||||||
|
Пока таблица не закрыта, журнал пишется без пользователя (`sarex_actor_type=anonymous`). Пользователя для события
|
||||||
|
можно найти по `request_id` в журналах приложений.
|
||||||
|
|
||||||
|
JWKS Zitadel — выбранный вариант, когда дойдёт до включения:
|
||||||
|
- **inline `jwks` — нет:** Zitadel ротирует ключи подписи по умолчанию раз в часы (`KeyConfig.PrivateKeyLifetime` 6h),
|
||||||
|
inline-набор устареет за день, и шлюз начнёт отдавать 401 всем.
|
||||||
|
- **`jwksUri` на внешний адрес, загрузка istiod — только если подтверждено:** istiod резолвит
|
||||||
|
`login.sarex.local.uralkali.com` и доверяет CA сертификата `uralkal-sarex-tls`. В istiod нет CA заказчика, и
|
||||||
|
отключать проверку (`JWKS_RESOLVER_INSECURE_SKIP_VERIFY`) нельзя.
|
||||||
|
- **Рекомендуется:** `PILOT_JWT_ENABLE_REMOTE_JWKS=envoy` (env istiod) и `jwksUri: http://login.sarex.local.uralkali.com:8080/oauth/v2/keys`
|
||||||
|
плюс `ServiceEntry` хоста `login.sarex.local.uralkali.com:8080` (HTTP, `resolution: DNS`, endpoint
|
||||||
|
`zitadel-idp-contour.zitadel.svc.cluster.local`). Ключи забирает Envoy шлюза через кластер mesh, без внешнего DNS и CA.
|
||||||
|
`Host` остаётся внешним доменом инстанса, его Zitadel узнаёт. Прямой `http://zitadel-idp-contour.zitadel.svc:8080/...`
|
||||||
|
Zitadel отклонит: инстанс определяется по `Host`.
|
||||||
|
|
||||||
|
Черновик (не подключён; включать после закрытия таблицы и согласования 401 с владельцами приложений):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: security.istio.io/v1
|
||||||
|
kind: RequestAuthentication
|
||||||
|
metadata: {name: audit-jwt, namespace: istio-system}
|
||||||
|
spec:
|
||||||
|
selector: {matchLabels: {istio: ingressgateway}}
|
||||||
|
jwtRules:
|
||||||
|
- issuer: https://login.sarex.local.uralkali.com # TODO(verify)
|
||||||
|
jwksUri: http://login.sarex.local.uralkali.com:8080/oauth/v2/keys
|
||||||
|
forwardOriginalToken: true
|
||||||
|
fromHeaders:
|
||||||
|
- {name: Authorization, prefix: "Bearer "}
|
||||||
|
- {name: Identity, prefix: "Bearer "} # TODO(verify): префикс
|
||||||
|
outputClaimToHeaders:
|
||||||
|
- {header: x-sarex-audit-sub, claim: sub}
|
||||||
|
- {header: x-sarex-audit-org, claim: "urn:zitadel:iam:user:resourceowner:id"} # TODO(verify): есть ли claim в access token
|
||||||
|
- issuer: https://camunda-keycloak.sarex.local.uralkali.com/auth/realms/camunda-platform
|
||||||
|
jwksUri: http://camunda-keycloak.camunda.svc.cluster.local/auth/realms/camunda-platform/protocol/openid-connect/certs # TODO(verify)
|
||||||
|
forwardOriginalToken: true
|
||||||
|
# - issuer: <django SIMPLE_JWT_ISSUER> # блокер: issuer и публичный ключ
|
||||||
|
```
|
||||||
|
|
||||||
|
### Предусловия подключения
|
||||||
|
|
||||||
|
- Топик `security.mesh-access`: 3 партиции, `retention.ms=259200000`, `max.message.bytes=1048576`, RF по контуру —
|
||||||
|
тем же путём, что `security.k8s-audit` (terraform или команды выше). Пользователь — тот же `security-audit`, ACL не нужны.
|
||||||
|
- Роль Vault `security-audit`: в bound SA добавлен `access-collector` (см. предусловия выше).
|
||||||
|
- Провайдер `audit-access` раскатан в istiod (см. «Подключение»).
|
||||||
|
|
||||||
|
### Sidecar и Vault
|
||||||
|
|
||||||
|
- Namespace `security-audit` без `istio-injection`: sidecar получают только поды с меткой `sidecar.istio.io/inject=true`
|
||||||
|
(webhook `object.sidecar-injector.istio.io`). Это `access-collector`. У `audit-collector` метка `"false"`.
|
||||||
|
Метка `istio-injection: disabled` отключила бы инъекцию совсем, даже по метке пода.
|
||||||
|
- `PeerAuthentication` в репозитории нет, поэтому режим PERMISSIVE. Шлюз шлёт в коллектор по auto-mTLS (оба в mesh),
|
||||||
|
коллектор в Kafka — тоже через mTLS (`kafka` в mesh), внутри остаётся `SASL_SSL`.
|
||||||
|
- Vault Agent (`agent-init-first`, pre-populate) + istio-proxy: по паттерну контура (`apps/bi`, `apps/bim`, `apps/cde`…)
|
||||||
|
`traffic.sidecar.istio.io/excludeOutboundPorts: "8200"`. Init-агент ходит в Vault мимо iptables-перехвата,
|
||||||
|
даже если `istio-init` отработал раньше.
|
||||||
|
- `holdApplicationUntilProxyStarts`: kafka exporter подключается к брокерам на старте и без готового proxy падает.
|
||||||
|
- `istio-init` требует `NET_ADMIN`/`NET_RAW`: enforce `privileged` это разрешает, профиль `warn: baseline` даст
|
||||||
|
предупреждения при создании подов — ожидаемо.
|
||||||
|
|
||||||
|
### Проверка после подключения
|
||||||
|
|
||||||
|
- Провайдер и порядок фильтров на шлюзе:
|
||||||
|
```bash
|
||||||
|
POD=$(kubectl -n istio-system get pod -l istio=ingressgateway -o jsonpath='{.items[0].metadata.name}')
|
||||||
|
istioctl -n istio-system proxy-config cluster $POD | grep access-collector
|
||||||
|
istioctl -n istio-system proxy-config listener $POD -o json \
|
||||||
|
| jq -r '.[].filterChains[].filters[] | select(.name=="envoy.filters.network.http_connection_manager")
|
||||||
|
| .typedConfig.httpFilters[].name' | uniq
|
||||||
|
```
|
||||||
|
Первым в списке — `security-audit.strip-audit-headers`, раньше `envoy.filters.http.jwt_authn` (когда появится).
|
||||||
|
В выводе `listener` есть `envoy.access_loggers.open_telemetry`.
|
||||||
|
- `curl -H 'x-sarex-audit-sub: spoof' https://sarex.local.uralkali.com/...` → в Kafka `user_sub` пустой.
|
||||||
|
- Запрос с `?token=canary` → строки `canary` нет в топике.
|
||||||
|
- После включения RequestAuthentication:
|
||||||
|
- запрос с валидным токеном → `user_sub` заполнен;
|
||||||
|
- с невалидным токеном Zitadel → 401 (так работает RequestAuthentication). **Согласовать с владельцами приложений до включения.**
|
||||||
|
- `kubectl -n security-audit scale deploy/access-collector --replicas=0` → трафик шлюза не падает (ALS асинхронный).
|
||||||
|
Потери видны в статистике Envoy: `kubectl -n istio-system exec $POD -- pilot-agent request GET stats | grep access_logs`.
|
||||||
|
Вернуть 2 реплики.
|
||||||
|
- Чтение топика: как для `security.k8s-audit`, `--topic security.mesh-access`.
|
||||||
|
|
||||||
|
### Мониторинг
|
||||||
|
|
||||||
|
- `otelcol_receiver_accepted_log_records{receiver="otlp"}` у `access-collector`: алерт, если 0 за 10 минут при живом
|
||||||
|
трафике шлюза (`istio_requests_total` на `ingressgateway`).
|
||||||
|
- `otelcol_exporter_sent_log_records` / `otelcol_exporter_send_failed_log_records` (`exporter="kafka/access"`),
|
||||||
|
`otelcol_exporter_queue_size` против `otelcol_exporter_queue_capacity`, `otelcol_exporter_enqueue_failed_log_records`.
|
||||||
|
- Метрики коллектора отдаются на `:8888` пода. Prometheus в Уралкалии по репозиторию не найден — сбор метрик отдельно.
|
||||||
|
|
||||||
|
### Открытые вопросы
|
||||||
|
|
||||||
|
- **IP клиента (п.15).** Шлюз — одна реплика на control-plane за Service `NodePort` (30080/30443) без
|
||||||
|
`externalTrafficPolicy`, т. е. `Cluster`. kube-proxy делает SNAT, и в `downstream_ip` попадает IP узла, а не клиента.
|
||||||
|
Нужно выяснить, есть ли перед NodePort балансировщик заказчика и передаёт ли он `X-Forwarded-For`. Если да —
|
||||||
|
`meshConfig.defaultConfig.gatewayTopology.numTrustedProxies` (или аннотация `proxy.istio.io/config` на шлюзе) по числу
|
||||||
|
прокси, иначе Envoy не доверяет XFF. Если балансировщика нет — `externalTrafficPolicy: Local` и DNS/LB только на
|
||||||
|
узел шлюза. Ничего из этого не меняли.
|
||||||
|
- **Статика** (`*/static/*`, фронтенды) пишется в журнал. Исключать ли — решение ИБ (фильтр в `mesh/telemetry.yaml`).
|
||||||
|
- **Запросы между сервисами** (sidecar'ы приложений) — отдельный Telemetry с фильтром, не в этой задаче.
|
||||||
|
- **Очередь в памяти:** `queue_size: 50000` считается в запросах (батчах до 2000 записей), а не в записях. При долгой
|
||||||
|
недоступности Kafka упрётся в `memory_limiter`/лимит 512Mi раньше, чем в `queue_size`. Записи сверх этого теряются,
|
||||||
|
шлюз не блокируется.
|
||||||
|
|
||||||
|
## События Zitadel
|
||||||
|
|
||||||
|
Вход, неуспешный вход, блокировки, изменения пользователей, ролей и members (п.14–16 документа ИБ).
|
||||||
|
|
||||||
|
```
|
||||||
|
Zitadel (Actions v2: Execution с условием event → Target restAsync, JSON по HTTP)
|
||||||
|
└─ http://access-collector.security-audit.svc.cluster.local:8088/zitadel/events (mTLS, sidecar у обоих)
|
||||||
|
└─ webhookevent/zitadel ─► transform/idp ─► Kafka security.idp-events ─► processor (infra/k8s-audit, idp/)
|
||||||
|
```
|
||||||
|
|
||||||
|
Target и Execution'ы заводит Job `idp-actions-bootstrap` (`base/idp-actions-bootstrap/`) через API Zitadel.
|
||||||
|
Список событий — `files/events.txt`.
|
||||||
|
|
||||||
|
### Почему без HMAC
|
||||||
|
|
||||||
|
Zitadel подписывает запросы (`X-ZITADEL-Signature`, HMAC-SHA256 тела), но `webhookevent` подпись проверять не умеет.
|
||||||
|
Вместо подписи источник подтверждается на транспортном уровне:
|
||||||
|
- **mTLS Istio:** у Zitadel и коллектора есть sidecar, identity источника — SA `zitadel`;
|
||||||
|
- **AuthorizationPolicy `access-collector`:** на 8088 только `POST /zitadel/events` от `cluster.local/ns/zitadel/sa/zitadel`,
|
||||||
|
на 4317 только из `istio-system`, остальное запрещено (ALLOW-политика закрывает всё, что не разрешено);
|
||||||
|
- **NetworkPolicy:** 8088 только из namespace `zitadel` от подов `app.kubernetes.io/name=idp-contour,component=start`.
|
||||||
|
|
||||||
|
**Условие:** Zitadel в mesh. Сейчас это так: ns `zitadel` с `istio-injection: enabled`, у Deployment метка
|
||||||
|
`sidecar.istio.io/inject: "true"` (чарт `idp-contour` 4.12.13). Если Zitadel выведут из mesh, у его запросов не будет
|
||||||
|
principal, и коллектор начнёт отвечать 403. Тогда либо вернуть sidecar, либо перейти на приёмник с проверкой HMAC
|
||||||
|
(signingKey из `CreateTarget` — во Vault).
|
||||||
|
|
||||||
|
Probe'ы kubelet AuthorizationPolicy не блокирует: при `rewriteAppHTTPProbers` (по умолчанию) sidecar переписывает
|
||||||
|
HTTP-probe на pilot-agent `:15020`, а тот ходит в приложение напрямую, мимо Envoy и RBAC-фильтра. При подключении сбора
|
||||||
|
метрик коллектора (`:8888`) нужно отдельное правило в политике.
|
||||||
|
|
||||||
|
### Поля события
|
||||||
|
|
||||||
|
Тело запроса Zitadel — `ContextInfoEvent` (`internal/repository/execution/queue.go`, v4.12.2), все поля `omitempty`.
|
||||||
|
|
||||||
|
| Поле | Откуда | Примечание |
|
||||||
|
|---|---|---|
|
||||||
|
| `event_type` | Zitadel | например, `user.human.password.check.failed` |
|
||||||
|
| `created_at` | Zitadel | RFC3339Nano |
|
||||||
|
| `aggregateID`, `aggregateType` | Zitadel | объект события (пользователь, грант, организация…) |
|
||||||
|
| `resourceOwner`, `instanceID` | Zitadel | организация и инстанс |
|
||||||
|
| `userID` | Zitadel | кто выполнил действие; нет — системное событие |
|
||||||
|
| `sequence`, `version` | Zitadel | |
|
||||||
|
| `details.user_name` | `event_payload.userName` | только `user.human.added`, `user.human.selfregistered` |
|
||||||
|
| `details.role_keys`, `details.project_id` | `event_payload.roleKeys`, `.projectId` | только `user.grant.*` |
|
||||||
|
| `sarex_kind` | — | `idp_event` |
|
||||||
|
| `sarex_contour` | env `CONTOUR` | |
|
||||||
|
| `sarex_outcome` | по `event_type` | `denied_authn` для `*.check.failed`, иначе `applied` |
|
||||||
|
| `sarex_actor_type` | по `userID` | `human`, без `userID` — `system` |
|
||||||
|
|
||||||
|
**`event_payload` удаляется целиком (п.23).** В payload бывают хеш пароля (`user.human.password.changed`),
|
||||||
|
секреты OTP, client secret, ПДн. Allowlist безопаснее denylist'а: новое поле в новой версии Zitadel не уйдёт в Kafka само.
|
||||||
|
Нужные ИБ поля копируются в `details` явно.
|
||||||
|
- `TODO(verify)`: allowlist стартовый. Уточнить по реальным событиям на стенде, какие ещё поля нужны processor'у.
|
||||||
|
|
||||||
|
Тело, которое не разобралось как JSON, заменяется маркером `{"sarex_kind": "idp_event", "sarex_error": "unparsed"}`:
|
||||||
|
сырой текст мог бы унести payload. В 4.12.2 `event_payload` — объект (`json.RawMessage`); вариант с base64-строкой тоже
|
||||||
|
поддержан.
|
||||||
|
|
||||||
|
### Job `idp-actions-bootstrap`
|
||||||
|
|
||||||
|
- **Образ.** В registry контура нет образа с `curl` и `jq` вместе. init-контейнер из `contour/zitadel/kubectl:1.33.4`
|
||||||
|
(уже нужен setup-Job'у Zitadel) копирует `jq` 1.6 с библиотеками в emptyDir. Скрипт идёт в
|
||||||
|
`contour/kafka-nn/kafka:4.0.0-debian-12-r10` (`curl` 7.88). Оба на Debian 12. `TODO(verify)`: заменить на один
|
||||||
|
toolbox-образ.
|
||||||
|
- **Инстанс Zitadel** выбирается заголовком `x-zitadel-instance-host` (+ `x-zitadel-public-host`, `X-Forwarded-Proto: https`).
|
||||||
|
Это `InstanceHostHeaders`/`PublicHostHeaders` из `defaults.yaml` v4.12.2. `Host` остаётся именем Service: при подмене
|
||||||
|
`Host` на внешний домен istio-proxy не нашёл бы маршрут и отправил бы запрос в passthrough, без mTLS.
|
||||||
|
- **Mesh.** Job в mesh (`sidecar.istio.io/inject=true`, `holdApplicationUntilProxyStarts`). В конце скрипт вызывает
|
||||||
|
`POST 127.0.0.1:15020/quitquitquit`, istio-proxy завершается, под — `Completed`. STRICT mTLS у Zitadel сейчас нет, но
|
||||||
|
Job к нему готов.
|
||||||
|
- **Vault:** роль `security-audit-bootstrap`, `secrets/data/security-audit/zitadel-bootstrap` → `pat` →
|
||||||
|
`/vault/secrets/zitadel-pat`; `excludeOutboundPorts: 8200` — паттерн контура.
|
||||||
|
- **Скрипт** (`files/idp-actions-bootstrap.sh`, POSIX sh) идемпотентен:
|
||||||
|
1. ждёт `/debug/ready` (10 попыток × 15 с);
|
||||||
|
2. печатает все типы событий инстанса (`known: …`, `POST /admin/v1/events/types/_search`);
|
||||||
|
3. ищет Target `security-audit-idp-events` по имени и создаёт или обновляет его (`restAsync`, endpoint, `timeout: 10s`);
|
||||||
|
4. для каждого события — `PUT /v2/actions/executions` с условием `{"event": {"event": "<тип>"}}`. Чужие targets
|
||||||
|
Execution'а сохраняются, свой добавляется в конец;
|
||||||
|
5. неизвестное имя события — ошибка: остальные события обрабатываются, код выхода ≠ 0.
|
||||||
|
|
||||||
|
PAT не печатается (`set -x` нет, заголовки — в файле в tmpfs), signingKey из ответов не выводится.
|
||||||
|
- **Перезапуск.** ConfigMap'ы скрипта и событий — из `configMapGenerator` (хеш в имени). Правка любого файла меняет
|
||||||
|
spec Job'а, и Flux пересоздаёт его (`kustomize.toolkit.fluxcd.io/force: enabled`). После `ttlSecondsAfterFinished` (сутки)
|
||||||
|
Job удаляется, и Flux создаёт его заново: раз в сутки идёт сверка, повторный запуск ничего не меняет.
|
||||||
|
- **Порядок.** У Job нет `dependsOn`: он стартует вместе с HelmRelease'ами. Если Zitadel ещё не готов — до 10 попыток,
|
||||||
|
дальше `backoffLimit: 3`. Execution'ы ссылаются на Target по id, коллектор может подняться позже: до его готовности
|
||||||
|
события теряются (повторов нет, см. риски). Подключать модуль, когда Zitadel уже работает.
|
||||||
|
|
||||||
|
### Предусловия подключения
|
||||||
|
|
||||||
|
- Топик `security.idp-events`: 3 партиции, `retention.ms=259200000`, RF по контуру, тем же путём, что остальные
|
||||||
|
топики. Пользователь `security-audit`, ACL не нужны.
|
||||||
|
- Сервисный пользователь Zitadel `security-audit-bootstrap` (machine user) с ролью `IAM_OWNER` на инстансе.
|
||||||
|
Нужны `action.target.read/write/delete`, `action.execution.read/write`, `events.read`. Его PAT — во Vault
|
||||||
|
`secrets/data/security-audit/zitadel-bootstrap`, ключ `pat`.
|
||||||
|
- Роль Vault `security-audit-bootstrap` в `auth/kubernetes`: bound SA `idp-actions-bootstrap`, namespace `security-audit`,
|
||||||
|
политика `read` на `secrets/data/security-audit/zitadel-bootstrap`.
|
||||||
|
- Feature flag не нужен: в v4.12.2 `actions` в `/v2/features/instance` удалён (reserved), Actions v2 включены всегда.
|
||||||
|
|
||||||
|
Проверка API изнутри кластера (ожидается `"https://login.sarex.local.uralkali.com"`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n zitadel exec deploy/zitadel-idp-contour -c istio-proxy -- \
|
||||||
|
curl -s -H 'x-zitadel-instance-host: login.sarex.local.uralkali.com' -H 'X-Forwarded-Proto: https' \
|
||||||
|
http://zitadel-idp-contour.zitadel.svc.cluster.local:8080/.well-known/openid-configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка после подключения
|
||||||
|
|
||||||
|
- `kubectl -n security-audit logs job/idp-actions-bootstrap -c bootstrap`: `Target …: создан`, по событиям
|
||||||
|
`создано`/`без изменений`, `ошибок=0`. Строки `known:` — сверка имён с инстансом.
|
||||||
|
- Создать тестового пользователя → в `security.idp-events` есть `user.human.added` без `event_payload` и с
|
||||||
|
`details.user_name`.
|
||||||
|
- Неверный пароль → `user.human.password.check.failed`, `sarex_outcome=denied_authn`.
|
||||||
|
- Канарейка в пароле тестового пользователя → её нет в топике (`grep` по выводу `kafka-console-consumer.sh`).
|
||||||
|
- `curl -X POST http://access-collector.security-audit:8088/zitadel/events -d '{}'` из пода в другом namespace → таймаут
|
||||||
|
(NetworkPolicy). Из пода с sidecar в `zitadel`, но не от SA `zitadel` → `403 RBAC: access denied`.
|
||||||
|
|
||||||
|
### Риски
|
||||||
|
|
||||||
|
- **Повторов доставки нет.** Для `restAsync` Zitadel 4.12.2 только пишет ошибку в лог (`internal/execution`): коллектор
|
||||||
|
или Kafka недоступны → событие потеряно. Что смягчает:
|
||||||
|
- 2 реплики, PDB, очередь отправки;
|
||||||
|
- 200 приходит после приёма в `batch`, дальше доставка в Kafka с ретраями.
|
||||||
|
|
||||||
|
Алерты:
|
||||||
|
- `otelcol_exporter_send_failed_log_records{exporter="kafka/idp"}`;
|
||||||
|
- нет `otelcol_receiver_accepted_log_records{receiver="webhookevent/zitadel"}` в рабочее время.
|
||||||
|
- **Полный источник — Events API Zitadel** (`POST /admin/v1/events/_search`). Суточную сверку количества по типам можно
|
||||||
|
добавить позже.
|
||||||
|
- **Размер тела.** `webhookevent` 0.121 читает тело одним `bufio.Scanner` без явного буфера: событие больше ~64 КиБ,
|
||||||
|
вероятно, отбрасывается. Ответ при этом 200. `TODO(verify)` на стенде; события пользователей обычно в разы меньше.
|
||||||
|
- В 0.121 у `webhookevent` нет `header_attribute_regex`, есть только `required_header`: заголовки в запись не попадают.
|
||||||
|
|
||||||
|
### Откат
|
||||||
|
|
||||||
|
Снять Target со всех Execution'ов из списка и удалить его — скрипт с `--remove`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n security-audit get job idp-actions-bootstrap -o json \
|
||||||
|
| jq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .metadata.labels,
|
||||||
|
.metadata.annotations, .metadata.ownerReferences, .spec.selector, .status)
|
||||||
|
| .spec.template.metadata.labels |= with_entries(select(.key | test("controller-uid|job-name") | not))
|
||||||
|
| .metadata.name = "idp-actions-remove" | .spec.template.spec.containers[0].args = ["--remove"]' \
|
||||||
|
| kubectl create -f -
|
||||||
|
```
|
||||||
|
|
||||||
|
Пока модуль подключён, Flux вернёт Execution'ы при следующем пересоздании Job'а. Для постоянного отключения убрать
|
||||||
|
`idp-actions-bootstrap` из `base/kustomization.yaml` или модуль из кластера.
|
||||||
|
|
||||||
|
## Журнал аудита Vault
|
||||||
|
|
||||||
|
П.19 документа ИБ: аудит Vault включён, чувствительные значения хешируются HMAC-SHA256, журнал идёт в поток ИБ.
|
||||||
|
П.23: секретов в журнале нет.
|
||||||
|
|
||||||
|
```
|
||||||
|
Vault ──audit device "file"──► /vault/audit/audit.log (VCT "audit", свой PVC у каждой реплики)
|
||||||
|
│ sidecar audit-rotate (busybox): ротация по размеру + SIGHUP vault (shareProcessNamespace)
|
||||||
|
│ sidecar audit-shipper (otelcol-contrib): filelog, чекпоинты и очередь — /vault/audit/.otelcol
|
||||||
|
▼ OTLP gRPC, mTLS (Vault и коллектор в mesh)
|
||||||
|
access-collector: otlp/vault :4319 → logs/vault → Kafka security.vault-audit
|
||||||
|
```
|
||||||
|
|
||||||
|
Vault-часть — оверлей `infrastructure/vault/uralkal-audit/` (наследует `vault/uralkal`), **не подключён**.
|
||||||
|
|
||||||
|
### Почему файл и sidecar
|
||||||
|
|
||||||
|
Vault работает **fail-closed**: если включённый audit device не может записать событие, Vault перестаёт обслуживать
|
||||||
|
запросы. Поэтому в пути записи нет сети, только локальный файл. Kafka, коллектор и сеть могут лежать сколько угодно:
|
||||||
|
Vault пишет в файл, shipper догоняет потом. Остаётся один риск того же класса — **полный диск**: ротация и очередь
|
||||||
|
ограничены, на заполнение PVC нужен алерт.
|
||||||
|
|
||||||
|
Что хешируется: file device по умолчанию заменяет все строковые значения запросов и ответов (секреты, токены,
|
||||||
|
`client_token`, accessor'ы) на `hmac-sha256:…` с ключом, известным только Vault. Одинаковые значения дают одинаковый
|
||||||
|
хеш: по нему можно сверять события (`vault write sys/audit-hash/file input=…`), не раскрывая значения.
|
||||||
|
**`log_raw` не включается никогда**: с ним в журнал попадают секреты открытым текстом, а журнал уходит в Kafka и S3.
|
||||||
|
|
||||||
|
Кредов Kafka у sidecar'а нет и быть не может: Vault Agent в поде самого Vault невозможен, Vault стартует запечатанным.
|
||||||
|
Креды есть только у `access-collector`.
|
||||||
|
|
||||||
|
### Решения с последствиями
|
||||||
|
|
||||||
|
- **Том: VCT, а не отдельный PVC.** В Уралкалии 3 реплики HA/raft (дефолт чарта, `ha.enabled: true`, `replicas: 3`;
|
||||||
|
`vault/uralkal` это не меняет). Каждой реплике нужен свой том. Это `server.auditStorage` чарта →
|
||||||
|
`volumeClaimTemplates: audit` (5Gi `local-path`, `TODO(verify)` объём). `volumeClaimTemplates` — **неизменяемое поле**
|
||||||
|
StatefulSet: обычный `helm upgrade` упадёт, нужна процедура с `kubectl delete sts --cascade=orphan` (ниже).
|
||||||
|
- **Unseal вручную.** `vault-unseal` в Уралкалии не подключён (есть только в `vk-infra`, `yc-infra-prod`). Ключи — в Secret
|
||||||
|
`vault/vault-secret` (`docs/closed-contour-deployment.md`). Новый pod template перезапустит все 3 пода, каждый
|
||||||
|
стартует sealed. Readiness чарта — `vault status`: sealed-под не Ready, и `RollingUpdate` ждёт его unseal, прежде чем
|
||||||
|
перейти к следующему. Поды распечатываются по одному, кворум raft (2 из 3) сохраняется.
|
||||||
|
- **mTLS без PERMISSIVE.** У подов Vault есть istio-proxy: ns `vault` с `istio-injection: enabled`, у чарта нет opt-out.
|
||||||
|
`PeerAuthentication` в контуре нет, т. е. PERMISSIVE по умолчанию. shipper шлёт plaintext gRPC (`tls.insecure: true`) до
|
||||||
|
своего istio-proxy, дальше auto-mTLS. На коллекторе AuthorizationPolicy: 4319 только от
|
||||||
|
`cluster.local/ns/vault/sa/vault-vault-contour`, плюс NetworkPolicy только из ns `vault`, `app.kubernetes.io/name=vault-contour`,
|
||||||
|
`component=server`. `TODO(verify)`: `kubectl -n vault get pod vault-vault-contour-0 -o jsonpath='{.spec.containers[*].name}'`
|
||||||
|
содержит `istio-proxy`. Если Vault окажется вне mesh, principal не будет, коллектор ответит 403, shipper накопит
|
||||||
|
очередь, а потом перестанет читать файл. Vault продолжит работать, но после KEEP ротаций начнутся потери.
|
||||||
|
Тогда — вариант (б): TLS на `otlp/vault` (cert-manager), CA в sidecar. Вариант (а) (PERMISSIVE на 4319) ничего не даёт,
|
||||||
|
пока нет STRICT, а principal без mTLS всё равно пуст.
|
||||||
|
- **Общий PID namespace и UID vault у sidecar'ов.** SIGHUP можно отправить только процессу своего UID: CAP_KILL у
|
||||||
|
non-root контейнера не работает, ambient capabilities нет. Файл `0600` читается только UID 100. Последствие:
|
||||||
|
sidecar'ы видят процесс vault и могут читать `/proc/<pid>/environ`, а при разрешающем `kernel.yama.ptrace_scope` и
|
||||||
|
dumpable-процессе — и его память. Смягчение: образы только из registry контура (busybox закреплён digest'ом),
|
||||||
|
`readOnlyRootFilesystem`, `drop: [ALL]`, `no-new-privileges`, без shell-доступа в под. `TODO(verify)`: значение
|
||||||
|
`ptrace_scope` на узлах `dedicated: generic`.
|
||||||
|
- **Отклонения от исходного шаблона:**
|
||||||
|
- `rotate.sh` проверяет pid vault **до** `mv`. Без SIGHUP Vault продолжил бы писать в `audit.log.1`, новый файл
|
||||||
|
не появился бы, ротация встала бы, а файл рос бы до заполнения диска;
|
||||||
|
- `queue_size` shipper'а — 1000, а не 20000: 20000 батчей по 500 записей не помещаются в PVC. При полной очереди
|
||||||
|
filelog перестаёт читать, и буфером служат сами файлы (KEEP × MAX_BYTES);
|
||||||
|
- `compaction` у `file_storage`: иначе файл bbolt-очереди не уменьшается.
|
||||||
|
- **ConfigMap'ы без хеша в имени** (`audit-shipper-config`, `audit-rotate-script`): правка не перезапускает Vault сама
|
||||||
|
(рестарт = unseal). Применить — ручной рестарт подов по одному с unseal.
|
||||||
|
|
||||||
|
### Включение audit device
|
||||||
|
|
||||||
|
Через terraform контура (`infra/terraform`, ветка `contour`, рядом с `vault-platform-secrets`) — в этой задаче не сделано:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
resource "vault_audit" "file" {
|
||||||
|
type = "file"
|
||||||
|
path = "file"
|
||||||
|
options = {
|
||||||
|
file_path = "/vault/audit/audit.log"
|
||||||
|
mode = "0600"
|
||||||
|
format = "json"
|
||||||
|
elide_list_responses = "true" # Vault 1.21.2 (образ vault:1.21.2) — поддерживается
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Альтернатива — CLI на активном узле: `vault audit enable file file_path=/vault/audit/audit.log mode=0600 format=json elide_list_responses=true`.
|
||||||
|
|
||||||
|
Audit device общий для кластера raft: **все** реплики должны иметь `/vault/audit` до включения. Иначе узел без тома,
|
||||||
|
став активным, не сможет писать журнал и перестанет обслуживать запросы.
|
||||||
|
|
||||||
|
### Порядок подключения (строго)
|
||||||
|
|
||||||
|
1. Топик `security.vault-audit`: 3 партиции, `retention.ms=259200000`, RF по контуру. Пользователь `security-audit`,
|
||||||
|
ACL не нужны (как у остальных топиков модуля).
|
||||||
|
2. `security-audit` подключён и работает: `access-collector` с `logs/vault`, порт 4319 в Service.
|
||||||
|
3. Подготовка Vault:
|
||||||
|
- `vault operator raft list-peers`: 3 voter'а, лидер есть;
|
||||||
|
- `vault operator raft snapshot save /tmp/pre-audit.snap`, снапшот скопировать из пода;
|
||||||
|
- ключи unseal из `vault/vault-secret` под рукой.
|
||||||
|
4. Переключение оверлея. VCT неизменяем, поэтому HelmRelease приостанавливается до пересоздания StatefulSet:
|
||||||
|
```bash
|
||||||
|
flux suspend helmrelease vault -n vault
|
||||||
|
```
|
||||||
|
MR: в `clusters/uralkal/kustomization.yaml` `../../infrastructure/vault/uralkal` → `../../infrastructure/vault/uralkal-audit`,
|
||||||
|
дождаться применения Kustomization `flux-system`: ConfigMap'ы появились, HelmRelease обновлён, но приостановлен.
|
||||||
|
```bash
|
||||||
|
kubectl -n vault get cm audit-shipper-config audit-rotate-script
|
||||||
|
kubectl -n vault delete sts vault-vault-contour --cascade=orphan # поды и PVC data остаются, Vault работает
|
||||||
|
kubectl -n vault get pods -l component=server # все 3 Running, unsealed
|
||||||
|
flux resume helmrelease vault -n vault # helm создаёт STS с новым template и VCT audit
|
||||||
|
```
|
||||||
|
5. RollingUpdate пересоздаёт поды с конца (`-2`, `-1`, `-0`). Каждый новый под стартует sealed:
|
||||||
|
`kubectl -n vault exec vault-vault-contour-N -c vault -- vault operator unseal` (порог ключей), дождаться Ready,
|
||||||
|
и только потом следующий под.
|
||||||
|
6. После раскатки:
|
||||||
|
- 3 пода Ready, в каждом контейнеры `vault`, `audit-shipper`, `audit-rotate`, `istio-proxy`;
|
||||||
|
- PVC `audit-vault-vault-contour-{0,1,2}` в состоянии Bound;
|
||||||
|
- `vault operator raft list-peers` — 3 voter'а;
|
||||||
|
- на каждом поде `kubectl -n vault exec vault-vault-contour-N -c vault -- sh -c 'touch /vault/audit/.w && rm /vault/audit/.w'`.
|
||||||
|
7. Только теперь — `vault_audit` (terraform) или `vault audit enable`. Если включить раньше, enable упадёт: Vault
|
||||||
|
проверяет запись при включении.
|
||||||
|
8. Проверка (ниже).
|
||||||
|
|
||||||
|
### ⚠ Откат
|
||||||
|
|
||||||
|
**Сначала** `vault audit disable file` (или убрать `vault_audit` из terraform и применить), убедиться, что
|
||||||
|
`vault audit list` пуст. **Потом** вернуть `vault/uralkal`. Удаление VCT — тоже изменение неизменяемого поля, поэтому
|
||||||
|
та же процедура: suspend → `delete sts --cascade=orphan` → resume → unseal по одному. PVC `audit-*` после отката
|
||||||
|
остаются, удалить вручную, когда журнал уже не нужен.
|
||||||
|
|
||||||
|
Если вернуть оверлей с включённым audit device, у Vault пропадёт `/vault/audit`, он не сможет писать журнал и
|
||||||
|
перестанет обслуживать запросы.
|
||||||
|
|
||||||
|
### Проверка после подключения
|
||||||
|
|
||||||
|
- `vault audit list`: device `file/`.
|
||||||
|
- Чтение тестового секрета → событие в `security.vault-audit` с `sarex_kind=vault_audit`, значение —
|
||||||
|
`hmac-sha256:…`, записи `"type": "request"` нет.
|
||||||
|
- Запрос без прав → `sarex_outcome=denied_authz`; неудачный логин → `denied_authn`.
|
||||||
|
- `kubectl -n security-audit scale deploy/access-collector --replicas=0` на 5 минут → Vault отвечает. После возврата
|
||||||
|
2 реплик события дочитаны без пропусков (сверка `request.id`).
|
||||||
|
- Искусственная ротация: `MAX_BYTES` поменять в ConfigMap нельзя, это env в values. Временно на одном поде:
|
||||||
|
`kubectl -n vault exec vault-vault-contour-N -c audit-rotate -- sh -c 'MAX_BYTES=1 INTERVAL=1 timeout 5 sh /scripts/rotate.sh'`
|
||||||
|
→ появился `audit.log.1`, новый `audit.log` создан Vault, события не потеряны.
|
||||||
|
|
||||||
|
### Мониторинг и алерты
|
||||||
|
|
||||||
|
- **Заполнение PVC `audit-*` > 80% — критично:** при полном диске Vault встаёт. Метрика `kubelet_volume_stats_*`.
|
||||||
|
- Ошибки и отставание `audit-shipper`:
|
||||||
|
- `otelcol_exporter_send_failed_log_records{exporter="otlp/audit"}`;
|
||||||
|
- `otelcol_exporter_queue_size` против `otelcol_exporter_queue_capacity`.
|
||||||
|
|
||||||
|
Метрики на `:8888` пода Vault; сбора метрик в Уралкалии пока нет.
|
||||||
|
- Отключение audit device: событие с путём `sys/audit/*` в самом журнале → правило алерта в processor.
|
||||||
|
- Строки `rotate: ОШИБКА` в логе `audit-rotate`.
|
||||||
|
|
||||||
|
### Открытые вопросы
|
||||||
|
|
||||||
|
- Размер PVC, `MAX_BYTES`, `KEEP`, `queue_size` — по замеру объёма. `KEEP × MAX_BYTES` должен перекрывать худший простой
|
||||||
|
коллектора, а их сумма с очередью — оставаться ниже 80% PVC.
|
||||||
|
- Фильтрация шумных путей средствами Vault: в OSS есть только `elide_list_responses` и `hmac_accessor`; фильтры audit
|
||||||
|
(`filter`) — Enterprise. Поэтому `request`-записи отбрасываются в shipper'е.
|
||||||
|
- Какой узел что пишет: standby в OSS пересылает запросы на активный, поэтому основной поток — с лидера; shipper есть на
|
||||||
|
всех трёх.
|
||||||
|
|
||||||
|
## Проверить перед подключением
|
||||||
|
|
||||||
|
Сверено по репозиторию и чартам 01.11.2026, доступа к кластеру не было.
|
||||||
|
|
||||||
|
| Что | Результат | Как проверено |
|
||||||
|
|---|---|---|
|
||||||
|
| Образ и `command.name` | `.../opentelemetry-collector/opentelemetry-collector-contrib:0.121.0-amd64`, `otelcol-contrib` | дефолт `values.yaml` чарта `opentelemetry-collector-prod` 0.117.1 (appVersion 0.120.0, тег образа 0.121.0); `otelcol-contrib components` в образе: filelog, transform, kafka, file_storage, health_check, memory_limiter, batch есть |
|
||||||
|
| Синтаксис kafka exporter | для 0.121.0 корректен (`topic`/`encoding` наверху, `auth.tls`) | `otelcol-contrib validate` в том же образе — OK |
|
||||||
|
| Kafka Service и порт | `kafka-kafka-contour.kafka.svc.cluster.local:9092`, `SASL_SSL` (**не** `kafka.kafka...`) | `helm template` чарта `kafka-contour` 0.1.15 с values `uralkal`; тот же адрес у `apps/*/uralkal` |
|
||||||
|
| CA Kafka | Secret `kafka/kafka-kafka-contour-tls`, ключ `ca.crt` (helm `genCA`, 365 дней) | шаблон `tls-secret.yaml` чарта; `apps/issues/uralkal` монтирует его же |
|
||||||
|
| Kafka 4.0.0 и `protocol_version: 2.8.0` | совместимо (Kafka 4 требует ≥ 2.1) | `appVersion` чарта `kafka-contour` |
|
||||||
|
| Vault | HelmRelease `vault/vault`; injector ходит на `http://vault-vault-contour.vault.svc:8200` | `infrastructure/vault/uralkal`, `helm template` `vault-contour` 0.2.3 |
|
||||||
|
| Узлы мастеров | метка и taint `node-role.kubernetes.io/control-plane` | так же сажается `istio-gateway/uralkal` |
|
||||||
|
| Рендер чарта | нет `hostPort`, Service, ClusterRole; nodeSelector/tolerations/securityContext как заданы | `helm template` 0.117.1 с values из собранного HelmRelease |
|
||||||
|
| Логика `transform` | 5 тестовых событий классифицированы верно, `token=***`, `managedFields` вырезаны | запуск коллектора локально в том же образе с `file`-экспортёром вместо kafka |
|
||||||
|
| **Журнал доступа шлюза** (02.11.2026) | | |
|
||||||
|
| Шлюз | HelmRelease `istio-system/ingressgateway`, метка `istio: ingressgateway`, 1 реплика на control-plane, `NodePort` 30080/30443, `externalTrafficPolicy` не задан (`Cluster`) | `helm template` `istio-gateway-contour` 1.24.0 с values `uralkal` |
|
||||||
|
| istiod | `istiod-contour` 1.24.0, ревизий нет; кроме `extensionProviders` mesh config не изменился | `helm template` до и после правки, diff только в ConfigMap `istio` |
|
||||||
|
| Инъекция sidecar | ns `istio-injection=enabled` или ns без метки + метка пода `sidecar.istio.io/inject=true`; `disabled` блокирует оба | селекторы MutatingWebhookConfiguration из рендера `istiod-contour` |
|
||||||
|
| mTLS | `PeerAuthentication` в репозитории нет → PERMISSIVE | поиск по `infrastructure/`, `clusters/uralkal`, чарту `istio-config-contour` |
|
||||||
|
| Vault + sidecar | `excludeOutboundPorts: "8200"` рядом с `agent-init-first` | `apps/bi`, `apps/bim`, `apps/cde/base` |
|
||||||
|
| Образ access-collector | тот же; `otlp` receiver, `transform`, `kafka` exporter есть | `otelcol-contrib validate` — OK |
|
||||||
|
| Логика `transform/access` | 7 записей в формате Envoy ALS: `-` → пусто, `token/***`, `***jwt***`, классы верные | локальный коллектор, OTLP/HTTP → `file`-экспортёр |
|
||||||
|
| Схемы Istio | EnvoyFilter (`v1alpha3`) и Telemetry (`v1`) проходят CRD-схемы `istio-base-contour` 1.24.0 | свой проверщик по `openAPIV3Schema`; `istioctl`/`kubeconform` на машине нет. Конфиг Lua (`patch.value`) схемой не проверяется |
|
||||||
|
| **События Zitadel** (02.11.2026) | | |
|
||||||
|
| Zitadel в mesh | ns `zitadel` `istio-injection: enabled`, у Deployment `sidecar.istio.io/inject: "true"`, SA `zitadel`, метки `app.kubernetes.io/name=idp-contour`, `component=start`; trust domain `cluster.local` | `helm template` `idp-contour` 4.12.13 с values `uralkal`, mesh config istiod |
|
||||||
|
| Версия Zitadel | образ `contour/zitadel/zitadel:4.12.2` | `values.yaml` чарта |
|
||||||
|
| Actions v2 API | `POST /v2/actions/targets`, `POST /v2/actions/targets/{id}` (update), `POST …/targets/search` (`targetNameFilter`), `PUT /v2/actions/executions` (`targets` — список id), `POST …/executions/search` (`inConditionsFilter`); условие `event.event`/`event.group`/`event.all`; feature flag не нужен | proto `zitadel/action/v2`, `feature/v2` тега v4.12.2 |
|
||||||
|
| Формат события | `ContextInfoEvent`, `event_payload` — `json.RawMessage`; повторов для async нет | `internal/repository/execution/queue.go`, `internal/execution/*.go` v4.12.2 |
|
||||||
|
| Имена событий | все 58 из `events.txt` есть в `internal/repository/{user,usergrant,org,project,instance,member}` v4.12.2; агрегата `group` нет | исходники; окончательно — строки `known:` в логе Job'а |
|
||||||
|
| `webhookevent` 0.121 | есть в образе; 200 после `ConsumeLogs` (с `batch` — после приёма в батч), 500 при ошибке; одна запись на запрос; `required_header` есть, `header_attribute_regex` нет | исходники receiver v0.121.0; локальный прогон |
|
||||||
|
| OTTL | `ParseJSON`, `Decode(…, "base64")`, `IsString`, `IsMatch`, `IsMap`, литерал map, `set` во вложенный отсутствующий map — работают | `validate` и прогон 6 событий: канарейки (хеш, base64, не-JSON) не дошли |
|
||||||
|
| Образ Job | `curl` + `jq` вместе нет; `kubectl:1.33.4` (jq 1.6) + `kafka:4.0.0-debian-12-r10` (curl 7.88) | `docker run` образов из registry контура |
|
||||||
|
| **Журнал аудита Vault** (02.11.2026) | | |
|
||||||
|
| Чарт `vault-contour` 0.2.3 | values: `server.auditStorage` (VCT `audit` + mount в vault), `extraContainers`, `volumes`, `volumeMounts`, `shareProcessNamespace`, `extraEnvironmentVars` — postRenderers не нужны | шаблоны `server-statefulset.yaml`, `_helpers.tpl` |
|
||||||
|
| Режим Vault | HA raft, 3 реплики, VCT `data`, `RollingUpdate`, `podManagementPolicy: Parallel`; UID/GID 100/1000, `fsGroup: 1000`; SA `vault-vault-contour`; метки `app.kubernetes.io/name=vault-contour`, `component=server` | `helm template` с values `vault/uralkal` |
|
||||||
|
| Unseal | ручной: `vault-unseal` в Уралкалии не подключён; readiness `vault status` держит rollout до unseal | `clusters/*`, `docs/closed-contour-deployment.md`, `values.yaml` чарта |
|
||||||
|
| Istio у Vault | ns `vault` `istio-injection: enabled`, opt-out в чарте нет → в mesh; PeerAuthentication нет | рендер, `infrastructure/vault/base/namespace.yaml` |
|
||||||
|
| Версия Vault | 1.21.2: `elide_list_responses` есть, file device по SIGHUP переоткрывает файл | тег образа |
|
||||||
|
| Образы | `opentelemetry-collector-contrib:0.121.0-amd64`; busybox 1.37.0 `contour/local-path-provisioner-nn/busybox@sha256:11f8…` (sh, stat, pidof, kill, mv) | `docker run` из registry |
|
||||||
|
| `vault/uralkal-audit` | собирается; postRenderers (nodeSelector/tolerations) на месте; ConfigMap'ы в `vault`; `vault/uralkal` побайтно тот же | `kubectl kustomize`, diff |
|
||||||
|
| Итоговый StatefulSet | `shareProcessNamespace: true`, VCT `data` + `audit`, оба sidecar'а UID 100/GID 1000, `/vault/audit` rw у vault и sidecar'ов | `helm template` + post-render тем же kustomize-патчем, что у Flux |
|
||||||
|
| Ротация и доставка | фиктивный `vault` (по SIGHUP переоткрывает файл) + `rotate.sh` (`MAX_BYTES=20000`, 16 ротаций) + shipper + access-collector: 1500/1500 событий, 0 дублей, `request` отброшены, `denied_authz` размечен; то же при остановке коллектора на 25 с | Docker: общий PID namespace, том с правами 100:1000/0600, `--read-only`, `--cap-drop ALL` |
|
||||||
|
| `rotate.sh` | `sh -n` busybox/dash/bash --posix — OK; без процесса vault ротация пропускается (файл не переименован); SIGTERM — выход за 0 с | busybox из registry. shellcheck на машине нет |
|
||||||
|
| Скрипт | `sh -n` dash/bash --posix/busybox — OK; mock: создание, повтор без изменений, объединение с чужим target, обновление endpoint, `--remove`, API недоступен, 401; утечек PAT/signingKey нет | mock Zitadel на Python, запуск в образе kafka с `--read-only`, uid 1001. shellcheck на машине нет |
|
||||||
|
|
||||||
|
Остаётся открытым:
|
||||||
|
|
||||||
|
- `TODO(verify)` **nodelocaldns** (`169.254.25.10`) в NetworkPolicy: в репозитории не подтверждается. При дефолте kubespray
|
||||||
|
включён, если нет — правило безвредно.
|
||||||
|
- **RequestAuthentication** не создана: не подтверждены издатели django и document-link, `issuer` Zitadel (`TODO(verify)`) —
|
||||||
|
см. «Пользователь в журнале». Пока `user_sub` пустой.
|
||||||
|
- **IP клиента за NodePort** — см. «Открытые вопросы» журнала доступа.
|
||||||
|
- `set(body, attributes)` + `encoding: raw` даёт JSON-объект (проверено локально через `file`-экспортёр, raw-маршалер
|
||||||
|
сериализует map в JSON) — подтвердить на стенде чтением топика. Там же проверить, что Envoy шлёт labels строками
|
||||||
|
(сравнения `response_code == "401"`).
|
||||||
|
- `TODO(verify)` **API Zitadel изнутри кластера** с `x-zitadel-instance-host` (команда в «События Zitadel»): по
|
||||||
|
исходникам должно работать, запрос не выполнялся.
|
||||||
|
- `TODO(verify)` **allowlist полей** `details` по реальным событиям; **лимит ~64 КиБ** тела у `webhookevent`.
|
||||||
|
- `TODO(verify)` **toolbox-образ** для Job вместо пары kubectl + kafka.
|
||||||
|
- `TODO(verify)` **Vault:** istio-proxy у подов Vault; `ptrace_scope` на узлах; объём PVC `audit`, `MAX_BYTES`/`KEEP`/`queue_size`
|
||||||
|
по замеру; создание STS заново через `helm upgrade` после `--cascade=orphan` (Helm создаёт отсутствующий ресурс — проверить
|
||||||
|
на стенде до Уралкалия).
|
||||||
|
- Некритичные предупреждения 0.121.0: `service::telemetry::metrics::address` устарел (так же во всех коллекторах репо);
|
||||||
|
OTTL-пути без префикса `log.` (контекст выводится автоматически).
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
# Транспортная аутентификация вместо HMAC-подписи Zitadel: mTLS Istio + проверка identity источника.
|
||||||
|
# Как только к workload применяется хотя бы одна ALLOW-политика, весь остальной трафик к нему запрещён.
|
||||||
|
# - Probe'ы kubelet не блокируются: sidecar переписывает их на pilot-agent (:15020), а тот обращается
|
||||||
|
# к приложению напрямую, мимо Envoy и его RBAC-фильтра.
|
||||||
|
# - При подключении сбора метрик коллектора (:8888) добавить отдельное правило для источника сбора.
|
||||||
|
# Условие: Zitadel в mesh (ns zitadel istio-injection=enabled, под с sidecar.istio.io/inject=true), иначе
|
||||||
|
# у его запросов нет principal и правило 1 их отклонит.
|
||||||
|
apiVersion: security.istio.io/v1
|
||||||
|
kind: AuthorizationPolicy
|
||||||
|
metadata:
|
||||||
|
name: access-collector
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
security-audit/collector: access # podLabels HelmRelease access-collector
|
||||||
|
action: ALLOW
|
||||||
|
rules:
|
||||||
|
- from:
|
||||||
|
- source:
|
||||||
|
principals: ["cluster.local/ns/zitadel/sa/zitadel"] # SA Deployment zitadel-idp-contour
|
||||||
|
to:
|
||||||
|
- operation: {ports: ["8088"], methods: ["POST"], paths: ["/zitadel/events"]}
|
||||||
|
- from:
|
||||||
|
- source:
|
||||||
|
namespaces: ["istio-system"]
|
||||||
|
to:
|
||||||
|
- operation: {ports: ["4317"]}
|
||||||
|
# Журнал аудита Vault: поды StatefulSet vault-vault-contour в mesh (ns vault, istio-injection=enabled).
|
||||||
|
# Если Vault выведут из mesh — principal пропадёт, будет 403, shipper копит очередь на PVC (см. README).
|
||||||
|
- from:
|
||||||
|
- source:
|
||||||
|
principals: ["cluster.local/ns/vault/sa/vault-vault-contour"]
|
||||||
|
to:
|
||||||
|
- operation: {ports: ["4319"]}
|
||||||
@ -0,0 +1,270 @@
|
|||||||
|
# Журнал доступа ingress-шлюза Istio: envoyOtelAls (провайдер audit-access в meshConfig istiod) → OTLP gRPC → Kafka.
|
||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: access-collector
|
||||||
|
namespace: security-audit
|
||||||
|
spec:
|
||||||
|
interval: 10m
|
||||||
|
timeout: 10m
|
||||||
|
chart:
|
||||||
|
spec:
|
||||||
|
chart: opentelemetry-collector-prod
|
||||||
|
version: "0.117.1"
|
||||||
|
sourceRef:
|
||||||
|
kind: HelmRepository
|
||||||
|
name: yc-oci-charts
|
||||||
|
namespace: flux-system
|
||||||
|
interval: 10m
|
||||||
|
install:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
upgrade:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
values:
|
||||||
|
mode: deployment
|
||||||
|
replicaCount: 2
|
||||||
|
fullnameOverride: access-collector
|
||||||
|
|
||||||
|
# Тот же образ, что у audit-collector: otlp receiver, transform, kafka exporter есть в сборке.
|
||||||
|
image:
|
||||||
|
repository: cr.yandex/crp3ccidau046kdj8g9q/opentelemetry-collector/opentelemetry-collector-contrib
|
||||||
|
tag: "0.121.0-amd64"
|
||||||
|
command:
|
||||||
|
name: otelcol-contrib
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
|
||||||
|
podDisruptionBudget:
|
||||||
|
enabled: true
|
||||||
|
minAvailable: 1
|
||||||
|
# Мягкая: при одном доступном узле вторая реплика всё равно встанет.
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 100
|
||||||
|
podAffinityTerm:
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
security-audit/collector: access
|
||||||
|
|
||||||
|
clusterRole:
|
||||||
|
create: false
|
||||||
|
serviceAccount:
|
||||||
|
create: true
|
||||||
|
name: access-collector # bound SA роли Vault security-audit (вместе с audit-collector)
|
||||||
|
presets:
|
||||||
|
logsCollection: {enabled: false}
|
||||||
|
hostMetrics: {enabled: false}
|
||||||
|
kubernetesAttributes: {enabled: false}
|
||||||
|
kubeletMetrics: {enabled: false}
|
||||||
|
kubernetesEvents: {enabled: false}
|
||||||
|
clusterMetrics: {enabled: false}
|
||||||
|
|
||||||
|
ports:
|
||||||
|
otlp:
|
||||||
|
enabled: true
|
||||||
|
containerPort: 4317
|
||||||
|
servicePort: 4317
|
||||||
|
protocol: TCP
|
||||||
|
appProtocol: grpc # Istio выбирает протокол по appProtocol
|
||||||
|
# События Zitadel Actions v2 (Target restAsync) — обычный JSON по HTTP, приёмник webhookevent.
|
||||||
|
zitadel-events:
|
||||||
|
enabled: true
|
||||||
|
containerPort: 8088
|
||||||
|
servicePort: 8088
|
||||||
|
protocol: TCP
|
||||||
|
appProtocol: http
|
||||||
|
# Журнал аудита Vault от sidecar'а audit-shipper в поде Vault (OTLP gRPC).
|
||||||
|
otlp-vault:
|
||||||
|
enabled: true
|
||||||
|
containerPort: 4319
|
||||||
|
servicePort: 4319
|
||||||
|
protocol: TCP
|
||||||
|
appProtocol: grpc
|
||||||
|
otlp-http: {enabled: false}
|
||||||
|
jaeger-compact: {enabled: false}
|
||||||
|
jaeger-thrift: {enabled: false}
|
||||||
|
jaeger-grpc: {enabled: false}
|
||||||
|
zipkin: {enabled: false}
|
||||||
|
metrics: {enabled: false}
|
||||||
|
service:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
podSecurityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 10001
|
||||||
|
runAsGroup: 10001
|
||||||
|
seccompProfile: {type: RuntimeDefault}
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 10001
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
privileged: false
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests: {cpu: 100m, memory: 128Mi}
|
||||||
|
limits: {cpu: 1000m, memory: 512Mi}
|
||||||
|
|
||||||
|
podLabels:
|
||||||
|
security-audit/collector: access # селектор NetworkPolicy и anti-affinity
|
||||||
|
sidecar.istio.io/inject: "true" # mTLS внутри mesh (п.30); namespace без istio-injection
|
||||||
|
podAnnotations:
|
||||||
|
sidecar.istio.io/inject: "true"
|
||||||
|
# Kafka exporter подключается к брокерам на старте: без готового istio-proxy коллектор упадёт.
|
||||||
|
proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}'
|
||||||
|
# Паттерн контура для Vault + sidecar (apps/*/base): трафик агента к Vault мимо istio-proxy.
|
||||||
|
traffic.sidecar.istio.io/excludeOutboundPorts: "8200"
|
||||||
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-init-first: "true"
|
||||||
|
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||||
|
vault.hashicorp.com/agent-requests-cpu: "25m"
|
||||||
|
vault.hashicorp.com/agent-limits-cpu: "100m"
|
||||||
|
vault.hashicorp.com/auth-path: auth/kubernetes
|
||||||
|
vault.hashicorp.com/role: security-audit
|
||||||
|
# Многострочные шаблоны — см. helmrelease-collector.yaml (toYaml | tpl в чарте).
|
||||||
|
vault.hashicorp.com/agent-inject-secret-kafka-username: secrets/data/security-audit/kafka
|
||||||
|
vault.hashicorp.com/agent-inject-template-kafka-username: |-
|
||||||
|
{{ "{{- with secret \"secrets/data/security-audit/kafka\" -}}" }}
|
||||||
|
{{ "{{ .Data.data.username }}" }}
|
||||||
|
{{ "{{- end -}}" }}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-kafka-password: secrets/data/security-audit/kafka
|
||||||
|
vault.hashicorp.com/agent-inject-template-kafka-password: |-
|
||||||
|
{{ "{{- with secret \"secrets/data/security-audit/kafka\" -}}" }}
|
||||||
|
{{ "{{ .Data.data.password }}" }}
|
||||||
|
{{ "{{- end -}}" }}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-kafka-ca.crt: secrets/data/security-audit/kafka
|
||||||
|
vault.hashicorp.com/agent-inject-template-kafka-ca.crt: |-
|
||||||
|
{{ "{{- with secret \"secrets/data/security-audit/kafka\" -}}" }}
|
||||||
|
{{ "{{ .Data.data.ca_crt }}" }}
|
||||||
|
{{ "{{- end -}}" }}
|
||||||
|
|
||||||
|
# Контур-специфичное (CONTOUR, KAFKA_BROKERS) задаётся в оверлее через extraEnvs.
|
||||||
|
alternateConfig:
|
||||||
|
extensions:
|
||||||
|
health_check: {endpoint: "${env:MY_POD_IP}:13133"}
|
||||||
|
receivers:
|
||||||
|
otlp:
|
||||||
|
protocols:
|
||||||
|
grpc:
|
||||||
|
endpoint: "${env:MY_POD_IP}:4317"
|
||||||
|
max_recv_msg_size_mib: 16
|
||||||
|
# Подпись HMAC (X-ZITADEL-Signature) не проверяется: доступ ограничен mTLS + AuthorizationPolicy
|
||||||
|
# (только SA zitadel) + NetworkPolicy. Отвечает 200 после ConsumeLogs, т. е. после приёма в batch.
|
||||||
|
# Отдельный порт: доступ на 4319 только для SA Vault (AuthorizationPolicy), 4317 — только шлюз.
|
||||||
|
otlp/vault:
|
||||||
|
protocols:
|
||||||
|
grpc:
|
||||||
|
endpoint: "${env:MY_POD_IP}:4319"
|
||||||
|
webhookevent/zitadel:
|
||||||
|
endpoint: "${env:MY_POD_IP}:8088"
|
||||||
|
path: /zitadel/events
|
||||||
|
health_path: /zitadel/health
|
||||||
|
processors:
|
||||||
|
memory_limiter: {check_interval: 1s, limit_mib: 400, spike_limit_mib: 100}
|
||||||
|
transform/access:
|
||||||
|
error_mode: ignore
|
||||||
|
log_statements:
|
||||||
|
- context: log
|
||||||
|
statements:
|
||||||
|
# labels из envoyOtelAls приходят атрибутами — делаем из них тело (в Kafka уходит body как JSON)
|
||||||
|
- set(body, attributes)
|
||||||
|
- set(body["sarex_contour"], "${env:CONTOUR}")
|
||||||
|
# Envoy пишет "-" для пустых значений
|
||||||
|
- set(body["user_sub"], "") where body["user_sub"] == "-"
|
||||||
|
- set(body["user_org"], "") where body["user_org"] == "-"
|
||||||
|
- set(body["trace_id"], "") where body["trace_id"] == "-"
|
||||||
|
# маскирование: токены в самом пути (публичные ссылки и т. п.)
|
||||||
|
- replace_pattern(body["path"], "(?i)(token|secret|key|signature)/[^/]+", "$$1/***")
|
||||||
|
- replace_pattern(body["path"], "[A-Za-z0-9_-]{20,}\\.[A-Za-z0-9_-]{20,}\\.[A-Za-z0-9_-]{10,}", "***jwt***")
|
||||||
|
# классификация
|
||||||
|
- set(body["sarex_action"], "read") where body["method"] == "GET" or body["method"] == "HEAD" or body["method"] == "OPTIONS"
|
||||||
|
- set(body["sarex_action"], "mutate") where body["method"] == "POST" or body["method"] == "PUT" or body["method"] == "PATCH" or body["method"] == "DELETE"
|
||||||
|
- set(body["sarex_outcome"], "applied") where Int(body["response_code"]) >= 200 and Int(body["response_code"]) < 400
|
||||||
|
- set(body["sarex_outcome"], "denied_authn") where body["response_code"] == "401"
|
||||||
|
- set(body["sarex_outcome"], "denied_authz") where body["response_code"] == "403"
|
||||||
|
- set(body["sarex_outcome"], "invalid") where Int(body["response_code"]) >= 400 and Int(body["response_code"]) < 500 and body["response_code"] != "401" and body["response_code"] != "403"
|
||||||
|
- set(body["sarex_outcome"], "error") where Int(body["response_code"]) >= 500 or body["response_code"] == "0"
|
||||||
|
- set(body["sarex_actor_type"], "anonymous")
|
||||||
|
- set(body["sarex_actor_type"], "human") where body["user_sub"] != ""
|
||||||
|
transform/idp:
|
||||||
|
error_mode: ignore
|
||||||
|
log_statements:
|
||||||
|
- context: log
|
||||||
|
statements:
|
||||||
|
- set(body, ParseJSON(body))
|
||||||
|
# Не JSON — тело целиком заменяется маркером: сырой текст мог бы унести event_payload в Kafka (п.23).
|
||||||
|
- 'set(body, {"sarex_kind": "idp_event", "sarex_error": "unparsed"}) where not IsMap(body)'
|
||||||
|
# В 4.12.2 event_payload — json.RawMessage (объект); base64-строку тоже поддерживаем
|
||||||
|
- set(body["event_payload"], ParseJSON(Decode(body["event_payload"], "base64"))) where IsString(body["event_payload"])
|
||||||
|
# allowlist (п.23): нужные ИБ поля копируются явно, payload удаляется ЦЕЛИКОМ
|
||||||
|
# (в событиях бывают хеши паролей, OTP-секреты, client secret, ПДн)
|
||||||
|
# TODO(verify): список полей по типам событий уточнить по реальным событиям на стенде
|
||||||
|
- set(body["details"]["user_name"], body["event_payload"]["userName"]) where IsMatch(body["event_type"], "^user\\.human\\.(added|selfregistered)$")
|
||||||
|
- set(body["details"]["role_keys"], body["event_payload"]["roleKeys"]) where IsMatch(body["event_type"], "^user\\.grant\\.")
|
||||||
|
- set(body["details"]["project_id"], body["event_payload"]["projectId"]) where IsMatch(body["event_type"], "^user\\.grant\\.")
|
||||||
|
- delete_key(body, "event_payload")
|
||||||
|
# обогащение и классификация
|
||||||
|
- set(body["sarex_kind"], "idp_event")
|
||||||
|
- set(body["sarex_contour"], "${env:CONTOUR}")
|
||||||
|
- set(body["sarex_outcome"], "applied")
|
||||||
|
- set(body["sarex_outcome"], "denied_authn") where IsMatch(body["event_type"], "check\\.failed$")
|
||||||
|
- set(body["sarex_actor_type"], "human")
|
||||||
|
- set(body["sarex_actor_type"], "system") where body["userID"] == "" or body["userID"] == nil
|
||||||
|
batch: {send_batch_size: 1000, send_batch_max_size: 2000, timeout: 2s}
|
||||||
|
exporters:
|
||||||
|
# Общие поля Kafka — YAML-якоря (&kafka_*), kafka/idp ссылается на них (*kafka_*).
|
||||||
|
kafka/access:
|
||||||
|
brokers: &kafka_brokers ["${env:KAFKA_BROKERS}"]
|
||||||
|
protocol_version: &kafka_protocol_version 2.8.0
|
||||||
|
topic: security.mesh-access
|
||||||
|
encoding: raw # в сообщение — только body (JSON записи), без OTLP-обёртки
|
||||||
|
auth: &kafka_auth
|
||||||
|
sasl: {mechanism: SCRAM-SHA-512, username: "${file:/vault/secrets/kafka-username}", password: "${file:/vault/secrets/kafka-password}"}
|
||||||
|
tls: {insecure: false, ca_file: /vault/secrets/kafka-ca.crt}
|
||||||
|
producer: &kafka_producer {required_acks: -1, compression: zstd, max_message_bytes: 1048576}
|
||||||
|
# Очередь в памяти (диска у Deployment нет): при падении пода неотправленные записи теряются.
|
||||||
|
sending_queue: &kafka_sending_queue {enabled: true, queue_size: 50000, num_consumers: 4}
|
||||||
|
retry_on_failure: &kafka_retry {enabled: true, initial_interval: 5s, max_interval: 60s, max_elapsed_time: 0}
|
||||||
|
kafka/idp:
|
||||||
|
brokers: *kafka_brokers
|
||||||
|
protocol_version: *kafka_protocol_version
|
||||||
|
topic: security.idp-events
|
||||||
|
encoding: raw
|
||||||
|
auth: *kafka_auth
|
||||||
|
producer: *kafka_producer
|
||||||
|
sending_queue: *kafka_sending_queue # своя очередь у каждого экспортёра, общие только настройки
|
||||||
|
retry_on_failure: *kafka_retry
|
||||||
|
# Body уже JSON-объект (json_parser в audit-shipper, sarex_* там же) — transform не нужен.
|
||||||
|
kafka/vault:
|
||||||
|
brokers: *kafka_brokers
|
||||||
|
protocol_version: *kafka_protocol_version
|
||||||
|
topic: security.vault-audit
|
||||||
|
encoding: raw
|
||||||
|
auth: *kafka_auth
|
||||||
|
producer: *kafka_producer
|
||||||
|
sending_queue: *kafka_sending_queue
|
||||||
|
retry_on_failure: *kafka_retry
|
||||||
|
service:
|
||||||
|
extensions: [health_check]
|
||||||
|
pipelines:
|
||||||
|
logs/access:
|
||||||
|
receivers: [otlp]
|
||||||
|
processors: [memory_limiter, transform/access, batch]
|
||||||
|
exporters: [kafka/access]
|
||||||
|
logs/idp:
|
||||||
|
receivers: [webhookevent/zitadel]
|
||||||
|
processors: [memory_limiter, transform/idp, batch]
|
||||||
|
exporters: [kafka/idp]
|
||||||
|
logs/vault:
|
||||||
|
receivers: [otlp/vault]
|
||||||
|
processors: [memory_limiter, batch]
|
||||||
|
exporters: [kafka/vault]
|
||||||
|
telemetry:
|
||||||
|
logs: {level: info}
|
||||||
|
metrics: {address: "${env:MY_POD_IP}:8888"}
|
||||||
245
infrastructure/security-audit/base/helmrelease-collector.yaml
Normal file
245
infrastructure/security-audit/base/helmrelease-collector.yaml
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: audit-collector
|
||||||
|
namespace: security-audit
|
||||||
|
spec:
|
||||||
|
interval: 10m
|
||||||
|
timeout: 10m
|
||||||
|
chart:
|
||||||
|
spec:
|
||||||
|
chart: opentelemetry-collector-prod
|
||||||
|
version: "0.117.1"
|
||||||
|
sourceRef:
|
||||||
|
kind: HelmRepository
|
||||||
|
name: yc-oci-charts
|
||||||
|
namespace: flux-system
|
||||||
|
interval: 10m
|
||||||
|
install:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
upgrade:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
values:
|
||||||
|
mode: daemonset
|
||||||
|
fullnameOverride: audit-collector
|
||||||
|
|
||||||
|
# Дефолт чарта 0.117.1 (appVersion 0.120.0), задан явно: contrib-сборка с filelog, transform,
|
||||||
|
# kafka exporter, file_storage, health_check, memory_limiter. Зеркало в registry контура — см. README.
|
||||||
|
image:
|
||||||
|
repository: cr.yandex/crp3ccidau046kdj8g9q/opentelemetry-collector/opentelemetry-collector-contrib
|
||||||
|
tag: "0.121.0-amd64"
|
||||||
|
command:
|
||||||
|
name: otelcol-contrib
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
|
||||||
|
# Только мастера: журнал аудита есть только там.
|
||||||
|
nodeSelector:
|
||||||
|
node-role.kubernetes.io/control-plane: ""
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/control-plane
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
rollout:
|
||||||
|
rollingUpdate:
|
||||||
|
maxUnavailable: 1
|
||||||
|
|
||||||
|
# Доступ к Kubernetes API коллектору не нужен.
|
||||||
|
clusterRole:
|
||||||
|
create: false
|
||||||
|
serviceAccount:
|
||||||
|
create: true
|
||||||
|
name: audit-collector # = роль Vault security-audit, bound SA
|
||||||
|
presets:
|
||||||
|
logsCollection: {enabled: false}
|
||||||
|
hostMetrics: {enabled: false}
|
||||||
|
kubernetesAttributes: {enabled: false}
|
||||||
|
kubeletMetrics: {enabled: false}
|
||||||
|
kubernetesEvents: {enabled: false}
|
||||||
|
clusterMetrics: {enabled: false}
|
||||||
|
|
||||||
|
# В daemonset-режиме чарт вешает hostPort на OTLP/Jaeger/Zipkin: приёмники не нужны,
|
||||||
|
# а hostPort конфликтует с другими DaemonSet-коллекторами на тех же узлах.
|
||||||
|
ports:
|
||||||
|
otlp: {enabled: false}
|
||||||
|
otlp-http: {enabled: false}
|
||||||
|
jaeger-compact: {enabled: false}
|
||||||
|
jaeger-thrift: {enabled: false}
|
||||||
|
jaeger-grpc: {enabled: false}
|
||||||
|
zipkin: {enabled: false}
|
||||||
|
metrics: {enabled: false}
|
||||||
|
service:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Журнал аудита — 0600 root:root. Root нужен как владелец файла, capabilities не нужны.
|
||||||
|
podSecurityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
runAsGroup: 0
|
||||||
|
seccompProfile: {type: RuntimeDefault}
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
runAsNonRoot: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
privileged: false
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests: {cpu: 50m, memory: 128Mi}
|
||||||
|
limits: {cpu: 500m, memory: 512Mi}
|
||||||
|
|
||||||
|
extraVolumes:
|
||||||
|
- name: audit-log
|
||||||
|
hostPath: {path: /var/log/audit, type: DirectoryOrCreate}
|
||||||
|
- name: otelcol-storage
|
||||||
|
hostPath: {path: /var/lib/otelcol-audit, type: DirectoryOrCreate}
|
||||||
|
extraVolumeMounts:
|
||||||
|
- {name: audit-log, mountPath: /var/log/audit, readOnly: true}
|
||||||
|
- {name: otelcol-storage, mountPath: /var/lib/otelcol-audit}
|
||||||
|
|
||||||
|
# Метка исключает под из webhook'а инъекции Istio ещё до мутации (namespace без istio-injection).
|
||||||
|
podLabels:
|
||||||
|
sidecar.istio.io/inject: "false"
|
||||||
|
podAnnotations:
|
||||||
|
sidecar.istio.io/inject: "false"
|
||||||
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-init-first: "true"
|
||||||
|
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||||
|
vault.hashicorp.com/agent-requests-cpu: "25m"
|
||||||
|
vault.hashicorp.com/agent-limits-cpu: "100m"
|
||||||
|
vault.hashicorp.com/auth-path: auth/kubernetes
|
||||||
|
vault.hashicorp.com/role: security-audit
|
||||||
|
# Чарт рендерит podAnnotations через toYaml | tpl: однострочный шаблон toYaml переносит,
|
||||||
|
# и литерал {{ "..." }} рвётся. Поэтому по строке на действие (block scalar не переносится).
|
||||||
|
vault.hashicorp.com/agent-inject-secret-kafka-username: secrets/data/security-audit/kafka
|
||||||
|
vault.hashicorp.com/agent-inject-template-kafka-username: |-
|
||||||
|
{{ "{{- with secret \"secrets/data/security-audit/kafka\" -}}" }}
|
||||||
|
{{ "{{ .Data.data.username }}" }}
|
||||||
|
{{ "{{- end -}}" }}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-kafka-password: secrets/data/security-audit/kafka
|
||||||
|
vault.hashicorp.com/agent-inject-template-kafka-password: |-
|
||||||
|
{{ "{{- with secret \"secrets/data/security-audit/kafka\" -}}" }}
|
||||||
|
{{ "{{ .Data.data.password }}" }}
|
||||||
|
{{ "{{- end -}}" }}
|
||||||
|
vault.hashicorp.com/agent-inject-secret-kafka-ca.crt: secrets/data/security-audit/kafka
|
||||||
|
vault.hashicorp.com/agent-inject-template-kafka-ca.crt: |-
|
||||||
|
{{ "{{- with secret \"secrets/data/security-audit/kafka\" -}}" }}
|
||||||
|
{{ "{{ .Data.data.ca_crt }}" }}
|
||||||
|
{{ "{{- end -}}" }}
|
||||||
|
|
||||||
|
# Контур-специфичное (CONTOUR, KAFKA_BROKERS) задаётся в оверлее через extraEnvs.
|
||||||
|
# Полный конфиг вместо мержа с дефолтами чарта.
|
||||||
|
alternateConfig:
|
||||||
|
extensions:
|
||||||
|
health_check:
|
||||||
|
endpoint: ${env:MY_POD_IP}:13133
|
||||||
|
file_storage/audit:
|
||||||
|
directory: /var/lib/otelcol-audit
|
||||||
|
compaction:
|
||||||
|
on_start: true
|
||||||
|
directory: /var/lib/otelcol-audit
|
||||||
|
|
||||||
|
receivers:
|
||||||
|
filelog/audit:
|
||||||
|
include:
|
||||||
|
- /var/log/audit/kube-apiserver-audit*.log # текущий и ротированные (lumberjack)
|
||||||
|
start_at: beginning
|
||||||
|
storage: file_storage/audit # позиции чтения переживают рестарт
|
||||||
|
max_log_size: 8MiB # дефолт 1MiB режет RequestResponse
|
||||||
|
poll_interval: 1s
|
||||||
|
include_file_name: false
|
||||||
|
operators:
|
||||||
|
- type: json_parser
|
||||||
|
parse_to: body
|
||||||
|
|
||||||
|
processors:
|
||||||
|
memory_limiter:
|
||||||
|
check_interval: 1s
|
||||||
|
limit_mib: 400
|
||||||
|
spike_limit_mib: 100
|
||||||
|
transform/audit:
|
||||||
|
error_mode: ignore
|
||||||
|
log_statements:
|
||||||
|
- context: log
|
||||||
|
statements:
|
||||||
|
# --- объём и шум ---
|
||||||
|
- delete_key(body["requestObject"]["metadata"], "managedFields") where body["requestObject"] != nil
|
||||||
|
- delete_key(body["responseObject"]["metadata"], "managedFields") where body["responseObject"] != nil
|
||||||
|
- delete_key(body["requestObject"]["metadata"]["annotations"], "kubectl.kubernetes.io/last-applied-configuration") where body["requestObject"] != nil
|
||||||
|
- delete_key(body["responseObject"]["metadata"]["annotations"], "kubectl.kubernetes.io/last-applied-configuration") where body["responseObject"] != nil
|
||||||
|
# --- маскирование (п.23): значения, а не сама команда exec ---
|
||||||
|
- replace_pattern(body["requestURI"], "(?i)(password|passwd|pwd|token|secret|api[_-]?key)(=|%3D)[^&]*", "$$1$$2***")
|
||||||
|
# --- обогащение ---
|
||||||
|
- set(body["sarex_contour"], "${env:CONTOUR}")
|
||||||
|
- set(body["sarex_dry_run"], IsMatch(body["requestURI"], "[?&]dryRun=All"))
|
||||||
|
# --- действие ---
|
||||||
|
- set(body["sarex_action"], "read") where body["verb"] == "get" or body["verb"] == "list"
|
||||||
|
- set(body["sarex_action"], "mutate") where body["verb"] == "create" or body["verb"] == "update" or body["verb"] == "patch" or body["verb"] == "delete" or body["verb"] == "deletecollection"
|
||||||
|
- set(body["sarex_action"], "status") where body["objectRef"]["subresource"] == "status"
|
||||||
|
- set(body["sarex_action"], "interactive") where IsMatch(body["objectRef"]["subresource"], "^(exec|attach|portforward)$")
|
||||||
|
# --- исход ---
|
||||||
|
- set(body["sarex_outcome"], "applied") where body["responseStatus"]["code"] >= 200 and body["responseStatus"]["code"] < 300 and body["sarex_dry_run"] == false
|
||||||
|
- set(body["sarex_outcome"], "dry_run") where body["responseStatus"]["code"] >= 200 and body["responseStatus"]["code"] < 300 and body["sarex_dry_run"] == true
|
||||||
|
- set(body["sarex_outcome"], "denied_authn") where body["responseStatus"]["code"] == 401
|
||||||
|
- set(body["sarex_outcome"], "denied_authz") where body["responseStatus"]["code"] == 403 and body["annotations"]["authorization.k8s.io/decision"] == "forbid"
|
||||||
|
- set(body["sarex_outcome"], "denied_admission") where body["responseStatus"]["code"] >= 400 and body["responseStatus"]["code"] < 500 and body["annotations"]["authorization.k8s.io/decision"] == "allow" and body["responseStatus"]["code"] != 404 and body["responseStatus"]["code"] != 409 and body["responseStatus"]["code"] != 422
|
||||||
|
- set(body["sarex_outcome"], "invalid") where body["responseStatus"]["code"] == 422
|
||||||
|
- set(body["sarex_outcome"], "conflict") where body["responseStatus"]["code"] == 409
|
||||||
|
- set(body["sarex_outcome"], "not_found") where body["responseStatus"]["code"] == 404
|
||||||
|
- set(body["sarex_outcome"], "error") where body["responseStatus"]["code"] >= 500
|
||||||
|
# --- субъект (порядок важен: сначала общее, потом частное) ---
|
||||||
|
- set(body["sarex_actor_type"], "human")
|
||||||
|
- set(body["sarex_actor_type"], "serviceaccount") where IsMatch(body["user"]["username"], "^system:serviceaccount:")
|
||||||
|
- set(body["sarex_actor_type"], "controller") where IsMatch(body["user"]["username"], "^system:serviceaccount:kube-system:") or IsMatch(body["user"]["username"], "^system:(kube-|apiserver)")
|
||||||
|
- set(body["sarex_actor_type"], "node") where IsMatch(body["user"]["username"], "^system:node:")
|
||||||
|
- set(body["sarex_actor_type"], "gitops") where IsMatch(body["user"]["username"], "^system:serviceaccount:flux-system:")
|
||||||
|
batch:
|
||||||
|
send_batch_size: 500
|
||||||
|
send_batch_max_size: 1000
|
||||||
|
timeout: 2s
|
||||||
|
|
||||||
|
exporters:
|
||||||
|
kafka/audit:
|
||||||
|
brokers:
|
||||||
|
- ${env:KAFKA_BROKERS}
|
||||||
|
protocol_version: 2.8.0
|
||||||
|
topic: security.k8s-audit
|
||||||
|
encoding: raw # в сообщение — только body (JSON события), без OTLP-обёртки
|
||||||
|
auth:
|
||||||
|
sasl:
|
||||||
|
mechanism: SCRAM-SHA-512
|
||||||
|
username: ${file:/vault/secrets/kafka-username}
|
||||||
|
password: ${file:/vault/secrets/kafka-password}
|
||||||
|
tls:
|
||||||
|
insecure: false
|
||||||
|
ca_file: /vault/secrets/kafka-ca.crt
|
||||||
|
producer:
|
||||||
|
required_acks: -1
|
||||||
|
compression: zstd
|
||||||
|
max_message_bytes: 5242880
|
||||||
|
sending_queue:
|
||||||
|
enabled: true
|
||||||
|
storage: file_storage/audit # очередь на диске: Kafka недоступна → данные не теряются
|
||||||
|
queue_size: 10000
|
||||||
|
num_consumers: 2
|
||||||
|
retry_on_failure:
|
||||||
|
enabled: true
|
||||||
|
initial_interval: 5s
|
||||||
|
max_interval: 60s
|
||||||
|
max_elapsed_time: 0 # повторять бесконечно; при полной очереди — backpressure на filelog
|
||||||
|
|
||||||
|
service:
|
||||||
|
extensions: [health_check, file_storage/audit]
|
||||||
|
pipelines:
|
||||||
|
logs/audit:
|
||||||
|
receivers: [filelog/audit]
|
||||||
|
processors: [memory_limiter, transform/audit, batch]
|
||||||
|
exporters: [kafka/audit]
|
||||||
|
telemetry:
|
||||||
|
logs:
|
||||||
|
level: info
|
||||||
|
metrics:
|
||||||
|
address: ${env:MY_POD_IP}:8888
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
# Типы событий Zitadel, на которые вешается Execution → access-collector (п.14–16 документа ИБ).
|
||||||
|
# По одному в строке, # — комментарий. Имена сверены с internal/repository/* Zitadel v4.12.2;
|
||||||
|
# фактический список инстанса Job печатает в лог (known: ...), неизвестное имя — ошибка Job'а.
|
||||||
|
# Групп пользователей (агрегат group) в v4.12.2 нет.
|
||||||
|
|
||||||
|
# создание пользователя
|
||||||
|
user.human.added
|
||||||
|
user.human.selfregistered
|
||||||
|
user.machine.added
|
||||||
|
# изменение пользователя
|
||||||
|
user.human.profile.changed
|
||||||
|
user.human.email.changed
|
||||||
|
user.human.phone.changed
|
||||||
|
user.username.changed
|
||||||
|
user.machine.changed
|
||||||
|
user.human.password.changed
|
||||||
|
# жизненный цикл: деактивация, блокировка, удаление
|
||||||
|
user.deactivated
|
||||||
|
user.reactivated
|
||||||
|
user.locked
|
||||||
|
user.unlocked
|
||||||
|
user.removed
|
||||||
|
# вход: пароль
|
||||||
|
user.human.password.check.succeeded
|
||||||
|
user.human.password.check.failed
|
||||||
|
# вход: второй фактор
|
||||||
|
user.human.mfa.otp.check.succeeded
|
||||||
|
user.human.mfa.otp.check.failed
|
||||||
|
user.human.mfa.otp.sms.check.succeeded
|
||||||
|
user.human.mfa.otp.sms.check.failed
|
||||||
|
user.human.mfa.otp.email.check.succeeded
|
||||||
|
user.human.mfa.otp.email.check.failed
|
||||||
|
user.human.mfa.u2f.token.check.succeeded
|
||||||
|
user.human.mfa.u2f.token.check.failed
|
||||||
|
user.human.passwordless.token.check.succeeded
|
||||||
|
user.human.passwordless.token.check.failed
|
||||||
|
# изменение факторов и токенов (добавление/снятие второго фактора, PAT, имперсонация)
|
||||||
|
user.human.mfa.otp.added
|
||||||
|
user.human.mfa.otp.removed
|
||||||
|
user.human.mfa.u2f.token.added
|
||||||
|
user.human.mfa.u2f.token.removed
|
||||||
|
user.human.passwordless.token.added
|
||||||
|
user.human.passwordless.token.removed
|
||||||
|
user.token.added
|
||||||
|
user.token.removed
|
||||||
|
user.impersonated
|
||||||
|
# роли (user grants)
|
||||||
|
user.grant.added
|
||||||
|
user.grant.changed
|
||||||
|
user.grant.cascade.changed
|
||||||
|
user.grant.removed
|
||||||
|
user.grant.cascade.removed
|
||||||
|
user.grant.deactivated
|
||||||
|
user.grant.reactivated
|
||||||
|
# members: инстанс (администраторы IAM), организация, проект, грант проекта
|
||||||
|
instance.member.added
|
||||||
|
instance.member.changed
|
||||||
|
instance.member.removed
|
||||||
|
instance.member.cascade.removed
|
||||||
|
org.member.added
|
||||||
|
org.member.changed
|
||||||
|
org.member.removed
|
||||||
|
org.member.cascade.removed
|
||||||
|
project.member.added
|
||||||
|
project.member.changed
|
||||||
|
project.member.removed
|
||||||
|
project.member.cascade.removed
|
||||||
|
project.grant.member.added
|
||||||
|
project.grant.member.changed
|
||||||
|
project.grant.member.removed
|
||||||
|
project.grant.member.cascade.removed
|
||||||
@ -0,0 +1,159 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Идемпотентная настройка Zitadel Actions v2 для журнала ИБ: Target (restAsync) → access-collector
|
||||||
|
# и Execution'ы на события из EVENTS_FILE. С флагом --remove (или MODE=remove) снимает Target
|
||||||
|
# с этих Execution'ов и удаляет его.
|
||||||
|
#
|
||||||
|
# Без set -x: заголовки с PAT лежат в файле $TMP/headers и в вывод не попадают.
|
||||||
|
# Ответы API с signingKey наружу не выводятся: коллектор подпись не проверяет. При переходе на приёмник
|
||||||
|
# с проверкой HMAC ключ из ответа CreateTarget нужно будет записать во Vault, а не в лог.
|
||||||
|
#
|
||||||
|
# Код выхода ≠ 0 при любой ошибке; остальные события при этом всё равно обрабатываются.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
MODE="${MODE:-apply}"
|
||||||
|
[ "${1:-}" = "--remove" ] && MODE=remove
|
||||||
|
|
||||||
|
: "${ZITADEL_API:?ZITADEL_API не задан (http://<service>:8080)}"
|
||||||
|
: "${ZITADEL_HOST:?ZITADEL_HOST не задан (внешний домен инстанса, ExternalDomain)}"
|
||||||
|
TARGET_NAME="${TARGET_NAME:-security-audit-idp-events}"
|
||||||
|
TARGET_ENDPOINT="${TARGET_ENDPOINT:-http://access-collector.security-audit.svc.cluster.local:8088/zitadel/events}"
|
||||||
|
TARGET_TIMEOUT="${TARGET_TIMEOUT:-10s}"
|
||||||
|
EVENTS_FILE="${EVENTS_FILE:-/config/events.txt}"
|
||||||
|
PAT_FILE="${PAT_FILE:-/vault/secrets/zitadel-pat}"
|
||||||
|
CURL_MAX_TIME="${CURL_MAX_TIME:-20}"
|
||||||
|
CONNECT_ATTEMPTS="${CONNECT_ATTEMPTS:-10}"
|
||||||
|
CONNECT_PAUSE="${CONNECT_PAUSE:-15}"
|
||||||
|
ISTIO_QUIT_ON_EXIT="${ISTIO_QUIT_ON_EXIT:-false}"
|
||||||
|
TMP="${TMPDIR:-/tmp}"
|
||||||
|
|
||||||
|
# Job в mesh: istio-proxy сам не завершится, без quitquitquit под останется Running.
|
||||||
|
quit_proxy() {
|
||||||
|
if [ "$ISTIO_QUIT_ON_EXIT" = "true" ]; then
|
||||||
|
curl -fsS --max-time 5 -X POST http://127.0.0.1:15020/quitquitquit >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap quit_proxy EXIT
|
||||||
|
|
||||||
|
log() { printf '%s\n' "$*"; }
|
||||||
|
err() { printf 'ОШИБКА: %s\n' "$*" >&2; }
|
||||||
|
|
||||||
|
[ -r "$PAT_FILE" ] || { err "нет файла PAT $PAT_FILE (Vault Agent)"; exit 1; }
|
||||||
|
[ -r "$EVENTS_FILE" ] || { err "нет списка событий $EVENTS_FILE"; exit 1; }
|
||||||
|
|
||||||
|
# Инстанс выбирается по x-zitadel-instance-host (InstanceHostHeaders Zitadel), Host остаётся именем Service:
|
||||||
|
# по нему istio-proxy находит маршрут и шлёт запрос с mTLS. X-Forwarded-Proto — инстанс с ExternalSecure.
|
||||||
|
umask 077
|
||||||
|
{
|
||||||
|
printf 'Authorization: Bearer %s\n' "$(tr -d '\r\n' <"$PAT_FILE")"
|
||||||
|
printf 'x-zitadel-instance-host: %s\n' "$ZITADEL_HOST"
|
||||||
|
printf 'x-zitadel-public-host: %s\n' "$ZITADEL_HOST"
|
||||||
|
printf 'X-Forwarded-Proto: https\n'
|
||||||
|
printf 'Content-Type: application/json\n'
|
||||||
|
printf 'Accept: application/json\n'
|
||||||
|
} >"$TMP/headers"
|
||||||
|
|
||||||
|
# api METHOD PATH [BODY]: тело ответа в stdout при 2xx; иначе код и текст ошибки в stderr, return 1.
|
||||||
|
api() {
|
||||||
|
_method=$1 _path=$2
|
||||||
|
if [ $# -ge 3 ]; then set -- --data "$3"; else set --; fi
|
||||||
|
_code=$(curl -sS -o "$TMP/resp" -w '%{http_code}' --max-time "$CURL_MAX_TIME" \
|
||||||
|
-X "$_method" -H @"$TMP/headers" "$@" "$ZITADEL_API$_path") || _code=000
|
||||||
|
case "$_code" in
|
||||||
|
2??) cat "$TMP/resp" ;;
|
||||||
|
*) err "$_method $_path → HTTP $_code: $(head -c 300 "$TMP/resp" 2>/dev/null | tr '\n' ' ')"; return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 1. Доступность API (Zitadel и коллектор поднимаются раньше Job'а, но без dependsOn) ---
|
||||||
|
attempt=1
|
||||||
|
until curl -fsS -o /dev/null --max-time "$CURL_MAX_TIME" -H @"$TMP/headers" "$ZITADEL_API/debug/ready"; do
|
||||||
|
if [ "$attempt" -ge "$CONNECT_ATTEMPTS" ]; then
|
||||||
|
err "Zitadel недоступен: $ZITADEL_API (попыток: $attempt)"; exit 1
|
||||||
|
fi
|
||||||
|
log "Zitadel не готов, попытка $attempt/$CONNECT_ATTEMPTS, пауза ${CONNECT_PAUSE}s"
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
sleep "$CONNECT_PAUSE"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- 2. Типы событий инстанса: в лог — для сверки списка, и для проверки имён ниже ---
|
||||||
|
# Ответ сначала в переменную: в конвейере api | jq код ошибки api потерялся бы (set -e смотрит на jq).
|
||||||
|
resp=$(api POST /admin/v1/events/types/_search '{}')
|
||||||
|
printf '%s' "$resp" | jq -r '.eventTypes[]?.type' | sort -u >"$TMP/known-events"
|
||||||
|
log "Типы событий Zitadel ($(wc -l <"$TMP/known-events")):"
|
||||||
|
sed 's/^/ known: /' "$TMP/known-events"
|
||||||
|
|
||||||
|
sed -e 's/#.*//' -e 's/[[:space:]]//g' "$EVENTS_FILE" | grep -v '^$' | sort -u >"$TMP/events"
|
||||||
|
|
||||||
|
# --- 3. Target ---
|
||||||
|
search=$(jq -nc --arg n "$TARGET_NAME" \
|
||||||
|
'{filters: [{targetNameFilter: {targetName: $n, method: "TEXT_FILTER_METHOD_EQUALS"}}]}')
|
||||||
|
resp=$(api POST /v2/actions/targets/search "$search")
|
||||||
|
printf '%s' "$resp" | jq -c --arg n "$TARGET_NAME" '[.targets[]? | select(.name == $n)
|
||||||
|
| {id, endpoint, timeout, async: has("restAsync")}]' >"$TMP/targets"
|
||||||
|
found=$(jq length "$TMP/targets")
|
||||||
|
if [ "$found" -gt 1 ]; then
|
||||||
|
err "Target'ов с именем $TARGET_NAME: $found, ожидался один — разберитесь вручную"; exit 1
|
||||||
|
fi
|
||||||
|
target_id=$(jq -r '.[0].id // empty' "$TMP/targets")
|
||||||
|
|
||||||
|
if [ "$MODE" = "apply" ]; then
|
||||||
|
want=$(jq -nc --arg e "$TARGET_ENDPOINT" --arg t "$TARGET_TIMEOUT" \
|
||||||
|
'{restAsync: {}, endpoint: $e, timeout: $t}')
|
||||||
|
if [ -z "$target_id" ]; then
|
||||||
|
# Из ответа берётся только id: signingKey не выводится и не сохраняется.
|
||||||
|
resp=$(api POST /v2/actions/targets "$(printf '%s' "$want" | jq -c --arg n "$TARGET_NAME" '. + {name: $n}')")
|
||||||
|
target_id=$(printf '%s' "$resp" | jq -r '.id // empty')
|
||||||
|
[ -n "$target_id" ] || { err "CreateTarget не вернул id"; exit 1; }
|
||||||
|
log "Target $TARGET_NAME: создан, id=$target_id"
|
||||||
|
elif jq -e --arg e "$TARGET_ENDPOINT" --arg t "$TARGET_TIMEOUT" \
|
||||||
|
'.[0] | .async and .endpoint == $e and .timeout == $t' "$TMP/targets" >/dev/null; then
|
||||||
|
log "Target $TARGET_NAME: без изменений, id=$target_id"
|
||||||
|
else
|
||||||
|
api POST "/v2/actions/targets/$target_id" "$want" >/dev/null
|
||||||
|
log "Target $TARGET_NAME: обновлён (тип/endpoint/timeout), id=$target_id"
|
||||||
|
fi
|
||||||
|
elif [ -z "$target_id" ]; then
|
||||||
|
log "Target $TARGET_NAME не найден — снимать нечего"; exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 4. Execution'ы: чужие targets сохраняются, свой добавляется в конец (или убирается при --remove) ---
|
||||||
|
rc=0; n_created=0; n_extended=0; n_same=0; n_removed=0; n_err=0
|
||||||
|
while IFS= read -r ev; do
|
||||||
|
if ! grep -qxF "$ev" "$TMP/known-events"; then
|
||||||
|
log " $ev: ошибка — такого типа нет в Zitadel"; n_err=$((n_err + 1)); rc=1; continue
|
||||||
|
fi
|
||||||
|
query=$(jq -nc --arg e "$ev" '{filters: [{inConditionsFilter: {conditions: [{event: {event: $e}}]}}]}')
|
||||||
|
if ! resp=$(api POST /v2/actions/executions/search "$query"); then
|
||||||
|
log " $ev: ошибка чтения"; n_err=$((n_err + 1)); rc=1; continue
|
||||||
|
fi
|
||||||
|
current=$(printf '%s' "$resp" \
|
||||||
|
| jq -c --arg e "$ev" '[.executions[]? | select(.condition.event.event == $e) | .targets[]?]')
|
||||||
|
has=$(printf '%s' "$current" | jq --arg t "$target_id" 'index($t) != null')
|
||||||
|
if [ "$MODE" = "apply" ]; then
|
||||||
|
if [ "$has" = "true" ]; then log " $ev: без изменений"; n_same=$((n_same + 1)); continue; fi
|
||||||
|
targets=$(printf '%s' "$current" | jq -c --arg t "$target_id" '. + [$t]')
|
||||||
|
if [ "$current" = "[]" ]; then status="создано"; else status="дополнено (были другие targets)"; fi
|
||||||
|
else
|
||||||
|
if [ "$has" != "true" ]; then log " $ev: без изменений"; n_same=$((n_same + 1)); continue; fi
|
||||||
|
targets=$(printf '%s' "$current" | jq -c --arg t "$target_id" 'map(select(. != $t))')
|
||||||
|
status="target снят"
|
||||||
|
fi
|
||||||
|
body=$(jq -nc --arg e "$ev" --argjson t "$targets" '{condition: {event: {event: $e}}, targets: $t}')
|
||||||
|
if api PUT /v2/actions/executions "$body" >/dev/null; then
|
||||||
|
log " $ev: $status"
|
||||||
|
case "$status" in
|
||||||
|
создано) n_created=$((n_created + 1)) ;;
|
||||||
|
дополнено*) n_extended=$((n_extended + 1)) ;;
|
||||||
|
*) n_removed=$((n_removed + 1)) ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
log " $ev: ошибка записи"; n_err=$((n_err + 1)); rc=1
|
||||||
|
fi
|
||||||
|
done <"$TMP/events"
|
||||||
|
|
||||||
|
if [ "$MODE" = "remove" ] && [ "$rc" -eq 0 ]; then
|
||||||
|
api DELETE "/v2/actions/targets/$target_id" >/dev/null && log "Target $TARGET_NAME удалён, id=$target_id" || rc=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Итог ($MODE): target=$target_id, событий=$(wc -l <"$TMP/events"), создано=$n_created, дополнено=$n_extended, снято=$n_removed, без изменений=$n_same, ошибок=$n_err"
|
||||||
|
exit "$rc"
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
# Один раз (и после каждой правки скрипта/списка событий) настраивает Target и Execution'ы в Zitadel.
|
||||||
|
# Spec Job'а неизменяем: Flux пересоздаёт его (force). После ttlSecondsAfterFinished Job удаляется,
|
||||||
|
# и Flux создаёт его заново при следующей сверке — скрипт идемпотентен, повторный запуск ничего не меняет.
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: idp-actions-bootstrap
|
||||||
|
annotations:
|
||||||
|
kustomize.toolkit.fluxcd.io/force: enabled
|
||||||
|
spec:
|
||||||
|
backoffLimit: 3
|
||||||
|
ttlSecondsAfterFinished: 86400
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
security-audit/job: idp-actions-bootstrap
|
||||||
|
# В mesh: запросы к Zitadel идут с mTLS (п.30). istio-proxy завершает сам скрипт (quitquitquit).
|
||||||
|
sidecar.istio.io/inject: "true"
|
||||||
|
annotations:
|
||||||
|
sidecar.istio.io/inject: "true"
|
||||||
|
proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}'
|
||||||
|
traffic.sidecar.istio.io/excludeOutboundPorts: "8200" # Vault мимо istio-proxy (паттерн контура)
|
||||||
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-init-first: "true"
|
||||||
|
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||||
|
vault.hashicorp.com/agent-requests-cpu: "25m"
|
||||||
|
vault.hashicorp.com/agent-limits-cpu: "100m"
|
||||||
|
vault.hashicorp.com/auth-path: auth/kubernetes
|
||||||
|
vault.hashicorp.com/role: security-audit-bootstrap
|
||||||
|
vault.hashicorp.com/agent-inject-secret-zitadel-pat: secrets/data/security-audit/zitadel-bootstrap
|
||||||
|
vault.hashicorp.com/agent-inject-template-zitadel-pat: |-
|
||||||
|
{{- with secret "secrets/data/security-audit/zitadel-bootstrap" -}}
|
||||||
|
{{ .Data.data.pat }}
|
||||||
|
{{- end -}}
|
||||||
|
spec:
|
||||||
|
serviceAccountName: idp-actions-bootstrap
|
||||||
|
restartPolicy: Never
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1001
|
||||||
|
runAsGroup: 1001
|
||||||
|
seccompProfile: {type: RuntimeDefault}
|
||||||
|
initContainers:
|
||||||
|
# В registry контура нет образа с curl и jq вместе: jq (1.6, Debian 12) берётся из образа kubectl,
|
||||||
|
# который уже использует setup-Job Zitadel, curl — из образа kafka (тоже Debian 12).
|
||||||
|
# TODO(verify): заменить на один toolbox-образ (generic/base-image), когда он появится.
|
||||||
|
- name: tools
|
||||||
|
image: cr.yandex/crp3ccidau046kdj8g9q/contour/zitadel/kubectl:1.33.4
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- cp -L /usr/bin/jq /lib/x86_64-linux-gnu/libjq.so.1 /lib/x86_64-linux-gnu/libonig.so.5 /tools/
|
||||||
|
securityContext: &container-security
|
||||||
|
runAsNonRoot: true
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
resources: &resources
|
||||||
|
requests: {cpu: 10m, memory: 32Mi}
|
||||||
|
limits: {cpu: 200m, memory: 128Mi}
|
||||||
|
volumeMounts:
|
||||||
|
- {name: tools, mountPath: /tools}
|
||||||
|
containers:
|
||||||
|
- name: bootstrap
|
||||||
|
image: cr.yandex/crp3ccidau046kdj8g9q/contour/kafka-nn/kafka:4.0.0-debian-12-r10
|
||||||
|
command: ["sh", "/scripts/idp-actions-bootstrap.sh"]
|
||||||
|
env:
|
||||||
|
# ZITADEL_API и ZITADEL_HOST задаёт оверлей контура.
|
||||||
|
- {name: PATH, value: "/tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}
|
||||||
|
- {name: LD_LIBRARY_PATH, value: /tools}
|
||||||
|
- {name: EVENTS_FILE, value: /config/events.txt}
|
||||||
|
- {name: PAT_FILE, value: /vault/secrets/zitadel-pat}
|
||||||
|
- {name: ISTIO_QUIT_ON_EXIT, value: "true"}
|
||||||
|
- {name: TMPDIR, value: /tmp}
|
||||||
|
securityContext: *container-security
|
||||||
|
resources: *resources
|
||||||
|
volumeMounts:
|
||||||
|
- {name: tools, mountPath: /tools, readOnly: true}
|
||||||
|
- {name: script, mountPath: /scripts, readOnly: true}
|
||||||
|
- {name: events, mountPath: /config, readOnly: true}
|
||||||
|
- {name: tmp, mountPath: /tmp}
|
||||||
|
volumes:
|
||||||
|
- {name: tools, emptyDir: {sizeLimit: 16Mi}}
|
||||||
|
- {name: tmp, emptyDir: {medium: Memory, sizeLimit: 16Mi}} # заголовки с PAT — только в памяти
|
||||||
|
- name: script
|
||||||
|
configMap: {name: idp-actions-bootstrap-script}
|
||||||
|
- name: events
|
||||||
|
configMap: {name: idp-actions-bootstrap-events}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
# Настройка Zitadel Actions v2 (Target + Execution'ы) для потока событий IdP.
|
||||||
|
# Скрипт и список событий — через configMapGenerator: хеш в имени ConfigMap меняет spec Job'а,
|
||||||
|
# и Flux (kustomize.toolkit.fluxcd.io/force) пересоздаёт неизменяемый Job при правке любого из них.
|
||||||
|
# namespace задаёт base (security-audit).
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- serviceaccount.yaml
|
||||||
|
- networkpolicy.yaml
|
||||||
|
- job.yaml
|
||||||
|
# ${...} в скрипте — переменные sh: в контурах с postBuild.substitute Flux не должен их подставлять.
|
||||||
|
generatorOptions:
|
||||||
|
annotations:
|
||||||
|
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||||
|
configMapGenerator:
|
||||||
|
- name: idp-actions-bootstrap-script
|
||||||
|
files:
|
||||||
|
- files/idp-actions-bootstrap.sh
|
||||||
|
- name: idp-actions-bootstrap-events
|
||||||
|
files:
|
||||||
|
- files/events.txt
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
# Дополняет общую audit-collector (podSelector: {} — действует и на под Job'а: DNS, Vault, Kafka).
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: idp-actions-bootstrap
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
security-audit/job: idp-actions-bootstrap
|
||||||
|
policyTypes: [Ingress, Egress]
|
||||||
|
ingress:
|
||||||
|
- ports:
|
||||||
|
- {port: 15021, protocol: TCP} # readiness istio-proxy (kubelet)
|
||||||
|
egress:
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: zitadel}
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: idp-contour
|
||||||
|
app.kubernetes.io/component: start
|
||||||
|
ports:
|
||||||
|
- {port: 8080, protocol: TCP}
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: istio-system}
|
||||||
|
podSelector:
|
||||||
|
matchLabels: {app: istiod}
|
||||||
|
ports:
|
||||||
|
- {port: 15012, protocol: TCP} # xDS и сертификаты istio-proxy
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
# = bound SA роли Vault security-audit-bootstrap. Доступ к Kubernetes API не нужен.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: idp-actions-bootstrap
|
||||||
11
infrastructure/security-audit/base/kustomization.yaml
Normal file
11
infrastructure/security-audit/base/kustomization.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: security-audit
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- networkpolicy.yaml
|
||||||
|
- networkpolicy-access-collector.yaml
|
||||||
|
- authorizationpolicy-access-collector.yaml
|
||||||
|
- helmrelease-collector.yaml
|
||||||
|
- helmrelease-access-collector.yaml
|
||||||
|
- idp-actions-bootstrap
|
||||||
12
infrastructure/security-audit/base/namespace.yaml
Normal file
12
infrastructure/security-audit/base/namespace.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: security-audit
|
||||||
|
labels:
|
||||||
|
# hostPath (/var/log/audit, чекпоинты) запрещён профилем baseline — нужен privileged.
|
||||||
|
pod-security.kubernetes.io/enforce: privileged
|
||||||
|
pod-security.kubernetes.io/warn: baseline
|
||||||
|
pod-security.kubernetes.io/audit: baseline
|
||||||
|
# Без istio-injection: инъекция только по метке пода sidecar.istio.io/inject=true
|
||||||
|
# (webhook object.sidecar-injector.istio.io). Метка disabled отключила бы и её.
|
||||||
|
# Sidecar получает access-collector (п.30); audit-collector — нет.
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
# Дополняет общую audit-collector (DNS, Kafka, Vault, 13133): политики складываются.
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: access-collector
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
security-audit/collector: access
|
||||||
|
policyTypes: [Ingress, Egress]
|
||||||
|
ingress:
|
||||||
|
# OTLP gRPC только от ingress-шлюза. Селектор шлюза — заглушка, контур задаёт его патчем оверлея;
|
||||||
|
# без патча не совпадёт ни с одним подом (закрыто по умолчанию).
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: istio-system}
|
||||||
|
podSelector:
|
||||||
|
matchLabels: {security-audit/gateway: unset}
|
||||||
|
ports:
|
||||||
|
- {port: 4317, protocol: TCP}
|
||||||
|
# istio-proxy: 15020 — переписанные probe'ы приложения (kubelet), 15021 — readiness самого sidecar.
|
||||||
|
- ports:
|
||||||
|
- {port: 15020, protocol: TCP}
|
||||||
|
- {port: 15021, protocol: TCP}
|
||||||
|
# События Zitadel (webhookevent) — только от подов Zitadel. Метки — чарт idp-contour, одинаковы во всех контурах.
|
||||||
|
# Правило добавлено последним: патч оверлея ссылается на /spec/ingress/0.
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: zitadel}
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: idp-contour
|
||||||
|
app.kubernetes.io/component: start
|
||||||
|
ports:
|
||||||
|
- {port: 8088, protocol: TCP}
|
||||||
|
# Журнал аудита Vault (audit-shipper в подах StatefulSet). Метки — чарт vault-contour.
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: vault}
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: vault-contour
|
||||||
|
component: server
|
||||||
|
ports:
|
||||||
|
- {port: 4319, protocol: TCP}
|
||||||
|
egress:
|
||||||
|
# xDS и сертификаты istio-proxy от istiod.
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: istio-system}
|
||||||
|
podSelector:
|
||||||
|
matchLabels: {app: istiod}
|
||||||
|
ports:
|
||||||
|
- {port: 15012, protocol: TCP}
|
||||||
32
infrastructure/security-audit/base/networkpolicy.yaml
Normal file
32
infrastructure/security-audit/base/networkpolicy.yaml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Коллектору нужны только DNS, Kafka и Vault (агент-инжектор). Входящие — только health-check kubelet'а.
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: audit-collector
|
||||||
|
spec:
|
||||||
|
podSelector: {}
|
||||||
|
policyTypes: [Ingress, Egress]
|
||||||
|
ingress:
|
||||||
|
- ports:
|
||||||
|
- {port: 13133, protocol: TCP} # health_check (liveness/readiness)
|
||||||
|
egress:
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: kube-system}
|
||||||
|
podSelector:
|
||||||
|
matchLabels: {k8s-app: kube-dns}
|
||||||
|
- ipBlock: {cidr: 169.254.25.10/32} # nodelocaldns kubespray; TODO(verify): enable_nodelocaldns в контуре
|
||||||
|
ports:
|
||||||
|
- {port: 53, protocol: UDP}
|
||||||
|
- {port: 53, protocol: TCP}
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: kafka}
|
||||||
|
ports:
|
||||||
|
# client-листенер SASL_SSL: bootstrap kafka-kafka-contour и advertised-адреса подов controller-headless
|
||||||
|
- {port: 9092, protocol: TCP}
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels: {kubernetes.io/metadata.name: vault}
|
||||||
|
ports:
|
||||||
|
- {port: 8200, protocol: TCP} # vault-vault-contour.vault.svc:8200 (AGENT_INJECT_VAULT_ADDR)
|
||||||
4
infrastructure/security-audit/kustomization.yaml
Normal file
4
infrastructure/security-audit/kustomization.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- base
|
||||||
11
infrastructure/security-audit/mesh/kustomization.yaml
Normal file
11
infrastructure/security-audit/mesh/kustomization.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Ресурсы шлюза Istio. Намеренно без поля namespace: base модуля переписал бы его на security-audit,
|
||||||
|
# а этим ресурсам нужно оставаться в istio-system. Подключается из оверлея контура как resource,
|
||||||
|
# селектор шлюза (заглушка security-audit/gateway: unset) патчится там.
|
||||||
|
#
|
||||||
|
# RequestAuthentication (пользователь в журнале — x-sarex-audit-sub/org) здесь пока нет: не подтверждены все
|
||||||
|
# издатели JWT на шлюзе, а неизвестный издатель даёт 401. См. README, «Журнал доступа шлюза».
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- strip-audit-headers.yaml
|
||||||
|
- telemetry.yaml
|
||||||
32
infrastructure/security-audit/mesh/strip-audit-headers.yaml
Normal file
32
infrastructure/security-audit/mesh/strip-audit-headers.yaml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Удаляет входящие x-sarex-audit-*: клиент не должен подставлять себе пользователя в журнал доступа.
|
||||||
|
# Стоит первым HTTP-фильтром (INSERT_FIRST) — раньше jwt_authn, который заполняет эти заголовки из claims.
|
||||||
|
apiVersion: networking.istio.io/v1alpha3
|
||||||
|
kind: EnvoyFilter
|
||||||
|
metadata:
|
||||||
|
name: strip-audit-headers
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
workloadSelector:
|
||||||
|
labels:
|
||||||
|
security-audit/gateway: unset
|
||||||
|
configPatches:
|
||||||
|
- applyTo: HTTP_FILTER
|
||||||
|
match:
|
||||||
|
context: GATEWAY
|
||||||
|
listener:
|
||||||
|
filterChain:
|
||||||
|
filter:
|
||||||
|
name: envoy.filters.network.http_connection_manager
|
||||||
|
patch:
|
||||||
|
operation: INSERT_FIRST
|
||||||
|
value:
|
||||||
|
name: security-audit.strip-audit-headers
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
|
||||||
|
default_source_code:
|
||||||
|
inline_string: |
|
||||||
|
function envoy_on_request(request_handle)
|
||||||
|
local headers = request_handle:headers()
|
||||||
|
headers:remove("x-sarex-audit-sub")
|
||||||
|
headers:remove("x-sarex-audit-org")
|
||||||
|
end
|
||||||
17
infrastructure/security-audit/mesh/telemetry.yaml
Normal file
17
infrastructure/security-audit/mesh/telemetry.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Включает провайдер audit-access (meshConfig.extensionProviders в istiod) на ingress-шлюзе.
|
||||||
|
# Провайдер должен быть раскатан раньше: иначе istiod не найдёт его и журнал не включится.
|
||||||
|
apiVersion: telemetry.istio.io/v1
|
||||||
|
kind: Telemetry
|
||||||
|
metadata:
|
||||||
|
name: gateway-audit-access
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
security-audit/gateway: unset
|
||||||
|
accessLogging:
|
||||||
|
- providers:
|
||||||
|
- name: audit-access
|
||||||
|
# Только health-check'и. Статику не исключаем — решение за ИБ.
|
||||||
|
filter:
|
||||||
|
expression: '!(request.url_path.startsWith("/healthz") || request.url_path.startsWith("/ready"))'
|
||||||
17
infrastructure/security-audit/uralkal/access-collector.yaml
Normal file
17
infrastructure/security-audit/uralkal/access-collector.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: access-collector
|
||||||
|
namespace: security-audit
|
||||||
|
spec:
|
||||||
|
dependsOn:
|
||||||
|
- name: kafka
|
||||||
|
namespace: kafka
|
||||||
|
- name: vault
|
||||||
|
namespace: vault
|
||||||
|
values:
|
||||||
|
extraEnvs:
|
||||||
|
- name: CONTOUR
|
||||||
|
value: uralkal
|
||||||
|
- name: KAFKA_BROKERS
|
||||||
|
value: kafka-kafka-contour.kafka.svc.cluster.local:9092
|
||||||
18
infrastructure/security-audit/uralkal/audit-collector.yaml
Normal file
18
infrastructure/security-audit/uralkal/audit-collector.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: audit-collector
|
||||||
|
namespace: security-audit
|
||||||
|
spec:
|
||||||
|
dependsOn:
|
||||||
|
- name: kafka
|
||||||
|
namespace: kafka
|
||||||
|
- name: vault
|
||||||
|
namespace: vault
|
||||||
|
values:
|
||||||
|
extraEnvs:
|
||||||
|
- name: CONTOUR
|
||||||
|
value: uralkal
|
||||||
|
# Service bitnami-чарта kafka-contour (релиз kafka); тот же адрес у приложений apps/*/uralkal.
|
||||||
|
- name: KAFKA_BROKERS
|
||||||
|
value: kafka-kafka-contour.kafka.svc.cluster.local:9092
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user