Перейти к содержимому

Курирование

Слой курирования обеспечивает детальный контроль доступа к пакетам для прокси-реестров. Он оценивает каждый запрос на скачивание по цепочке фильтров: список блокировки, список разрешений и изоляция пространств имён.


Курирование отвечает на вопрос: должен ли этот пакет быть допущен в мою организацию?

Цепочка фильтров выполняется в следующем порядке:

  1. Фильтр пространств имён — блокирует проксирование внутренних имён пакетов во внешний реестр (всегда активен, даже в режиме off)
  2. Фильтр списка блокировки — блокирует пакеты, соответствующие явным правилам запрета
  3. Фильтр списка разрешений — запрет по умолчанию: допускаются только явно указанные пакеты

Каждый фильтр возвращает одно из трёх решений:

  • Allow — запрос продолжается
  • Block — запрос отклоняется (403 в режиме enforce)
  • Skip — нет решения, передать следующему фильтру

Если все фильтры вернули Skip, запрос разрешается.


РежимПоведение
offКурирование отключено. Все прокси-запросы проходят. Фильтр пространств имён по-прежнему активен.
auditФильтры оценивают и логируют решения, но никогда не блокируют. Полезно для тестирования правил перед применением.
enforceФильтры оценивают и блокируют совпадающие запросы ответом 403. Требует указания allowlist_path.
Окно терминала
# Environment variable
export NORA_CURATION_MODE=audit
config.toml
[curation]
mode = "audit" # "off", "audit", "enforce"
on_failure = "closed" # "closed" (fail-safe) or "open" (fail-open)

Параметр on_failure определяет поведение при ошибке или панике фильтра:

  • closed (по умолчанию) — расценивать как блокировку (безопасный отказ)
  • open — расценивать как разрешение (открытый отказ)

Список блокировки — это JSON-файл, содержащий правила запрета. Если любое правило совпадает с запросом пакета, он блокируется.

{
"version": 1,
"rules": [
{
"registry": "npm",
"name": "event-stream",
"version": "3.3.6",
"reason": "CVE-2018-16492: malicious dependency flatmap-stream"
},
{
"registry": "*",
"name": "colors",
"version": "1.4.1",
"reason": "Maintainer protest: infinite loop in v1.4.1"
},
{
"registry": "pypi",
"name": "colourama",
"version": "*",
"reason": "Typosquatting: malicious clone of colorama"
},
{
"registry": "maven",
"name": "org.log4j.**",
"version": "*",
"reason": "Block all log4j packages pending security review"
}
]
}

Поля registry, name и version поддерживают простые glob-паттерны:

ПаттернСовпадения
*Всё
foo*Префикс: foo, foobar, foo-baz
*fooСуффикс: foo, barfoo
foo.**Иерархический префикс с разделителем-точкой: foo, foo.bar, foo.bar.baz (для Maven groupId)
foo/**Иерархический префикс с разделителем-слешем: foo, foo/bar, foo/bar/baz (для модулей Go)
exactТочное совпадение строки
Окно терминала
export NORA_CURATION_MODE=enforce
export NORA_CURATION_BLOCKLIST_PATH=/etc/nora/blocklist.json
[curation]
mode = "enforce"
blocklist_path = "/etc/nora/blocklist.json"

Список разрешений — это список с запретом по умолчанию: допускаются только явно указанные пакеты. Это самая строгая форма курирования.

{
"version": 1,
"entries": [
{
"registry": "npm",
"name": "lodash",
"version": "4.17.21",
"integrity": "sha256:abc123def456...",
"integrity_source": "upstream"
},
{
"registry": "npm",
"name": "express",
"version": "4.18.2"
},
{
"registry": "cargo",
"name": "serde",
"version": "1.0.203",
"integrity": "sha256:789abc...",
"integrity_source": "local"
}
]
}

Поля:

  • registry — точный тип реестра: npm, pypi, maven, cargo, docker, go, gems, terraform, ansible, nuget, pub, conan
  • name — точное имя пакета
  • version — точная строка версии
  • integrity (необязательно) — хеш SHA-256 для проверки после скачивания
  • integrity_source (необязательно, информационное) — upstream, local или manual
Окно терминала
export NORA_CURATION_MODE=enforce
export NORA_CURATION_ALLOWLIST_PATH=/etc/nora/allowlist.json

Когда mode=enforce, параметр allowlist_path является обязательным. NORA откажется запускаться без него.

Когда require_integrity=true, каждая запись списка разрешений должна содержать хеш integrity. После скачивания артефакта из вышестоящего реестра NORA вычисляет его SHA-256 и сравнивает со значением из записи списка разрешений. При несовпадении запрос блокируется.

Окно терминала
export NORA_CURATION_REQUIRE_INTEGRITY=true

Это защищает от атак на цепочку поставок, при которых пакет заменяется после проверки.


Изоляция пространств имён предотвращает атаки подмены зависимостей (dependency confusion), блокируя прокси-запросы для пакетов, соответствующих паттернам внутренних пространств имён. Этот фильтр всегда активен, даже когда режим курирования установлен в off.

Окно терминала
export NORA_CURATION_INTERNAL_NAMESPACES="@mycompany/*,com.mycompany.**,github.com/myorg/**"
[curation]
internal_namespaces = [
"@mycompany/*", # npm scoped packages
"com.mycompany.**", # Maven groupId hierarchy
"github.com/myorg/**", # Go modules
]

Когда прокси-запрос совпадает с паттерном внутреннего пространства имён, NORA возвращает 403 вместо обращения к вышестоящему реестру. Это гарантирует, что внутренние пакеты обслуживаются только из локального хранилища.


Для экстренных ситуаций (например, критическое обновление пакета заблокировано курированием) токен обхода позволяет определённым запросам пропускать фильтры курирования. Изоляция пространств имён по-прежнему применяется.

Окно терминала
export NORA_CURATION_BYPASS_TOKEN="your-secret-token"

Отправляйте токен в HTTP-заголовке X-Nora-Bypass-Token:

Окно терминала
curl -H "X-Nora-Bypass-Token: your-secret-token" \
https://registry.example.com/npm/lodash/-/lodash-4.17.22.tgz

События обхода логируются с тегом [SECURITY] для целей аудита. Используйте токены обхода осторожно и регулярно выполняйте их ротацию.


Совместное использование списка блокировки и списка разрешений

Заголовок раздела «Совместное использование списка блокировки и списка разрешений»

Когда оба файла настроены, порядок проверки следующий:

  1. Список блокировки проверяется первым. Если пакет соответствует правилу списка блокировки, он блокируется независимо от наличия в списке разрешений.
  2. Список разрешений проверяется вторым. Если пакета нет в списке разрешений, он блокируется.

Это означает, что список блокировки действует как наложение поверх списка разрешений. Вы можете одобрить пакет в списке разрешений, а затем заблокировать конкретную уязвимую версию через список блокировки без изменения списка разрешений.

[curation]
mode = "enforce"
blocklist_path = "/etc/nora/blocklist.json"
allowlist_path = "/etc/nora/allowlist.json"

NORA включает CLI-команды для работы с файлами курирования.

Проверьте JSON-файл списка блокировки или списка разрешений перед развёртыванием:

Окно терминала
nora curation validate /etc/nora/blocklist.json

Вывод:

OK: Valid blocklist -- 4 rules
[1] npm/event-stream@3.3.6 -- CVE-2018-16492: malicious dependency flatmap-stream
[2] */colors@1.4.1 -- Maintainer protest: infinite loop in v1.4.1
[3] pypi/colourama@* -- Typosquatting: malicious clone of colorama
[4] maven/org.log4j.**@* -- Block all log4j packages pending security review
Окно терминала
nora curation validate /etc/nora/allowlist.json

Вывод:

OK: Valid allowlist -- 3 entries (2 with integrity)
[1] npm/lodash@4.17.21 [hash]
[2] npm/express@4.18.2
[3] cargo/serde@1.0.203 [hash]

Команда validate проверяет:

  • Корректность синтаксиса JSON
  • Версию схемы (должна быть 1)
  • Наличие обязательных полей

Объясняет решение курирования для конкретного пакета. Требует работающей конфигурации (читает config.toml или переменные окружения):

Окно терминала
nora curation explain cargo:serde@1.0.203

Формат пакета: registry:name@version. Часть с версией необязательна:

Окно терминала
nora curation explain npm:lodash

Команда explain загружает настроенные файлы списка блокировки и списка разрешений и моделирует цепочку фильтров, показывая, какой фильтр принял решение и почему.


Когда курирование блокирует запрос в режиме enforce, ответ представляет собой JSON с кодом 403:

{
"error": "blocked_by_policy",
"error_version": "v1",
"context": {
"rule": "blocklist",
"reason": "CVE-2018-16492: malicious dependency flatmap-stream",
"registry": "npm",
"package": "event-stream",
"version": "3.3.6"
},
"hint": "Run: nora curation explain event-stream@3.3.6",
"docs": "https://docs.getnora.dev/curation"
}

Заголовки ответа:

  • X-Nora-Decision: blocked
  • X-Nora-Rule: blocklist
  • X-Nora-Reason: CVE-2018-16492: ...

Эти заголовки позволяют CI/CD-пайплайнам обнаруживать и обрабатывать блокировки курирования программным путём.


Решения курирования записываются в аудит-лог и видны через заголовки ответа:

ЗаголовокОписание
X-Nora-Decisionallowed или blocked
X-Nora-RuleКакое правило сработало (blocklist, allowlist, cve, namespace)
X-Nora-ReasonЧеловекочитаемая причина решения

Используйте nora_http_requests_total{status="403"} в Prometheus для отслеживания заблокированных загрузок.


{
"version": 1,
"rules": [
{
"registry": "npm",
"name": "event-stream",
"version": "3.3.6",
"reason": "CVE-2018-16492"
}
]
}

Режим только со списком разрешений (строгий)

Заголовок раздела «Режим только со списком разрешений (строгий)»

Одобряйте только проверенные пакеты. Всё остальное запрещено:

Окно терминала
export NORA_CURATION_MODE=enforce
export NORA_CURATION_ALLOWLIST_PATH=/etc/nora/allowlist.json
export NORA_CURATION_REQUIRE_INTEGRITY=true

Сначала запустите в режиме аудита, чтобы увидеть, что будет заблокировано, не мешая разработчикам:

Окно терминала
export NORA_CURATION_MODE=audit
export NORA_CURATION_BLOCKLIST_PATH=/etc/nora/blocklist.json
export NORA_CURATION_ALLOWLIST_PATH=/etc/nora/allowlist.json

Проверьте логи на наличие записей [AUDIT] Download would be blocked, затем переключитесь на enforce, когда будете удовлетворены результатом.

Блокировка всех пакетов из пространства имён

Заголовок раздела «Блокировка всех пакетов из пространства имён»
{
"version": 1,
"rules": [
{
"registry": "maven",
"name": "org.apache.log4j.**",
"version": "*",
"reason": "Log4Shell family - use logback instead"
},
{
"registry": "go",
"name": "github.com/untrusted-org/**",
"version": "*",
"reason": "Untrusted organization"
}
]
}