Установщик повторно использует только проверенный аккаунт akyldash-runner, subordinate ID mappings и файлы с совпадающей конфигурацией. UID, home, primary/supplementary groups проверяются; sudo и docker group не допускаются.
Образ существующего хранилища должен иметь размер 15 GiB и ext4. При несовпадении установщик останавливается без fallocate/mkfs.
Mount unit включается/запускается полным именем с .mount. Уже смонтированный store проверяется по target, ext4 и backing loop image до действий с mountpoint.
Повтор rootless Docker setup не перезаписывает уже существующий unit.
Добавлены mock-тесты идемпотентности и восстановления после прерывания между enable и start; CI запускает их. Документация отражает сообщённое владельцем частичное состояние VPS.
VPS не изменялся; production install script не запускался.
## Изменения
- Установщик повторно использует только проверенный аккаунт `akyldash-runner`, subordinate ID mappings и файлы с совпадающей конфигурацией. UID, home, primary/supplementary groups проверяются; `sudo` и `docker` group не допускаются.
- Образ существующего хранилища должен иметь размер 15 GiB и ext4. При несовпадении установщик останавливается без `fallocate`/`mkfs`.
- Mount unit включается/запускается полным именем с `.mount`. Уже смонтированный store проверяется по target, ext4 и backing loop image до действий с mountpoint.
- Повтор rootless Docker setup не перезаписывает уже существующий unit.
- Добавлены mock-тесты идемпотентности и восстановления после прерывания между enable и start; CI запускает их. Документация отражает сообщённое владельцем частичное состояние VPS.
## Проверки
- `bash infra/runner/test-install-runner-idempotency.sh`
- `bash infra/runner/test-runner-config.sh`
- `bash -n` затронутых shell-файлов
- `git diff --check`
VPS не изменялся; production install script не запускался.
Проверил изменения PR 44 и сценарий повторного запуска. Обнаружены два существенных случая, которые установщик пока принимает или обрабатывает небезопасно. Локально прошли test-install-runner-idempotency.sh, test-runner-config.sh, test-install-runner-guard.sh и bash -n.
Проверил изменения PR 44 и сценарий повторного запуска. Обнаружены два существенных случая, которые установщик пока принимает или обрабатывает небезопасно. Локально прошли test-install-runner-idempotency.sh, test-runner-config.sh, test-install-runner-guard.sh и bash -n.
mount_info=$(findmnt --noheadings --output TARGET,FSTYPE,SOURCE --mountpoint "$store")||{ runner_die 'runner store is not mounted';return 1;}
read -r target filesystem source<<<"$mount_info"||{ runner_die 'runner store mount information is empty';return 1;}
[[$target=="$store"&&$filesystem== ext4 &&$source== /dev/loop* ]]||{ runner_die 'runner store is mounted from an unexpected target, filesystem, or device';return 1;}
P2 — повторный запуск принимает mount без обязательных nosuid,nodev. Проверка сопоставляет только target, ext4, loop device и backing image. Если тот же образ уже смонтирован вручную без защитных опций, установщик сочтёт его корректным и продолжит, хотя unit задаёт Options=loop,nosuid,nodev; systemctl enable не исправит опции уже активного mount. Проверьте фактические findmnt -o OPTIONS (или эквивалент) и прерывайте установку при отсутствии nosuid/nodev; добавьте отрицательный тест.
**P2 — повторный запуск принимает mount без обязательных `nosuid,nodev`.** Проверка сопоставляет только target, ext4, loop device и backing image. Если тот же образ уже смонтирован вручную без защитных опций, установщик сочтёт его корректным и продолжит, хотя unit задаёт `Options=loop,nosuid,nodev`; `systemctl enable` не исправит опции уже активного mount. Проверьте фактические `findmnt -o OPTIONS` (или эквивалент) и прерывайте установку при отсутствии `nosuid`/`nodev`; добавьте отрицательный тест.
P1 — повторный запуск может изменить права на системные каталоги через symlink в home runner..config и его потомки принадлежат akyldash-runner, но перед install -d -o ... их тип и путь не проверяются. Runner пользователь может заменить .config ссылкой на /etc; тогда этот вызов следует по ссылке и рекурсивно создаёт/делает доступными runner пользователю, например, /etc/docker и /etc/systemd/user/docker.service.d. Воспроизвёл поведение install -d на временных каталогах: режим целевого каталога меняется через symlink. Перед любыми root операциями на путях внутри home нужно отклонять ссылки в каждом родительском компоненте (и проверить ownership/режим), либо работать через безопасное открытие каталога без следования ссылкам.
**P1 — повторный запуск может изменить права на системные каталоги через symlink в home runner.** `.config` и его потомки принадлежат `akyldash-runner`, но перед `install -d -o ...` их тип и путь не проверяются. Runner пользователь может заменить `.config` ссылкой на `/etc`; тогда этот вызов следует по ссылке и рекурсивно создаёт/делает доступными runner пользователю, например, `/etc/docker` и `/etc/systemd/user/docker.service.d`. Воспроизвёл поведение `install -d` на временных каталогах: режим целевого каталога меняется через symlink. Перед любыми root операциями на путях внутри home нужно отклонять ссылки в каждом родительском компоненте (и проверить ownership/режим), либо работать через безопасное открытие каталога без следования ссылкам.
Повторно проверил PR 44. Проверка nosuid,nodev исправляет второе замечание; статический symlink теперь отклоняется. Однако проверка каталога оставляет гонку, а режимы каталогов от предыдущей версии установщика блокируют заявленный сценарий возобновления. Текущие тесты проходят, оба случая воспроизведены отдельно.
Повторно проверил PR 44. Проверка `nosuid,nodev` исправляет второе замечание; статический symlink теперь отклоняется. Однако проверка каталога оставляет гонку, а режимы каталогов от предыдущей версии установщика блокируют заявленный сценарий возобновления. Текущие тесты проходят, оба случая воспроизведены отдельно.
P1 — восстановление после предыдущего установщика блокируется правами промежуточных каталогов. Старый вызов install -d -m 0700 "$runner_home" "$runner_home/.config/docker" "$runner_home/.config/systemd/user/docker.service.d" создаёт .config, .config/systemd и .config/systemd/user с режимом 0755 (GNU install -d применяет -m к конечным каталогам). Если первый запуск дошёл до создания/mount образа, эти каталоги уже существуют. Новая проверка требует 700 и останавливается на .config до возобновления. Воспроизвёл старую структуру во временном дереве: runner_ensure_private_directory отклоняет .config 0755. Примите и безопасно нормализуйте ожидаемое старое состояние либо корректируйте проверку; добавьте тест перехода от дерева предыдущей версии.
**P1 — восстановление после предыдущего установщика блокируется правами промежуточных каталогов.** Старый вызов `install -d -m 0700 "$runner_home" "$runner_home/.config/docker" "$runner_home/.config/systemd/user/docker.service.d"` создаёт `.config`, `.config/systemd` и `.config/systemd/user` с режимом **0755** (GNU `install -d` применяет `-m` к конечным каталогам). Если первый запуск дошёл до создания/mount образа, эти каталоги уже существуют. Новая проверка требует `700` и останавливается на `.config` до возобновления. Воспроизвёл старую структуру во временном дереве: `runner_ensure_private_directory` отклоняет `.config` 0755. Примите и безопасно нормализуйте ожидаемое старое состояние либо корректируйте проверку; добавьте тест перехода от дерева предыдущей версии.
P1 — проверка symlink остаётся уязвимой к подмене пути между проверкой и записью. Родительские каталоги внутри /var/lib/akyldash-runner принадлежат самому runner пользователю. После [[ ! -L $current ]] он может переименовать .config и поставить ссылку на /etc до install -d/последующих root записей. Финальная проверка ! -L $directory проверяет только последний компонент и такую подмену пропускает. Воспроизвёл с подменой .config непосредственно перед install: функция успешно создала docker вне исходного дерева. Нужно удерживать доверенный дескриптор каталога и выполнять операции относительно него с запретом следования symlink, либо исключить возможность изменения предков runner пользователем на время root операций.
**P1 — проверка symlink остаётся уязвимой к подмене пути между проверкой и записью.** Родительские каталоги внутри `/var/lib/akyldash-runner` принадлежат самому runner пользователю. После `[[ ! -L $current ]]` он может переименовать `.config` и поставить ссылку на `/etc` до `install -d`/последующих root записей. Финальная проверка `! -L $directory` проверяет только последний компонент и такую подмену пропускает. Воспроизвёл с подменой `.config` непосредственно перед `install`: функция успешно создала `docker` вне исходного дерева. Нужно удерживать доверенный дескриптор каталога и выполнять операции относительно него с запретом следования symlink, либо исключить возможность изменения предков runner пользователем на время root операций.
Повторно проверил исправления: оба замечания к конфигурационным каталогам закрыты, сценарий старых 0755 каталогов теперь проходит, mount-опции проверяются. Осталась аналогичная гонка на создании файла образа под runner-owned home. Локальные тесты и синтаксические проверки прошли; отдельный опыт подтвердил повреждение файла через подмену пути.
Повторно проверил исправления: оба замечания к конфигурационным каталогам закрыты, сценарий старых `0755` каталогов теперь проходит, mount-опции проверяются. Осталась аналогичная гонка на создании файла образа под runner-owned home. Локальные тесты и синтаксические проверки прошли; отдельный опыт подтвердил повреждение файла через подмену пути.
P1 — новый образ всё ещё создаётся root по пути, контролируемому runner пользователем. После проверки [[ -e $image || -L $image ]] runner может создать symlink docker-store.img на любой файл до fallocate, а затем mkfs.ext4 -F следует по тому же пути. Воспроизвёл подмену в момент вызова fallocate на временных файлах: размер файла за ссылкой изменился с 4 до 1048576 байт. На VPS это даёт runner пользователю возможность направить root запись/форматирование вне runner home при повторном запуске после создания аккаунта, но до создания образа. Создавайте образ через безопасно открытый каталог и эксклюзивное создание без следования ссылкам; форматируйте именно созданный файловый дескриптор/проверенный inode, либо исключите изменение пути runner пользователем на время всей операции.
**P1 — новый образ всё ещё создаётся root по пути, контролируемому runner пользователем.** После проверки `[[ -e $image || -L $image ]]` runner может создать symlink `docker-store.img` на любой файл до `fallocate`, а затем `mkfs.ext4 -F` следует по тому же пути. Воспроизвёл подмену в момент вызова `fallocate` на временных файлах: размер файла за ссылкой изменился с 4 до 1048576 байт. На VPS это даёт runner пользователю возможность направить root запись/форматирование вне runner home при повторном запуске после создания аккаунта, но до создания образа. Создавайте образ через безопасно открытый каталог и эксклюзивное создание без следования ссылкам; форматируйте именно созданный файловый дескриптор/проверенный inode, либо исключите изменение пути runner пользователем на время всей операции.
Повторная проверка PR 44: запись и форматирование по открытому FD исправили предыдущую гонку; проверил на настоящих mkfs.ext4/blkid, тесты проходят. Но между этой проверкой и монтированием systemd остаётся подменяемый runner пользователем путь образа.
Повторная проверка PR 44: запись и форматирование по открытому FD исправили предыдущую гонку; проверил на настоящих mkfs.ext4/blkid, тесты проходят. Но между этой проверкой и монтированием systemd остаётся подменяемый runner пользователем путь образа.
P1 — проверенный FD не закрепляет образ, который позже смонтирует systemd. После возврата runner_ensure_image закрывает дескрипторы; /var/lib/akyldash-runner принадлежит runner пользователю, и он может переименовать проверенный образ и заменить docker-store.img своим ext4 файлом до systemctl start в runner_ensure_mount. Unit использует What=$runner_image по имени. Последующая runner_validate_mount сравнивает backing только с текущимrealpath этого имени, поэтому подмену тоже принимает (воспроизвёл отдельной проверкой функции). В итоге root может смонтировать непроверенную файловую систему, контролируемую runner пользователем. Разместите образ/его родительский каталог вне runner-writable дерева либо закрепите проверенный inode до монтирования и сравнивайте mount именно с ним.
**P1 — проверенный FD не закрепляет образ, который позже смонтирует systemd.** После возврата `runner_ensure_image` закрывает дескрипторы; `/var/lib/akyldash-runner` принадлежит runner пользователю, и он может переименовать проверенный образ и заменить `docker-store.img` своим ext4 файлом до `systemctl start` в `runner_ensure_mount`. Unit использует `What=$runner_image` по имени. Последующая `runner_validate_mount` сравнивает backing только с *текущим* `realpath` этого имени, поэтому подмену тоже принимает (воспроизвёл отдельной проверкой функции). В итоге root может смонтировать непроверенную файловую систему, контролируемую runner пользователем. Разместите образ/его родительский каталог вне runner-writable дерева либо закрепите проверенный inode до монтирования и сравнивайте mount именно с ним.
Проверил исправления и текущую версию PR 44. Root-owned home (0711) закрывает подмену образа и точки монтирования между проверкой и systemd; writable runtime/config перенесены в подкаталоги runner пользователя. Замечания прошлых ревью устранены. Прошли тесты install-runner-idempotency, runner-config, install-runner-guard, bash -n, py_compile и git diff --check; отдельно проверил создание и повторную валидацию ext4 образа с реальными mkfs.ext4/blkid при mode 0711.
Проверил исправления и текущую версию PR 44. Root-owned home (0711) закрывает подмену образа и точки монтирования между проверкой и systemd; writable runtime/config перенесены в подкаталоги runner пользователя. Замечания прошлых ревью устранены. Прошли тесты install-runner-idempotency, runner-config, install-runner-guard, bash -n, py_compile и git diff --check; отдельно проверил создание и повторную валидацию ext4 образа с реальными mkfs.ext4/blkid при mode 0711.
admin
merged commit e6b099cf31 into main2026-10-11 23:22:17 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Изменения
akyldash-runner, subordinate ID mappings и файлы с совпадающей конфигурацией. UID, home, primary/supplementary groups проверяются;sudoиdockergroup не допускаются.fallocate/mkfs..mount. Уже смонтированный store проверяется по target, ext4 и backing loop image до действий с mountpoint.Проверки
bash infra/runner/test-install-runner-idempotency.shbash infra/runner/test-runner-config.shbash -nзатронутых shell-файловgit diff --checkVPS не изменялся; production install script не запускался.
Проверил изменения PR 44 и сценарий повторного запуска. Обнаружены два существенных случая, которые установщик пока принимает или обрабатывает небезопасно. Локально прошли test-install-runner-idempotency.sh, test-runner-config.sh, test-install-runner-guard.sh и bash -n.
@@ -0,0 +60,4 @@local image=$1 store=$2 target filesystem source backing mount_infomount_info=$(findmnt --noheadings --output TARGET,FSTYPE,SOURCE --mountpoint "$store") || { runner_die 'runner store is not mounted'; return 1; }read -r target filesystem source <<<"$mount_info" || { runner_die 'runner store mount information is empty'; return 1; }[[ $target == "$store" && $filesystem == ext4 && $source == /dev/loop* ]] || { runner_die 'runner store is mounted from an unexpected target, filesystem, or device'; return 1; }P2 — повторный запуск принимает mount без обязательных
nosuid,nodev. Проверка сопоставляет только target, ext4, loop device и backing image. Если тот же образ уже смонтирован вручную без защитных опций, установщик сочтёт его корректным и продолжит, хотя unit задаётOptions=loop,nosuid,nodev;systemctl enableне исправит опции уже активного mount. Проверьте фактическиеfindmnt -o OPTIONS(или эквивалент) и прерывайте установку при отсутствииnosuid/nodev; добавьте отрицательный тест.@@ -50,3 +81,3 @@runner_uid=$(id -u "$runner_user")install -d -o "$runner_user" -g "$runner_user" -m 0700 "$runner_home" "$runner_store" "$runner_home/.config/docker" "$runner_home/.config/systemd/user/docker.service.d"install -d -o "$runner_user" -g "$runner_user" -m 0700 "$runner_home" "$runner_home/.config/docker" "$runner_home/.config/systemd/user/docker.service.d"P1 — повторный запуск может изменить права на системные каталоги через symlink в home runner.
.configи его потомки принадлежатakyldash-runner, но передinstall -d -o ...их тип и путь не проверяются. Runner пользователь может заменить.configссылкой на/etc; тогда этот вызов следует по ссылке и рекурсивно создаёт/делает доступными runner пользователю, например,/etc/dockerи/etc/systemd/user/docker.service.d. Воспроизвёл поведениеinstall -dна временных каталогах: режим целевого каталога меняется через symlink. Перед любыми root операциями на путях внутри home нужно отклонять ссылки в каждом родительском компоненте (и проверить ownership/режим), либо работать через безопасное открытие каталога без следования ссылкам.Повторно проверил PR 44. Проверка
nosuid,nodevисправляет второе замечание; статический symlink теперь отклоняется. Однако проверка каталога оставляет гонку, а режимы каталогов от предыдущей версии установщика блокируют заявленный сценарий возобновления. Текущие тесты проходят, оба случая воспроизведены отдельно.@@ -0,0 +76,4 @@fidoneif [[ -e $directory ]]; then[[ $(stat -c '%U:%G:%a' "$directory") == "$owner:$group:700" ]] || { runner_die "runner directory has unexpected ownership or permissions: $directory"; return 1; }P1 — восстановление после предыдущего установщика блокируется правами промежуточных каталогов. Старый вызов
install -d -m 0700 "$runner_home" "$runner_home/.config/docker" "$runner_home/.config/systemd/user/docker.service.d"создаёт.config,.config/systemdи.config/systemd/userс режимом 0755 (GNUinstall -dприменяет-mк конечным каталогам). Если первый запуск дошёл до создания/mount образа, эти каталоги уже существуют. Новая проверка требует700и останавливается на.configдо возобновления. Воспроизвёл старую структуру во временном дереве:runner_ensure_private_directoryотклоняет.config0755. Примите и безопасно нормализуйте ожидаемое старое состояние либо корректируйте проверку; добавьте тест перехода от дерева предыдущей версии.@@ -0,0 +79,4 @@[[ $(stat -c '%U:%G:%a' "$directory") == "$owner:$group:700" ]] || { runner_die "runner directory has unexpected ownership or permissions: $directory"; return 1; }returnfiinstall -d -o "$owner" -g "$group" -m 0700 "$directory" || return 1P1 — проверка symlink остаётся уязвимой к подмене пути между проверкой и записью. Родительские каталоги внутри
/var/lib/akyldash-runnerпринадлежат самому runner пользователю. После[[ ! -L $current ]]он может переименовать.configи поставить ссылку на/etcдоinstall -d/последующих root записей. Финальная проверка! -L $directoryпроверяет только последний компонент и такую подмену пропускает. Воспроизвёл с подменой.configнепосредственно передinstall: функция успешно создалаdockerвне исходного дерева. Нужно удерживать доверенный дескриптор каталога и выполнять операции относительно него с запретом следования symlink, либо исключить возможность изменения предков runner пользователем на время root операций.Повторно проверил исправления: оба замечания к конфигурационным каталогам закрыты, сценарий старых
0755каталогов теперь проходит, mount-опции проверяются. Осталась аналогичная гонка на создании файла образа под runner-owned home. Локальные тесты и синтаксические проверки прошли; отдельный опыт подтвердил повреждение файла через подмену пути.@@ -0,0 +28,4 @@[[ $filesystem == ext4 ]] || { runner_die "existing runner image does not contain ext4; refusing to format"; return 1; }returnfifallocate -l "$size" "$image" || return 1P1 — новый образ всё ещё создаётся root по пути, контролируемому runner пользователем. После проверки
[[ -e $image || -L $image ]]runner может создать symlinkdocker-store.imgна любой файл доfallocate, а затемmkfs.ext4 -Fследует по тому же пути. Воспроизвёл подмену в момент вызоваfallocateна временных файлах: размер файла за ссылкой изменился с 4 до 1048576 байт. На VPS это даёт runner пользователю возможность направить root запись/форматирование вне runner home при повторном запуске после создания аккаунта, но до создания образа. Создавайте образ через безопасно открытый каталог и эксклюзивное создание без следования ссылкам; форматируйте именно созданный файловый дескриптор/проверенный inode, либо исключите изменение пути runner пользователем на время всей операции.Повторная проверка PR 44: запись и форматирование по открытому FD исправили предыдущую гонку; проверил на настоящих mkfs.ext4/blkid, тесты проходят. Но между этой проверкой и монтированием systemd остаётся подменяемый runner пользователем путь образа.
@@ -71,3 +96,1 @@EOFsystemctl daemon-reloadsystemctl enable --now "$(basename "$runner_mount_unit" .mount)"runner_ensure_image "$runner_image" "$runner_image_bytes" "$runner_uid" || die 'runner image initialization failed'P1 — проверенный FD не закрепляет образ, который позже смонтирует systemd. После возврата
runner_ensure_imageзакрывает дескрипторы;/var/lib/akyldash-runnerпринадлежит runner пользователю, и он может переименовать проверенный образ и заменитьdocker-store.imgсвоим ext4 файлом доsystemctl startвrunner_ensure_mount. Unit используетWhat=$runner_imageпо имени. Последующаяrunner_validate_mountсравнивает backing только с текущимrealpathэтого имени, поэтому подмену тоже принимает (воспроизвёл отдельной проверкой функции). В итоге root может смонтировать непроверенную файловую систему, контролируемую runner пользователем. Разместите образ/его родительский каталог вне runner-writable дерева либо закрепите проверенный inode до монтирования и сравнивайте mount именно с ним.Проверил исправления и текущую версию PR 44. Root-owned home (0711) закрывает подмену образа и точки монтирования между проверкой и systemd; writable runtime/config перенесены в подкаталоги runner пользователя. Замечания прошлых ревью устранены. Прошли тесты install-runner-idempotency, runner-config, install-runner-guard, bash -n, py_compile и git diff --check; отдельно проверил создание и повторную валидацию ext4 образа с реальными mkfs.ext4/blkid при mode 0711.