Переменные окружения являются базовым механизмом хранения конфигурационных значений и секретов в JavaScript-приложениях, работающих в Node.js и смежных средах. Они позволяют отделить чувствительные данные от исходного кода и минимизировать риск их утечки при публикации репозиториев или сборке приложения.
В Node.js переменные окружения доступны через объект
process.env. Их основное назначение — хранение значений,
которые могут отличаться между окружениями: разработка, тестирование,
продакшн.
Типичные примеры:
Использование выглядит следующим образом:
const dbUrl = process.env.DATABASE_URL;
const apiKey = process.env.API_KEY;
Несмотря на простоту, этот подход имеет ограничения:
Поэтому переменные окружения рассматриваются как минимальный уровень защиты, но не как полноценное секретное хранилище.
В процессе разработки часто используется файл .env,
который загружается через библиотеку dotenv:
import dotenv from 'dotenv';
dotenv.config();
console.log(process.env.API_KEY);
Файл .env позволяет централизовать конфигурацию, однако
требует строгого контроля:
Практика показывает, что утечки .env файлов остаются
одной из частых причин компрометации приложений.
По мере роста системы появляются требования, которые невозможно удовлетворить только переменными окружения:
В таких условиях используются специализированные секретные хранилища.
Современные инфраструктуры используют внешние системы управления секретами:
Их ключевые возможности:
Пример получения секрета из Vault:
import vault from 'node-vault';
const client = vault({
endpoint: 'https://vault.example.com',
token: process.env.VAULT_TOKEN
});
const secret = await client.read('secret/data/api');
console.log(secret.data.data.API_KEY);
Однако даже при использовании таких систем возникает необходимость локальной защиты данных внутри приложения. Здесь используется дополнительный уровень — криптографическое упаковывание данных.
Библиотека @hapi/iron предоставляет механизм безопасного
сериализованного хранения данных с использованием симметричного
шифрования и HMAC-подписи.
Основная идея заключается в том, что объект превращается в строку, которая:
Процесс состоит из двух операций:
seal — упаковка объекта в защищённую строкуunseal — восстановление исходного объектаПример использования:
import Iron from '@hapi/iron';
const password = process.env.IRON_PASSWORD;
const data = {
userId: 42,
role: 'admin'
};
const sealed = await Iron.seal(data, password, Iron.defaults);
const unsealed = await Iron.unseal(sealed, password, Iron.defaults);
В результате sealed представляет собой строку,
безопасную для хранения в cookies, базах данных или токенах.
Iron часто применяется в следующих сценариях:
Пример хранения сессии:
const sessionData = {
userId: 1001,
permissions: ['read', 'write']
};
const sealedSession = await Iron.seal(
sessionData,
process.env.SESSION_SECRET,
Iron.defaults
);
Такой подход позволяет хранить состояние клиента без необходимости раскрывать структуру данных.
На практике Iron и переменные окружения используются совместно:
const secret = process.env.IRON_SECRET_KEY;
const token = await Iron.seal(payload, secret, Iron.defaults);
Ключевой принцип заключается в том, что сам секрет не попадает в репозиторий, а загружается извне.
Хранение ключей требует многоуровневого подхода:
Каждое окружение должно иметь собственные секреты:
Доступ к секретам должен быть строго ограничен:
Ключи должны периодически обновляться:
Наиболее распространённые проблемы:
const secret = "hardcoded-secret"; // критическая ошибка
Такой подход делает невозможным безопасное развертывание.
console.log(process.env.API_KEY);
Логи часто сохраняются в централизованных системах и могут стать источником утечки.
Это увеличивает радиус поражения при компрометации.
На практике используется многоуровневая модель:
Такой подход обеспечивает:
import Iron from '@hapi/iron';
import vault from 'node-vault';
const client = vault({ endpoint: process.env.VAULT_URL });
const secretData = await client.read('secret/data/app');
const encryptionKey = secretData.data.data.IRON_KEY;
async function createSecureSession(user) {
return await Iron.seal(
{
id: user.id,
email: user.email
},
encryptionKey,
Iron.defaults
);
}
В такой архитектуре: