Политики
Node.js содержит экспериментальную поддержку создания политик при загрузке кода.
Политики — это функция безопасности, предназначенная для предоставления гарантий о том, какой код Node.js может загрузить. Использование политик предполагает безопасные методы работы с файлами политики, например, обеспечение невозможности перезаписи файлов политики приложением Node.js с помощью разрешений на доступ к файлам.
Лучшей практикой является обеспечение того, чтобы манифест политики был только для чтения для запущенного приложения Node.js и что приложение Node.js не могло каким-либо образом изменить этот файл. Типичная настройка — создание файла политики от имени другого пользователя, отличного от пользователя, под которым запускается Node.js, и предоставление разрешений на чтение пользователю, под которым запускается Node.js.
Включение
Флаг --experimental-policy может использоваться для включения функций политик при загрузке модулей.
После его установки все модули должны соответствовать файлу манифеста политики, переданному в флаге:
node --experimental-policy=policy.json app.js
Манифест политики будет использоваться для принудительного соблюдения ограничений на загружаемый Node.js код.
Для предотвращения несанкционированных изменений файлов политики на диске, целостность самого файла политики может быть обеспечена с помощью --policy-integrity. Это позволяет запускать node и проверять содержимое файла политики даже в случае изменения файла на диске.
node --experimental-policy=policy.json --policy-integrity="sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0" app.js
Особенности
Поведение при ошибках
При неудачной проверке политики Node.js по умолчанию выбросит ошибку. Можно изменить поведение обработки ошибок на несколько вариантов, определив поле "onerror" в манифесте политики. Доступны следующие значения для изменения поведения:
-
"exit": немедленно завершит процесс. Никакие очистительные действия выполняться не будут. -
"log": запишет ошибку в месте возникновения сбоя. -
"throw": выбросит ошибку JS в месте возникновения сбоя. Это значение по умолчанию.
{
"onerror": "log",
"resources": {
"./app/checked.js": {
"integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
}
}
} Проверки целостности
Файлы политики должны использовать проверки целостности с использованием строк Subresource Integrity, совместимых с атрибутом integrity браузера, связанным с абсолютными URL-адресами. атрибутом integrity.
При использовании require() или import все ресурсы, участвующие в загрузке, проверяются на целостность, если указан манифест политики. Если ресурс не соответствует целостности, указанной в манифесте, будет выброшена ошибка.
Пример файла политики, который позволил бы загрузить файл checked.js:
{
"resources": {
"./app/checked.js": {
"integrity": "sha384-SggXRQHwCG8g+DktYYzxkXRIkTiEYWBHqev0xnpCxYlqMBufKZHAHQM3/boDaI/0"
}
}
} Каждый ресурс, перечисленный в манифесте политики, может быть одного из следующих форматов для определения его местоположения:
- Строка относительного URL-адреса до ресурса из манифеста, например
./resource.js,../resource.js, или/resource.js. - Полная строка URL-адреса ресурса, например
file:///resource.js.
При загрузке ресурсов весь URL-адрес должен совпадать, включая параметры поиска и фрагмент. ./a.js?b не будет использоваться при попытке загрузить ./a.js и наоборот.
Для создания строк целостности можно использовать скрипт, например node -e 'process.stdout.write("sha256-");process.stdin.pipe(crypto.createHash("sha256").setEncoding("base64")).pipe(process.stdout)' < FILE.
Целостность может быть задана как логическое значение true для принятия любого тела для ресурса, что может быть полезно для разработки на локальном уровне. Не рекомендуется в рабочей среде, поскольку это позволит считать допустимым неожиданное изменение ресурсов.
Перенаправление зависимостей
Приложению может потребоваться отправить исправленные версии модулей или предотвратить доступ всех модулей ко всем остальным модулям. Перенаправление можно использовать, перехватывая попытки загрузки модулей, которые нужно заменить.
{
"resources": {
"./app/checked.js": {
"dependencies": {
"fs": true,
"os": "./app/node_modules/alt-os",
"http": { "import": true }
}
}
}
} Зависимости индексируются по строке запрошенного спецификатора и имеют значения true, null, строки, указывающей на модуль для разрешения, или объекта условий.
Строка спецификатора не выполняет никаких поисков и должна точно соответствовать предоставленному require() или import, за исключением шага канонизации. Поэтому, если политика использует несколько различных строк для указания на один и тот же модуль (например, без расширения), может потребоваться несколько спецификаторов.
Строки спецификаторов канонизируются, но не разрешаются перед использованием для сопоставления, чтобы обеспечить некоторую совместимость с картами импорта, например, если ресурсу file:///C:/app/server.js было задано следующее перенаправление из политики, расположенной по адресу file:///C:/app/policy.json:
{
"resources": {
"file:///C:/app/utils.js": {
"dependencies": {
"./utils.js": "./utils-v2.js"
}
}
}
} Любой спецификатор, используемый для загрузки file:///C:/app/utils.js, будет перехвачен и перенаправлен на file:///C:/app/utils-v2.js вместо этого, независимо от использования абсолютного или относительного спецификатора. Однако если используется спецификатор, не являющийся строкой абсолютного или относительного URL-адреса, он не будет перехвачен. Таким образом, если используется импорт, такой как import('#utils'), он не будет перехвачен.
Если значение перенаправления равно true, будет использован параметр "dependencies" в верхней части файла политики. Если это поле в верхней части файла политики равно true, будут использоваться стандартные алгоритмы поиска узла для поиска модуля.
Если значение перенаправления является строкой, оно разрешается относительно манифеста и немедленно используется без поиска.
Любой спецификатор, для которого попытка разрешения не перечислена в зависимостях, приводит к ошибке в соответствии с политикой.
Перенаправление не предотвращает доступ к API посредством таких методов, как прямой доступ к require.cache или через module.constructor, которые позволяют получить доступ к загрузке модулей. Перенаправление политики затрагивает только спецификаторы для require() и import. Другие методы, такие как предотвращение нежелательного доступа к API через переменные, необходимы для блокировки этого пути загрузки модулей.
Можно указать логическое значение true для карты зависимостей, чтобы разрешить модулю загружать любой спецификатор без перенаправления. Это может быть полезно для разработки на локальном уровне и может иметь некоторое обоснованное применение в рабочей среде, но должно использоваться с осторожностью после проверки модуля, чтобы убедиться в корректности его поведения.
Аналогично "exports" в package.json, зависимости также можно задать в виде объектов, содержащих условия, которые определяют способ загрузки зависимостей. В приведенном выше примере "http" разрешается, когда условие "import" является частью его загрузки.
Значение null для разрешенного значения места расположения модуля приводит к отказу в разрешении. Это можно использовать, чтобы гарантировать, что некоторые виды динамического доступа явно запрещены.
Неизвестные значения местоположения разрешенного модуля вызывают ошибки, но не гарантируют совместимость в будущем.
Пример: Замещенная зависимость
Перенаправленные зависимости могут обеспечивать ослабленную или измененную функциональность, соответствующую применению. Например, регистрировать данные о времени выполнения функций путем обертывания оригинальной:
const original = require('fn');
module.exports = function fn(...args) {
console.time();
try {
return new.target ?
Reflect.construct(original, args) :
Reflect.apply(original, this, args);
} finally {
console.timeEnd();
}
}; Области
Используйте поле "scopes" манифеста для одновременной настройки параметров многих ресурсов. Поле "scopes" работает путем сопоставления ресурсов по их сегментам. Если область или ресурс включает "cascade": true, неизвестные спецификаторы будут искаться в содержащей их области. Область содержания для каскадного поиска находится путем рекурсивного сокращения URL-адреса ресурса путем удаления сегментов для специальных схем, сохранения конечных "/" суффиксов и удаления запроса и фрагмента. Это приводит к окончательному сокращению URL до его источника. Если URL не является специальным, область будет расположена по источнику URL. Если область для источника или в случае неявного источника не найдена, может быть использована строка протокола как область. Если область для протокола URL не найдена, будет использована окончательная пустая строка "" область.
Обратите внимание, что URL-адреса blob: принимают свой источник из содержащегося в них пути, поэтому область "blob:https://nodejs.org" не окажет никакого влияния, так как никакой URL не может иметь источник blob:https://nodejs.org; URL, начинающиеся с blob:https://nodejs.org/ будут использовать https://nodejs.org в качестве источника, и поэтому https: в качестве области протокола. Для URL-адресов неявного источника blob: у них будет blob: в качестве области протокола, так как они не принимают источники.
Пример
{
"scopes": {
"file:///C:/app/": {},
"file:": {},
"": {}
}
} Если файл расположен по адресу file:///C:/app/bin/main.js, будут проверены следующие области в порядке:
"file:///C:/app/bin/"
Это определяет политику для всех ресурсов на основе файлов в "file:///C:/app/bin/". Это не в поле "scopes" политики и будет пропущено. Добавление этой области в политику приведет к ее использованию до области "file:///C:/app/".
"file:///C:/app/"
Это определяет политику для всех ресурсов на основе файлов в "file:///C:/app/". Это в поле "scopes" политики, и оно определяет политику для ресурса по адресу file:///C:/app/bin/main.js. Если область имеет "cascade": true, любые невыполненные запросы к ресурсу будут делегированы следующей соответствующей области для file:///C:/app/bin/main.js, "file:".
"file:///C:/"
Это определяет политику для всех ресурсов на основе файлов в "file:///C:/". Это не в поле "scopes" политики и будет пропущено. Она не будет использоваться для file:///C:/app/bin/main.js до тех пор, пока "file:///" не будет установлено в каскадное, или если оно не будет в "scopes" политики.
"file:///"
Это определяет политику для всех ресурсов на основе файлов на localhost. Это не в поле "scopes" политики и будет пропущено. Она не будет использоваться для file:///C:/app/bin/main.js до тех пор, пока "file:///" не будет установлено в каскадное или если оно не будет в "scopes" политики.
"file:"
Это определяет политику для всех ресурсов на основе файлов. Она не будет использоваться для file:///C:/app/bin/main.js до тех пор, пока "file:///" не будет установлено в каскадное или если оно не будет в "scopes" политики.
""
Это определяет политику для всех ресурсов. Она не будет использоваться для file:///C:/app/bin/main.js до тех пор, пока "file:" не будет установлено в каскадное.
Целостность с использованием областей
Установка целостности в true в области установит целостность любого ресурса, не найденного в манифесте, в true.
Установка целостности в null в области установит целостность любого ресурса, не найденного в манифесте, в провал соответствия.
Отсутствие целостности эквивалентно установке целостности в null.
"cascade" для проверок целостности будет проигнорировано, если "integrity" явно установлено.
Следующий пример позволяет загрузить любой файл:
{
"scopes": {
"file:": {
"integrity": true
}
}
} Перенаправление зависимостей с использованием областей
Следующий пример позволит получить доступ к fs для всех ресурсов в ./app/:
{
"resources": {
"./app/checked.js": {
"cascade": true,
"integrity": true
}
},
"scopes": {
"./app/": {
"dependencies": {
"fs": true
}
}
}
} Следующий пример позволит получить доступ к fs для всех ресурсов data::
{
"resources": {
"data:text/javascript,import('fs');": {
"cascade": true,
"integrity": true
}
},
"scopes": {
"data:": {
"dependencies": {
"fs": true
}
}
}
} Пример: Эмуляция карт импорта
Учитывая карту импорта:
{
"imports": {
"react": "./app/node_modules/react/index.js"
},
"scopes": {
"./ssr/": {
"react": "./app/node_modules/server-side-react/index.js"
}
}
} {
"dependencies": true,
"scopes": {
"": {
"cascade": true,
"dependencies": {
"react": "./app/node_modules/react/index.js"
}
},
"./ssr/": {
"cascade": true,
"dependencies": {
"react": "./app/node_modules/server-side-react/index.js"
}
}
}
} Карты импорта предполагают, что по умолчанию можно получить любой ресурс. Это означает, что "dependencies" на верхнем уровне политики должно быть установлено в true. Политики требуют, чтобы это было включено, поскольку это включает кросс-связывание всех ресурсов приложения, что не имеет смысла для многих сценариев. Они также предполагают, что любая заданная область имеет доступ к любой области, находящейся над ее разрешенными зависимостями; все области, эмулирующие карты импорта, должны установить "cascade": true.
Карты импорта имеют только один верхний уровень области видимости для своих «импортов». Поэтому для эмуляции "imports" используйте область видимости "". Для эмуляции "scopes" используйте "scopes" аналогичным образом, как работает "scopes" в картах импорта.
Ограничения: Политики не используют сопоставление строк для различных способов определения области видимости. Они выполняют обход URL. Это означает, что такие вещи, как blob: и data: URL могут не быть полностью совместимыми между двумя системами. Например, карты импорта могут частично соответствовать data: или blob: URL, разделяя URL на символ /, политики намеренно не могут. Для blob: URL области видимости карты импорта не принимают происхождение blob: URL.
Кроме того, карты импорта работают только с import, поэтому может быть желательно добавить условие "import" ко всем зависимостям.
© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v16.x/docs/api/policy.html