Привет, Хабр. Я Игорь Юрченко, backend-разработчик Сбера. В предыдущей статье я описал автоматизацию развёртывания потоков NiFi, а сейчас расскажу о дальнейшем развитии этого подхода для интеграции с корпоративной системой управления секретами — SecMan. Буду использовать терминологию предыдущей статьи.

Потоки NiFi для работы требуют указания секретов в свойствах компонентов: паролей БД, SSL-сертификатов, учётные данные HTTP и так далее. Хранить их в виде переменных в NiFi версий 1.х нельзя (нет шифрования, в отличие от sensitive-свойств компонентов), а в NiFi версий 2.х и переменных больше нет. В метаданных (бизнес-параметрах, которые подставляются в свойства компонентов при развёртывании шаблона потока) держать эту информацию тоже нельзя, потому что метаданные хранятся в Git без шифрования. Кроме того, на стендах пароли разные, а файл метаданных — один для всех стендов, поэтому после развёртывания множества потоков секреты нужно вводить в каждом из них вручную. Если секрет имеет ограниченный срок действия, то и обновлять его нужно периодически во всех зависимых потоках. Общий подход к решению этих проблем — в Git хранить только шаблоны и метаданные потока, как описано в предыдущей статье, а секреты автоматически подставлять из SecMan в момент развёртывания на конкретный стенд.

NiFi позволяет вводить значения в свойства компонентов ссылкой вида #{parameter} на parameter context — набор пар «ключ-значение», связанную с потоком сущность. Значение может быть зашифровано (помечено как sensitive, у свойств компонентов есть тот же атрибут). Parameter context создаётся и заполняется другим компонентом — parameter provider. В его свойствах настраивается способ получения секретов из внешнего сервиса, по кнопке «Fetch parameters» он сохраняет все полученные секреты в parameter context. Отношения: один provider — один context — один или множество потоков (в этой статье описана связь context только с одним потоком).

#{parameter} в свойстве процессора:

Связь потока с context-ом:

Context с sensitive-значениями:

Свойства провайдера (на примере штатного FileParameterProvider):

Кнопка «Fetch parameters»:

Форма создания context-а после нажатия fetch:

Задача подстановки секретов в поток в момент развёртывания сводится к программному (через REST API NiFi) созданию provider-а, нажатию его кнопки «Fetch parameters» и связыванию созданного context с развёрнутым потоком. При этом в шаблоне потока в свойствах нужных компонентов уже должны быть прописаны ссылки на ключи из context (то есть каждый шаблон умеет работать со строго определёнными ключами секретов). Можно использовать любой штатный provider, который поддерживает интеграцию с выбранным хранилищем секретов, а в этой статье описано создание custom provider-а для интеграции с корпоративной системой управления секретами — SecMan.

SecMan с точки зрения пользователя — просто REST API, отдающий секреты. Внутри есть разные «движки» с разными форматами запросов и ответов. Например, для движка KV (возвращает key-value — имена и значения секретов):

curl -k https://host.ru/path/to/secret/engine/kv -H "X-Vault-Namespace: some_namespace" -H "X-Vault-Token: some_token"

{
  "data": {
    "CLICKHOUSE_PASSWORD": "pass123",
    "POSTGRES_PASSWORD": "pass456",
    "SOME_TOKEN": "base64_encoded_token"
  },
}

Для получения секретов создан custom NiFi-компонент под названием SecmanParameterProvider. Это наследник класса org.apache.nifi.parameter.AbstractParameterProvider из библиотеки org.apache.nifi:nifi-api. Подобно штатным провайдерам, он имеет набор свойств, необходимых для работы, — адрес SecMan, конфигурация доступа в разные движки (пространство имён, учётные данные, путь), а также ссылку на компонент SSLContextService, предоставляющий SSL/TLS-соединение.

Свойства SecmanParameterProvider:

Свойства SSLContextService (в Truststore Filename путь до JKS с сертификатом для доступа в REST API SecMan):

Класс SecmanParameterProvider реализует метод List<ParameterGroup> fetchParameters(ConfigurationContext context) интерфейса org.apache.nifi.parameter.ParameterProvider и вызывается фреймворком NiFi при нажатии кнопки «Fetch parameters» в интерфейсе. В этом методе выполняются запросы в SecMan, а их результаты формируются в List<ParameterGroup>, который фреймворк NiFi затем превращает в parameter context.

Описание сборки проекта custom-компонента NiFi можно найти в сети. Если совсем кратко: наследование от Maven-артефакта org.apache.nifi:nifi-nar-bundles, NiFi NAR Maven Plugin, команда mvn clean install. В результате получаем файл ./[project]-nar/target/[project]-[version].nar. Копируем его в папку $NIFI_HOME/lib экземпляра NiFi, перезапускаем его и получаем возможность создавать SecmanParameterProvider как в интерфейсе, так и программно.

Дальше работает знакомый по предыдущей статье джарник nifi-deployer. Напомню его функции: на входе есть файлы шаблона потока (описания всех компонентов), подготовленного группой разработки, и файл метаданных (бизнес-параметры), подготовленный пользователем-владельцем потока; nifi-deployer соединяет их (подставляет параметры api-data в текст шаблона по placeholder-ам типа [[ parameter ]]), отправляет в Registry и NiFi, обновляет variables готового потока по метаданным. В начале этого процесса появляется дополнительный шаг — программное создание parameter provider-а в целевом NiFi. Для этого используется метод REST API NiFi POST /controller/parameter-providers. Тело запроса содержит имя и версию провайдера, а также его свойства (в общем случае в экземпляре NiFi могут существовать разные версии одного и того же провайдера с разными наборами свойств). Все эти параметры являются техническими и разнятся между стендами (как минимум, адрес и учётные данные SecMan), логично подставлять их из Jenkins pipeline.

После создания провайдера вызываем у него метод fetchParameters: POST /parameter-providers/$providerId/parameters/fetch-requests. Полученный набор имён (ключей) параметров передаём в POST /parameter-providers/$providerId/apply-parameters-requests, в этот момент в NiFi создаётся parameter context. Его имя добавляем в шаблон потока перед отправкой в Registry, так устанавливается связь потока с созданным context-ом.

Скрытый текст

Пример команды развёртывания:

java -jar nifi-deployer-0.38-SNAPSHOT.jar -i nifi.url=http://localhost:8081/nifi-api registry.url=http://localhost:18080/nifi-registry-api bucket.name=default import.version.file=rdbms-exact-json-1.24.json import.variable.file=rdbms-exact-json.json user=... password=... ssl.keystore.filename=nifi.p12 import.api.data.default=secmanSSLControllerServiceName=SecManSSLService|secmanURL=https://.../v1|secmanNamespace=namespace1|secmanRoleId=role1|secmanSecretId=secret1|secmanNamespacePlatform=namespace2|secmanRoleIdPlatform=role2|secmanSecretIdPlatform=secret2|secmanSecretPathPlatform=path/to/secrets/for/flow/name|secmanParameterProviderVersion=0.2

В параметре import.api.data.default в формате key-value передаются значения для свойств provider-а — целевая версия, адрес SecMan, учётные данные, путь в KV-движке до секретов этого потока.

Журнал развёртывания:

11.09.2026 21:28:50.921 [main] INFO deployTemplateAndMetadata2RegistryAndNiFi$ - read template from file /home/.../rdbms-exact-json-1.24.json
11.09.2026 21:28:50.938 [main] INFO deployTemplateAndMetadata2RegistryAndNiFi$ - read metadata from file /home/.../rdbms-exact-json.json
11.09.2026 21:28:51.079 [main] INFO deployTemplateAndMetadata2RegistryAndNiFi$ - parse template from file /home/.../rdbms-exact-json-1.24.json
11.09.2026 21:28:51.128 [main] INFO getParameterProviderInfo$ - use param provider template from resources for version 0.2
11.09.2026 21:28:51.129 [main] INFO ApiNiFiImport - get parameter provider rdbms-exact-json-example
11.09.2026 21:28:51.129 [main] DEBUG HttpClient - GET http://localhost:8081/nifi-api/flow/parameter-providers
11.09.2026 21:28:51.209 [main] INFO getParameterProviderInfo$ - create parameter provider rdbms-exact-json-example
11.09.2026 21:28:51.210 [main] INFO ApiNiFiImport - get controller service SecManSSLService
11.09.2026 21:28:51.210 [main] DEBUG HttpClient - GET http://localhost:8081/nifi-api/flow/controller/controller-services
11.09.2026 21:28:51.253 [main] INFO ApiNiFiImport - create parameter provider
11.09.2026 21:28:51.254 [main] DEBUG HttpClient - POST http://localhost:8081/nifi-api/controller/parameter-providers
11.09.2026 21:28:51.270 [main] INFO ApiNiFiImport - fetch parameters with provider 91bac3ba-01a0-1000-e886-4413b32df0ce
11.09.2026 21:28:51.275 [main] DEBUG HttpClient - POST http://localhost:8081/nifi-api/parameter-providers/91bac3ba-01a0-1000-e886-4413b32df0ce/parameters/fetch-requests
11.09.2026 21:28:51.790 [main] INFO ApiNiFiImport - apply parameters with provider 91bac3ba-01a0-1000-e886-4413b32df0ce, create context
11.09.2026 21:28:51.796 [main] DEBUG HttpClient - POST http://localhost:8081/nifi-api/parameter-providers/91bac3ba-01a0-1000-e886-4413b32df0ce/apply-parameters-requests
11.09.2026 21:28:52.848 [main] DEBUG HttpClient - GET http://localhost:8081/nifi-api/parameter-providers/91bac3ba-01a0-1000-e886-4413b32df0ce/apply-parameters-requests/fc3fd9a0-2595-41f0-a233-d694097bc1ed
11.09.2026 21:28:52.871 [main] DEBUG HttpClient - DELETE http://localhost:8081/nifi-api/parameter-providers/91bac3ba-01a0-1000-e886-4413b32df0ce/apply-parameters-requests/fc3fd9a0-2595-41f0-a233-d694097bc1ed
11.09.2026 21:28:52.906 [main] INFO ApiRegistry - get all flow versions from Registry
11.09.2026 21:28:52.906 [main] INFO ApiRegistry - get bucket ID for bucket name default
11.09.2026 21:28:52.907 [main] DEBUG HttpClient - GET http://localhost:18080/nifi-registry-api/buckets
11.09.2026 21:28:52.934 [main] INFO ApiRegistry - using bucket 13a3c613-105b-44ce-bc86-3ce5756800a1
11.09.2026 21:28:52.934 [main] INFO ApiRegistry - get flow ID for flow name rdbms-exact-json-example and bucketId 13a3c613-105b-44ce-bc86-3ce5756800a1
11.09.2026 21:28:52.934 [main] DEBUG HttpClient - GET http://localhost:18080/nifi-registry-api/buckets/13a3c613-105b-44ce-bc86-3ce5756800a1/flows
11.09.2026 21:28:52.954 [main] INFO ApiRegistry - create flow rdbms-exact-json-example
11.09.2026 21:28:52.954 [main] DEBUG HttpClient - POST http://localhost:18080/nifi-registry-api/buckets/13a3c613-105b-44ce-bc86-3ce5756800a1/flows
11.09.2026 21:28:53.032 [main] INFO ApiRegistry - using flow 4e46a4ff-6ac1-4a7e-bacd-de71ced5b1cf
11.09.2026 21:28:53.032 [main] DEBUG HttpClient - GET http://localhost:18080/nifi-registry-api/buckets/13a3c613-105b-44ce-bc86-3ce5756800a1/flows/4e46a4ff-6ac1-4a7e-bacd-de71ced5b1cf/versions
11.09.2026 21:28:53.058 [main] INFO importFlow2Registry$ - update flow rdbms-exact-json-example in Registry
11.09.2026 21:28:53.059 [main] DEBUG HttpClient - POST http://localhost:18080/nifi-registry-api/buckets/13a3c613-105b-44ce-bc86-3ce5756800a1/flows/4e46a4ff-6ac1-4a7e-bacd-de71ced5b1cf/versions
11.09.2026 21:28:53.136 [main] INFO importFlow2Registry$ - /home/.../rdbms-exact-json-1.24.json imported to Registry
11.09.2026 21:28:53.143 [Thread-21] INFO vci.getVersionControlInfo$ - get version control info for rdbms-exact-json-example recursively from d6c097de-018d-1000-d01a-713fa9822276
11.09.2026 21:28:53.157 [main] DEBUG HttpClient - GET http://localhost:8081/nifi-api/controller/registry-clients
11.09.2026 21:28:53.168 [main] INFO importGroupFromRegistry2NiFi$ - registry b0806a28-018d-1000-0318-a7256e10492a
11.09.2026 21:28:53.170 [main] DEBUG HttpClient - POST http://localhost:8081/nifi-api/process-groups/d6c097de-018d-1000-d01a-713fa9822276/process-groups
11.09.2026 21:28:53.415 [main] INFO importGroupFromRegistry2NiFi$ - imported http://localhost:8081/nifi?processGroupId=91bacb92-01a0-1000-65c4-38c33a31761a from Registry to NiFi

Создан parameter provider:

Создан parameter context:

Создан поток:

Поток связан с context-ом:

В первом процессоре потока в свойстве Database Password использован #{MAINTAINENCE_DB_PASSWORD} (значение sensitive-свойства замаскировано, но значение туда подставляется по этому ключу из context-а):

Таким образом, решена проблема подстановки во множество потоков стендозависимых и управляемых централизованно секретов.

Комментарии (1)


  1. aglaiadymb
    18.09.2026 18:40

    8 минут чтения ради вывода не кладите пароли в Git, забирайте их из vault при деплое