core.url
Загрузка файла по https-URL с checksum-верификацией и SSRF-guard.
core.url.fetched скачивает файл по url в path и приводит mode/owner/group к декларации. Работает secure-by-default: только https://, SSRF-guard по резолвнутому IP, проверка TLS-цепочки и checksum-верификация во временном файле до atomic rename — неверный хэш не материализуется никогда. Идемпотентность идёт по checksum, а без него — по SHA-256; каждый контур безопасности снимается отдельным opt-out-флагом. Без shell.
Требования
- Rootтребуется
- Сторона
soul-side - Коллекция
soulstack.network - Категория
network
network_outboundfs_write_rootСостояния
core.url.fetched — Файл по url материализован в path с заданными mode/owner/group.
Контент скачан заново либо правился атрибут (mode/owner/group). Ветвление — в таблице «Поведение fetched по веткам».
Совпадение по checksum/SHA-256 без правки атрибутов.
Параметры
| Параметр | Тип | Обяз. / дефолт | Описание |
|---|---|---|---|
url | string | required | Источник (только https://; SSRF-guard на резолвнутый IP). |
path | string | required | Целевой путь файла. |
checksum | string | optional | Ожидаемый хэш в форме "sha256:<hex>"/"sha1:<hex>" (supply-chain). |
mode | string | optional | Права в восьмеричной форме, напр. "0644". |
owner | string | optional | Владелец (имя пользователя). |
group | string | optional | Группа-владелец (имя группы). |
headers | map<string> | optional | HTTP-заголовки запроса (значения секретны, в output не попадают). If-None-Match/If-Modified-Since здесь же дают conditional-GET (304 → no-op). |
timeout | string | optional | Таймаут запроса (Soul Stack duration, напр. "300s"). |
allow_http | bool | optional · false | Разрешить http:// (downgrade-риск). НЕ открывает SSRF — dial-guard держится отдельно (allow_private). |
insecure_skip_verify | bool | optional · false | Не проверять TLS-цепочку (self-signed / internal CA). MITM-риск. |
allow_private | bool | optional · false | Снять SSRF-guard: разрешить dial в metadata/loopback/RFC1918 (легитимный internal endpoint). |
Пример — Скачать релиз с GitHub с проверкой checksum
- name: Fetch node_exporter tarball with checksum module: core.url.fetched params: url: "https://github.com/prometheus/node_exporter/releases/download/v1.9.0/node_exporter-1.9.0.linux-amd64.tar.gz" path: /tmp/node_exporter-1.9.0.tar.gz checksum: "sha256:abc...def" mode: "0644"Output
| Поле | Тип | Описание |
|---|---|---|
path | string | |
url | string (эхо без headers) | |
sha256 | string (SHA-256 записанного/совпавшего содержимого) | |
size | int (байты) | |
changed | bool | |
fetched | true |
Справочник
Поведение fetched по веткам
Логика зависит от того, задан ли checksum и совпадает ли уже лежащий по path файл.
| Условие | Действие | changed |
|---|---|---|
| checksum задан + файл есть и совпал по хэшу | контент не качается; mode/owner/group приводятся к декларации (convergence) | true только если правился атрибут |
| checksum задан, файла нет / хэш не совпал | скачать во temp → verify по checksum → atomic rename; mismatch → failed, temp удаляется, целевой путь не трогается | true |
| checksum не задан + файл есть и совпал по SHA-256 | скачать во temp → сравнить SHA-256 → записи нет; mode/owner/group сверяются | true только если правился атрибут |
| checksum не задан + содержимое отличается / файла нет | скачать во temp → atomic rename | true |
Заметки
- Условный GET: If-None-Match / If-Modified-Since в headers дают conditional-GET — сервер может ответить 304 Not Modified (тело не передаётся). 304 при существующем локальном файле → no-op (mode/owner/group приводятся к декларации, output по существующему файлу); 304 без локального файла → failed (stale-валидатор без кэша, скачивать нечего). 304 проверяется раньше общей проверки 2xx.
- Права зависят от целевого path: запись в системный каталог требует root, в user-writable путь (напр. /tmp) — нет. Манифест объявляет network_outbound + fs_write_root.
- checksum: поддержаны sha256 и sha1; md5 сознательно не поддержан (слаб для supply-chain). В output.sha256 всегда SHA-256, даже если checksum задавался по sha1.
- Снятие любого opt-out-флага (allow_http / allow_private / insecure_skip_verify) кладёт строку в output warnings — только host, без полного URL и headers (query/path и заголовки могут нести секреты).
- Не выполняет подпроцессов: скачивание, хэширование и запись — in-process, без shell.
Безопасность и умолчания
- По умолчанию только https:// — http://, file:// и трюки вида https://\nhttp://evil отвергаются (схема сверяется регистронезависимо через url.Parse). allow_http допускает http://, но file:// / ftp:// остаются запрещены и SSRF-guard не ослабляется.
- SSRF-guard: dial в metadata (169.254.169.254), loopback, RFC1918, link-local/CGNAT заблокирован по фактически резолвнутому IP — закрывает прямой SSRF (кража cloud-metadata IAM-кредов) и DNS-rebind (dial по уже проверенному IP, без второго резолва). Хост с парой A-записей «публичный + metadata» блокируется целиком. Снимается только allow_private.
- Checksum verify ДО публикации: скачивание всегда идёт во временный файл в каталоге path, хэш считается на лету, verify против checksum — до rename. При mismatch temp удаляется, целевой путь не трогается — неверный хэш не материализуется никогда.
- TLS — системный trust store по умолчанию; снимается insecure_skip_verify (self-signed / internal CA, MITM-риск). Редиректы: downgrade https→http отвергается (допускается только при allow_http; редирект на не-http(s) схему блокируется всегда, как и dial в приватные адреса на hop'е без allow_private).
- headers sensitive-by-construction (ADR-010 §7.4): значения никогда не логируются и не попадают в output/register, в том числе в эхо-поле url.