Использование Credentials Service для паролей и ключей API
Введение в Credentials Service
> ⚠️ Внимание: Никогда не храните пароли, ключи API или любые другие секретные данные в виде открытого текста в своих потоках. Это создает серьезную уязвимость в системе безопасности.
При создании любых систем автоматизации, особенно тех, которые взаимодействуют с внешними сервисами, облачными платформами или защищенным оборудованием, мы неизбежно сталкиваемся с необходимостью управления учетными данными. К ним относятся:
- Пароли для доступа к базам данных (MySQL).
- Имена пользователей и пароли для подключения к MQTT-брокерам.
- Ключи API для взаимодействия с погодными сервисами, системами уведомлений (Telegram, Pushover) или облачными платформами.
- Токены аутентификации (например, Bearer-токены) для доступа к RESTful API.
Самый распространенный, но и самый опасный анти-паттерн — это хранение этих данных в виде обычного текста непосредственно в логике потока.
Проблема хранения секретных данных в открытом виде
Представьте себе поток, который отправляет данные в защищенный API. Начинающий инженер может решить эту задачу, поместив API-ключ прямо в узел `function` или `change`:
// НЕПРАВИЛЬНО: хранение ключа в коде
const API_KEY = "super-secret-key-12345";
msg.headers = {
"Authorization": "Bearer " + API_KEY
};
// ... остальная логика
return msg;
Или, что еще хуже, в узел `inject`, чтобы "удобно" запускать поток с нужными параметрами. Почему это представляет серьезную угрозу?
Решение: Node-RED Credentials Service
Для решения этой фундаментальной проблемы безопасности в Node-RED встроен специальный механизм — Credentials Service. Это стандартная служба, предназначенная для безопасного хранения и управления секретными данными.
Принцип ее работы основан на разделении конфигурации и учетных данных:
- `flows.json`: Этот файл, как и прежде, хранит всю структуру ваших потоков: узлы, их связи, настройки, но не содержит секретных данных. Вместо пароля или ключа API он хранит только ссылку (уникальный идентификатор) на учетные данные.
- `flows_cred.json`: Это отдельный, специальный файл, в котором и хранятся все секретные данные. Записи в этом файле сопоставляются с узлами в `flows.json` по их внутренним идентификаторам. Содержимое этого файла по умолчанию обфусцировано (запутано) и, что самое важное, может быть надежно зашифровано.
Таким образом, вы можете безопасно делиться файлом `flows.json`, не опасаясь утечки паролей. Для полноценной работы системы на другом контроллере потребуется перенести оба файла: `flows.json` и `flows_cred.json`, а также ключ шифрования, если он был задан. Этот механизм обеспечивает изоляцию секретов, упрощает их администрирование и значительно повышает общий уровень безопасности системы.
---
Определение и регистрация Credentials в нодах
Вы наверняка замечали, что при настройке таких узлов, как `mqtt in`, `http request` или `influxdb out`, поля для ввода пароля или токена ведут себя особенно: введенные символы отображаются как точки, а после развертывания (Deploy) поле выглядит пустым. Это прямое проявление работы Credentials Service. Нода "знает", что определенные поля содержат секретную информацию и должны обрабатываться особым образом.
> 💡 Подсказка: Для разработчиков собственных нод: правильное определение credentials на уровне регистрации ноды — ключевой аспект создания безопасных и удобных в использовании компонентов. Понимание этого механизма полезно и для инженеров-интеграторов, так как позволяет глубже понять принципы работы платформы.
Это поведение не является магией, а определяется разработчиком узла на этапе его создания. Рассмотрим, как это устроено на примере стандартного узла для настройки MQTT-брокера (`mqtt-broker`). Каждый узел в Node-RED состоит из как минимум двух файлов:
Определение Credentials в `.js`-файле узла
В серверном `.js`-файле узла, при его регистрации в системе Node-RED с помощью функции `RED.nodes.registerType`, разработчик добавляет специальный объект `credentials`. Этот объект перечисляет все свойства, которые должны считаться секретными.
// Фрагмент .js файла узла конфигурации MQTT брокера
module.exports = function(RED) {
function MQTTBrokerNode(n) {
RED.nodes.createNode(this, n);
// ... логика узла
// this.credentials содержит расшифрованные значения
// например, this.credentials.user и this.credentials.password
}
RED.nodes.registerType("mqtt-broker", MQTTBrokerNode, {
credentials: {
user: {type: "text"},
password: {type: "password"}
}
});
}
В этом примере объект `credentials` указывает, что узел `mqtt-broker` имеет два секретных поля: `user` и `password`.
- `user: {type: "text"}`: определяет учетное данное с именем `user`. Тип `text` означает, что в редакторе это будет обычное текстовое поле, но его значение будет сохранено в `flows_cred.json`.
- `password: {type: "password"}`: определяет учетное данное с именем `password`. Тип `password` дополнительно сообщает редактору, что это поле для ввода пароля, и его содержимое следует маскировать (отображать точками).
Когда вы вводите данные в эти поля в редакторе, Node-RED не сохраняет их в основном потоке `flows.json`, а помещает в `flows_cred.json`, связывая с уникальным ID узла.
Отображение полей в `.html`-файле узла
Соответствующий `.html`-файл должен содержать HTML-разметку для этих полей. Чтобы редактор Node-RED "понял", что эти `input`-поля соответствуют определенным в `.js`-файле credentials, их `id` должен иметь префикс `node-config-input-`.
Обратите внимание на два ключевых момента:
Таким образом, связка между определением в `RED.nodes.registerType` и HTML-разметкой позволяет Node-RED бесшовно и безопасно управлять секретными данными, предоставляя пользователю интуитивно понятный интерфейс.
---
Практика: Использование Credentials в ноде Function
Стандартные ноды, как мы увидели, имеют предопределенные поля для учетных данных. Но что делать, если вам нужно использовать API-ключ или токен в собственной логике внутри узла `function`? К счастью, узел `function` также имеет встроенную поддержку Credentials Service.
Это позволяет вам определять собственные именованные секреты и безопасно получать к ним доступ в коде, не "засвечивая" их в теле функции или в объекте `msg`.
Добавление Credentials в узел Function
Чтобы добавить секретное поле в узел `function`, выполните следующие шаги:
Давайте создадим два поля для гипотетического API:
- Свойство: `apiKey`, Тип: `password`
- Свойство: `apiUser`, Тип: `text`
После добавления этих свойств в интерфейсе редактора появятся соответствующие поля для ввода. Введите в них произвольные значения (например, `mySecretKey123` и `admin`). Нажмите "Готово" (Done), а затем "Развернуть" (Deploy).
Доступ к Credentials в коде
Теперь, когда учетные данные сохранены, к ним можно получить доступ внутри кода узла `function` через специальный объект `this.credentials`. Это свойство узла, которое Node-RED автоматически заполняет расшифрованными значениями при инициализации потока.
Давайте напишем код, который формирует заголовки для HTTP-запроса, используя сохраненные секреты.
// Код для вкладки "Функция" (Function)
// Получаем доступ к ранее определенным credentials
// Важно: this.credentials доступен только в этом контексте
// и не передается в объекте msg.
const apiKey = this.credentials.apiKey;
const apiUser = this.credentials.apiUser;
// Проверяем, что credentials были установлены
if (!apiKey || !apiUser) {
node.error("API Key или API User не настроены! Проверьте вкладку 'Настройка' в узле.", msg);
node.status({fill:"red", shape:"dot", text:"Ошибка: нет credentials"});
return null; // Прерываем выполнение потока
}
// Формируем структуру для следующего узла (например, http request)
// مطابق قرارداد сообщения, которое мы рассмотрели ранее
msg.headers = {
'Content-Type': 'application/json',
'X-Auth-User': apiUser,
'Authorization': 'Bearer ' + apiKey
};
msg.payload = {
"source": "HI-Controller-01",
"value": 25.5,
"ts": Date.now()
};
node.status({fill:"green", shape:"dot", text:"Credentials OK"});
return msg;
Проверка безопасности
Самое главное — убедиться, что наши секреты не утекают.
Вы получите примерно такой JSON:
[
{
"id": "a1b2c3d4.e5f6g7",
"type": "function",
"z": "...",
"name": "Формирование API запроса",
"func": "// Код для вкладки \"Функция\" (Function)\n// ...",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"credentials": {
"apiKey": "",
"apiUser": ""
},
"x": 350,
"y": 200,
"wires": [["h8i9j0k1.l2m3n4"]]
}
]
Обратите внимание на объект `"credentials"`. Имена свойств (`apiKey`, `apiUser`) присутствуют, но их значения пусты. Сами значения надежно хранятся в зашифрованном файле `flows_cred.json` и не попадают в экспорт. Это доказывает, что данный метод является безопасным для хранения секретной информации.
---
Активация шифрования: настройка `credentialSecret`
По умолчанию Node-RED не шифрует, а лишь обфусцирует (делает нечитаемым) содержимое файла `flows_cred.json`. Это защищает от случайного просмотра, но не является надежной криптографической защитой. Для активации полноценного AES-шифрования необходимо задать секретный ключ в главном конфигурационном файле Node-RED.
> ⚠️ Внимание: Потеря ключа `credentialSecret` приведет к невозможности расшифровать существующие credentials. Всегда создавайте резервные копии `settings.js` и `flows_cred.json` после его установки или изменения.
Файл, управляющий глобальными настройками Node-RED на вашем контроллере HI, называется `settings.js`.
Поиск и редактирование `settings.js`
На стандартной Linux-системе, которую использует контроллер HI, этот файл обычно располагается в домашней директории пользователя, от имени которого запущен Node-RED, в подпапке `.node-red`.
- Полный путь: `/home/user/.node-red/settings.js` (замените `user` на имя вашего пользователя).
Для редактирования файла подключитесь к контроллеру по SSH и используйте любой текстовый редактор, например `nano`:
nano ~/.node-red/settings.js
Пролистайте файл вниз до секции, касающейся безопасности потоков (`Flow security`). Вы найдете закомментированную строку:
// ... другие настройки
module.exports = {
// ...
/* The following property can be used to enable básico-auth for the editor /
//adminAuth: {
// ...
/* The following property can be used to encrypt credentials in storage /
//credentialSecret: "a secret key",
// ...
}
Ваша задача — раскомментировать строку `credentialSecret` и заменить значение `"a secret key"` на ваш собственный, надежный и уникальный ключ.
Генерация надежного ключа
Использовать простую фразу в качестве ключа небезопасно. Ключ должен быть длинной, случайной строкой символов. На контроллере HI, работающем на Debian, можно легко сгенерировать такой ключ с помощью стандартных утилит.
Способ 1: Использование `openssl` (рекомендуемый)Эта команда сгенерирует 24 случайных байта и закодирует их в формат Base64, что идеально подходит для ключа.
openssl rand -base64 24
Пример вывода: `wJ/k9sL+r7dE6gZ5hXNqR8mF3tYjCgO+`
Способ 2: Использование `/dev/urandom`Эта команда читает случайные данные из системного генератора, фильтрует их и кодирует в Base64.
head -c 24 /dev/urandom | base64
Пример вывода: `bVpGk8tQjLzR7sYf+NqWpXzHgCjE/wA=`
Выберите любой из этих способов, сгенерируйте ключ, скопируйте его и вставьте в `settings.js`:
// ...
/* The following property can be used to encrypt credentials in storage /
credentialSecret: "wJ/k9sL+r7dE6gZ5hXNqR8mF3tYjCgO+",
// ...
Сохраните файл (в `nano` это `Ctrl+O`, `Enter`, затем `Ctrl+X`).
Перезапуск Node-RED и последствия
После изменения `settings.js` необходимо перезапустить службу Node-RED, чтобы изменения вступили в силу.
# Команда может отличаться в зависимости от конфигурации системы
sudo systemctl restart nodered
> ℹ️ Информация: Если вы установили `credentialSecret` на системе, где уже были сохранены какие-либо credentials, они станут нечитаемыми. Node-RED попытается расшифровать их старым методом (обфускация) и потерпит неудачу. Вам придется заново ввести все пароли и ключи API в соответствующих узлах. После этого они будут сохранены в `flows_cred.json` уже в зашифрованном виде с использованием вашего нового ключа. То же самое произойдет, если вы когда-либо измените или потеряете `credentialSecret`.
Именно поэтому критически важно сделать резервную копию файла `settings.js` (с вашим ключом) и файла `flows_cred.json` и хранить их в надежном месте. Без ключа зашифрованные данные превращаются в бесполезный набор символов.
---
Пример: Защита подключения к MQTT-брокеру
Давайте применим полученные знания на практике и защитим подключение к MQTT-брокеру, который требует аутентификации.
Сценарий: Наш контроллер HI должен публиковать данные в топик на защищенном MQTT-брокере. У нас есть следующие учетные данные:- Имя пользователя: `hi_controller`
- Пароль: `P@$$w0rd_mqtt_!2024`
Пошаговая настройка
На этом настройка завершена. Узел будет использовать сохраненные учетные данные для подключения к брокеру.
Анализ файлов конфигурации
Теперь самое интересное. Посмотрим, что произошло "под капотом". Мы проанализируем (упрощенно) содержимое файлов `flows.json` и `flows_cred.json`.
Файл `flows.json`:В этом файле вы найдете два объекта: один для самого узла `mqtt out` и один для узла конфигурации `mqtt-broker`, который мы только что создали.
[
{
"id": "abc12345.def678",
"type": "mqtt out",
"z": "...",
"name": "Отправка телеметрии",
"topic": "telemetry/office/temp",
"qos": "1",
"retain": "false",
"broker": "xyz98765.uvw432", // <-- Ссылка на узел конфигурации
"x": 450,
"y": 300,
"wires": []
},
{
"id": "xyz98765.uvw432", // <-- Уникальный ID узла конфигурации
"type": "mqtt-broker",
"name": "Secure Broker",
"broker": "192.168.1.10",
"port": "1883",
"clientid": "HI-Controller-Main"
// ЗАМЕТЬТЕ: здесь нет ни имени пользователя, ни пароля!
}
]
Как видите, `flows.json` содержит все настройки, кроме секретных. Узел `mqtt out` (`id: abc12345...`) просто ссылается на узел `mqtt-broker` (`id: xyz98765...`) через поле `"broker"`.
Файл `flows_cred.json`:Если вы откроете этот файл, его содержимое будет выглядеть примерно так (если `credentialSecret` был настроен):
{
"xyz98765.uvw432": {
"user": "hi_controller",
"password": "b1a2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2g3h4i5j6k7l8m9n0o1p2q3r4s5t6u7v8w9x0y1z2a3b4c5d6e7f8g9h0i1j2k3l4m5n6o7p8"
},
"$": "a1a4b6b8c9c0d3d5e2e8f1f5g8g4h7h6i1i9j3j2k6k7l5l0m9m4n8n2o6o3p1p7q..."
}
- `"xyz98765.uvw432"`: Это ключ объекта, который является ID узла конфигурации из `flows.json`.
- `"user": "hi_controller"`: Имя пользователя сохранено в открытом виде (потому что мы определили его как тип `text`).
- `"password": "b1a2c3..."`: Пароль превратился в длинную, зашифрованную строку.
- `"$"`: Это специальное свойство, содержащее хэш, который Node-RED использует для проверки целостности файла.
Этот пример наглядно демонстрирует принцип разделения: `flows.json` определяет структуру, а `flows_cred.json` — секреты, привязанные к этой структуре через ID узлов.
---
Итоги и лучшие практики
> 🔗 Связанный материал: Для повышения уровня организации проекта, рассмотрите возможность инкапсуляции логики работы с API, включая получение credentials, в Subflow. Это позволит создать переиспользуемый и легко настраиваемый компонент. Подробнее о создании подпотоков мы говорили в уроке `COURSE-06-M06-L03`.
В этом уроке мы рассмотрели один из самых важных аспектов создания безопасных и профессиональных систем на базе Node-RED — управление секретными данными с помощью Credentials Service. Неправильное хранение паролей и ключей является одной из самых серьезных и распространенных уязвимостей в системах автоматизации.
Давайте закрепим ключевые принципы и лучшие практики:
Что дальше
Освоив безопасное хранение учетных данных, мы готовы перейти к следующему важному аспекту организации крупных проектов — версионированию и управлению изменениями. В следующем уроке мы рассмотрим, как интегрировать проекты Node-RED с системой контроля версий Git, что позволит отслеживать все изменения, откатываться к предыдущим версиям и организовывать совместную работу над проектом.