From a4984a6b3727066e91ba3bffaecabfb19ac436c0 Mon Sep 17 00:00:00 2001 From: oqyude Date: Fri, 9 Oct 2026 19:26:31 +0300 Subject: [PATCH] sops secrets path changed --- docs/arch/invariants.md | 52 +++++++++++++++++++++++++++- modules/containers/open-webui.nix | 10 +++++- modules/containers/remnawave.nix | 15 ++++++-- modules/containers/tape-rotation.nix | 4 ++- modules/server/authelia.nix | 13 ++++--- 5 files changed, 81 insertions(+), 13 deletions(-) diff --git a/docs/arch/invariants.md b/docs/arch/invariants.md index a75225a..4c613c1 100644 --- a/docs/arch/invariants.md +++ b/docs/arch/invariants.md @@ -42,6 +42,7 @@ | 4 | `100.64.0.0` = Tailscale sapphira | Назначен вручную; в 4 файлах | AGENTS.md §5 | | 5 | 3x-ui заморожен | Панель на latest; ядро Xray на 26.7.x; миграция 26.9 провалена | todo C1–C5 | | 6 | nftables на VDS — явная финальная политика | Сейчас ruleset без финального правила + конфликт с `firewall.*` | todo A3 | +| 7 | sops-пути — через `config.sops.secrets..path` | Любой `path =` override на sops-блоке делает хардкод-потребителя молча сломанным: rebuild зелёный, сервис стартует, контент пустой | этот коммит, см. §S1 | ## Сводка по ловушкам @@ -192,4 +193,53 @@ git-истории файла (последний коммит, где Q&A бы 6. каждый `listen.addr` — реально назначенный адрес, а не сеть; 7. все файлы в `secrets/` матчат `path_regex` из `.sops.yaml`. -**Вопрос:** какие из этих проверок ты хочешь, а какие — лишний CI? \ No newline at end of file +**Вопрос:** какие из этих проверок ты хочешь, а какие — лишний CI? + +--- + +## Шаблон инварианта + +Этот шаблон — для добавления новых инвариантов в этот документ +(и для зеркалирования в `AGENTS.md`). Та же 4-осевая структура +используется, чтобы вытащить «невидимое знание владельца» из +существующего кода в явное утверждение. + +1. **Утверждение** — что именно верно и нельзя менять без осознанного + решения. Один-два абзаца, никаких «может быть». +2. **Где** — конкретные файлы и строки. Агент не должен угадывать. +3. **Почему** — что происходит при нарушении. Лучше всего — сценарий + (rebuild / рантайм), а не абстрактный риск. +4. **Действие** — `todo X.Y`, ссылка на коммит, или явное + «закреплено автоматической проверкой (см. §11.2)». + +Дополнительные поля по необходимости: «ловушка» (выглядит сломанным, +намеренно), «обратное» (где это уже было сломано раньше), +«как проверить» (grep / CI). + +### S1 — sops-пути: `config.sops.secrets..path` + +- **Утверждение.** Любой потребитель sops-секрета в `modules/` ссылается + на путь через `${config.sops.secrets..path}`, а не через + литерал `"/run/secrets/"`. Атрибут sops-блока — единственный + источник истины для on-disk пути. +- **Где.** `modules/server/authelia.nix:107-108`, + `modules/containers/open-webui.nix:101`, + `modules/containers/tape-rotation.nix:63` — потребители + sops-материализации. Отдельный случай — композитный env-файл, + **не** sops: `modules/containers/remnawave.nix:16, 70, 138, 145` + — там `envFile` в `let`-биндинге, чтобы две копии пути не + разъехались. +- **Почему.** sops-nix материализует секреты на `/run/secrets/` + по умолчанию, но `sops.secrets..path` это переопределяет. + Любой такой override в будущей правке делает хардкод-потребителя + **молча** сломанным: `nixos-rebuild` проходит, сервис стартует, + файл читается — но контент от прошлой версии или пустой. Симптом + приходит из рантайма, не из CI. В `remnawave.nix` тот же риск + был двойной: путь хардкожен и в генераторе, и в контейнере, и + расхождение двух копий → silent breakage. +- **Действие.** Закреплено в коммите этой правки. Автоматической + проверки пока нет (см. §11.2 — список потенциальных CI-проверок). +- **Обратное.** До правки: `authelia.nix:51,108,109` — через хелпер + `sopsPath = name: "/run/secrets/${name}"`; `open-webui.nix:95`, + `tape-rotation.nix:61`, `remnawave.nix:61, 129, 136` — литеральный + хардкод. \ No newline at end of file diff --git a/modules/containers/open-webui.nix b/modules/containers/open-webui.nix index db96987..861e948 100644 --- a/modules/containers/open-webui.nix +++ b/modules/containers/open-webui.nix @@ -92,7 +92,15 @@ in # is the only source — and the container will refuse to start with # WEBUI_SECRET_KEY="" (env.py:762 — SystemExit). The error message is # the clear signal that the secret needs to be created. - environmentFiles = lib.optional (builtins.pathExists ./secrets/open-webui.env) "/run/secrets/open-webui-env"; + # Path comes from the sops block below (`config.sops.secrets..path`) + # rather than a hardcoded "/run/secrets/" — see invariant S1 + # in docs/arch/invariants.md. Guards the sopsFile existence so the + # block is optional; the same predicate is what `sops.secrets` uses + # to decide whether to declare the attr at all, so `.path` is only + # read when the secret actually exists. + environmentFiles = + lib.optional (builtins.pathExists ./secrets/open-webui.env) + config.sops.secrets."open-webui-env".path; volumes = [ "${panel}/data:/app/backend/data:rw" ]; diff --git a/modules/containers/remnawave.nix b/modules/containers/remnawave.nix index 23c424e..9b071cf 100644 --- a/modules/containers/remnawave.nix +++ b/modules/containers/remnawave.nix @@ -6,6 +6,15 @@ xlib, ... }: +let + # Composite env file path shared by the generator + # (remnawave-env.service below) and the container's `environmentFiles`. + # Lifting to a single binding prevents the two copies from drifting + # apart in future edits — see invariant S1 in docs/arch/invariants.md. + # Note: this is NOT a sops materialization (it's written by a oneshot), + # so `config.sops.secrets.<...>.path` is not the right primitive here. + envFile = "/run/secrets/remnawave-env"; +in { # Runtime virtualisation.podman = { @@ -58,7 +67,7 @@ # "WEBHOOK_URL" = "https://your-webhook-url.com/endpoint"; }; environmentFiles = [ - "/run/secrets/remnawave-env" + envFile ]; ports = [ "3003:3003/tcp" @@ -126,14 +135,14 @@ User = "root"; }; script = '' - cat > /run/secrets/remnawave-env < ${envFile} <` at - # /run/secrets/ by default. Hardcoding the path here keeps - # the module independent of how the secret attr is named; rename only - # the sops block below if a different path is needed. - sopsPath = name: "/run/secrets/${name}"; in { options.host.authelia = { @@ -105,8 +100,12 @@ in package = pkgs.authelia; secrets = lib.mkIf sopsReady { - jwtSecretFile = sopsPath "authelia-jwt-secret"; - storageEncryptionKeyFile = sopsPath "authelia-storage-encryption-key"; + # Read paths through `config.sops.secrets..path` (not via a + # hardcoded "/run/secrets/") so that any future `path =` + # override on the sops block below is picked up automatically — + # see invariant S1 in docs/arch/invariants.md. + jwtSecretFile = config.sops.secrets."authelia-jwt-secret".path; + storageEncryptionKeyFile = config.sops.secrets."authelia-storage-encryption-key".path; }; settings = {