From 9d9c429033dbb14bebc174e3694ce240b3d75b00 Mon Sep 17 00:00:00 2001 From: Kirill Date: Sat, 17 Oct 2026 17:17:23 +0300 Subject: [PATCH] feat(sops): Restructuring of sops keys --- .sops.yaml | 8 +- host/ThinkPad/secrets/secrets.nix | 9 +- host/ThinkPad/secrets/wallhaven.pem | 14 --- modules/user.nix | 1 + modules/user/secrets.nix | 8 ++ secret.md | 167 +++++++++++++++++++++++----- user/kirill/secrets.yaml | 18 +++ 7 files changed, 172 insertions(+), 53 deletions(-) delete mode 100644 host/ThinkPad/secrets/wallhaven.pem create mode 100644 modules/user/secrets.nix create mode 100644 user/kirill/secrets.yaml diff --git a/.sops.yaml b/.sops.yaml index c993f72..9748098 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -1,7 +1,11 @@ keys: - - &kirill age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg + - &key age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg creation_rules: - path_regex: host/[^/]+/secrets/.+$ key_groups: - age: - - *kirill + - *key + - path_regex: user/[^/]+/secrets\.yaml$ + key_groups: + - age: + - *key diff --git a/host/ThinkPad/secrets/secrets.nix b/host/ThinkPad/secrets/secrets.nix index b1d5ecc..4d2ef6d 100644 --- a/host/ThinkPad/secrets/secrets.nix +++ b/host/ThinkPad/secrets/secrets.nix @@ -2,13 +2,6 @@ sops = { age.keyFile = "/root/age.key"; - secrets = { - wallhaven = { - format = "binary"; - sopsFile = ./wallhaven.pem; - owner = "kirill"; - mode = "0400"; - }; - }; + secrets = { }; }; } diff --git a/host/ThinkPad/secrets/wallhaven.pem b/host/ThinkPad/secrets/wallhaven.pem deleted file mode 100644 index 8a33be6..0000000 --- a/host/ThinkPad/secrets/wallhaven.pem +++ /dev/null @@ -1,14 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:0Dw6uMFnVNZKg2ANaHRcMicOPQ7U+mi5V6dia47GDUMM,iv:ScI+ESgBXXcu5hlDkP6m50FL8lp7S3E2cLbf1M9ZAbQ=,tag:VBwdRDyWV3unNUhSVguu2g==,type:str]", - "sops": { - "age": [ - { - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3bWFwMUlwMmxJenZib2Qy\nRk5TMkVtQS9kLzgyWlFNbFVFbzJmUVNXZFRBCkVKQldtcTd5ZElzcVpienhDM3dh\nTmFQZTN0KytZVnZQMmNZNGFQSUlIbXMKLS0tIERBVmhGMmgwZmNwTHB6eEZ0bFJX\nWFN2cVE0WmFna2V5UWE2NXJ6OU9YM2cKWitmQY994raC3Kz/QYXVmiONTomW/JYB\n0PADqEp32xsw/gFnTwi7zbfFPD63ME2PYJ4I9wNKcgR/cFoW2Sv8Jg==\n-----END AGE ENCRYPTED FILE-----\n", - "recipient": "age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg" - } - ], - "lastmodified": "2026-10-16T17:19:39Z", - "mac": "ENC[AES256_GCM,data:+CSe7gGwiSANibzDJ4DVTq1OXykiR1vv49szqlxCZyAeKqnxbEjaNFCdslxPAa8SGttDqDxM9aO59DSO3AKxzR9J4kfKqZ7eA8/cNC0DKltzS4AkLkPcGjFpctdUiYENXaPrtk1tV8e/GeW1//f3R61hzAEama8jjkoeAYaDZIM=,iv:aD4c8y2a8whXyG0ig05bryZPfh/NrETeth+O5D4/bPE=,tag:r1EbYa0lsr/u2Rw6ZFjYsQ==,type:str]", - "version": "3.13.3" - } -} diff --git a/modules/user.nix b/modules/user.nix index 6e0d3cf..71a3051 100644 --- a/modules/user.nix +++ b/modules/user.nix @@ -14,5 +14,6 @@ ./user/packages/kdenlive.nix ./user/quickshell.nix ./user/wob.nix + ./user/secrets.nix ]; } diff --git a/modules/user/secrets.nix b/modules/user/secrets.nix new file mode 100644 index 0000000..1bc1150 --- /dev/null +++ b/modules/user/secrets.nix @@ -0,0 +1,8 @@ +{ config, ... }: { + sops = { + defaultSopsFile = ../../user/${config.home.username}/secrets.yaml; + age.keyFile = "${config.home.homeDirectory}/.config/sops/age/keys.txt"; + + secrets."tokens/apis/wallhaven" = {}; + }; +} diff --git a/secret.md b/secret.md index a3b6987..19ff2e9 100644 --- a/secret.md +++ b/secret.md @@ -5,21 +5,36 @@ ## Как устроено - `.sops.yaml` в корне репозитория — список age-реципиентов и правила выбора ключа по пути файла. -- Зашифрованные секреты хоста: `host//secrets/<файл>`. -- Метаданные секретов: `host//secrets/secrets.nix`, подключается из `host//configuration.nix` через `imports`. -- При активации sops-nix расшифровывает секреты в `/run/secrets/<имя>` (runtime, в `/nix/store` ничего не попадает). -- age-ключ выводится из существующего SSH-ключа. +- Секреты делятся на два уровня: + - **host** (системные, читает root): зашифрованные файлы в `host//secrets/<файл>`, метаданные в `host//secrets/secrets.nix`, подключается из `host//configuration.nix` через `imports`. При активации расшифровываются в `/run/secrets/<имя>`. + - **user** (пользовательские, читает home-manager): один файл `user//secrets.yaml`, метаданные в `modules/user/secrets.nix`, подключается из `modules/user.nix` через `imports`. При активации расшифровываются в `~/.config/sops-nix/secrets/<имя>`. +- Расшифровка идёт в runtime, в `/nix/store` секреты не попадают. +- age-ключи выводятся из существующих SSH-ключей: + - host — `/root/age.key` (читает root при активации), + - user — `~/.config/sops/age/keys.txt` (читает пользователь при активации home-manager). -Текущее правило в `.sops.yaml`: +Текущие правила в `.sops.yaml`: ```yaml +keys: + - &key age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg + creation_rules: - path_regex: host/[^/]+/secrets/.+$ + key_groups: + - age: + - *key + - path_regex: user/[^/]+/secrets\.yaml$ + key_groups: + - age: + - *key ``` -То есть правило выбирается по пути файла: всё, что лежит в `host//secrets/`, шифруется на перечисленных реципиентов. +Правило выбирается по пути файла. Важно: `path_regex` проверяется против пути **входного** файла относительно корня репозитория, поэтому открытое значение нужно создавать по целевому пути (см. примеры ниже). -## Добавить новый секрет +## Host-секрет (системный) + +### Добавить новый 1. Придумай имя, дальше для примера — `mysecret`. @@ -29,13 +44,13 @@ creation_rules: printf '%s' 'СЕКРЕТНОЕ_ЗНАЧЕНИЕ' > host/ThinkPad/secrets/mysecret.plain ``` -3. Зашифруй (приватный ключ не нужен — sops берёт публичного реципиента из `.sops.yaml`): +3. Зашифруй (приватный ключ не нужен — sops берёт публичного реципиента из `.sops.yaml`). Флаги указываются **до** позиционных аргументов: ```bash nix-shell -p sops --run \ "sops --input-type binary --output-type binary \ - --encrypt host/ThinkPad/secrets/mysecret.plain \ - --output host/ThinkPad/secrets/mysecret.enc" + --encrypt --output host/ThinkPad/secrets/mysecret.enc \ + host/ThinkPad/secrets/mysecret.plain" rm host/ThinkPad/secrets/mysecret.plain ``` @@ -76,34 +91,114 @@ creation_rules: sudo ls -l /run/secrets/mysecret ``` -## Изменить или посмотреть существующий секрет +### Изменить или посмотреть существующий секрет Используется приватный ключ (`/root/age.key`), поэтому нужен root: ```bash sudo SOPS_AGE_KEY_FILE=/root/age.key nix-shell -p sops --run \ - "sops host/ThinkPad/secrets/wallhaven.pem" + "sops --input-type binary --output-type binary host/ThinkPad/secrets/mysecret.enc" ``` `sops` сам не ищет `/root/age.key`, путь передаётся через `SOPS_AGE_KEY_FILE`. +Для бинарных файлов нужны `--input-type`/`--output-type`, иначе sops не сможет определить формат. + +## User-секрет (home-manager) + +Все пользовательские секреты лежат в одном файле `user//secrets.yaml`. Значения — вложенные ключи YAML, а имя секрета в nix-коде совпадает с путём этого ключа (`/` = вложенность). Например, секрет `tokens/apis/wallhaven` читается из: + +```yaml +tokens: + apis: + wallhaven: ЗНАЧЕНИЕ +``` + +Путь расшифрованного файла: `~/.config/sops-nix/secrets/tokens/apis/wallhaven`. + +### Добавить новый + +1. Придумай имя, дальше для примера — `tokens/apis/wallhaven`. + +2. Добавь значение в общий файл секретов (sops откроет расшифрованное содержимое в `$EDITOR` и зашифрует при сохранении; путь совпадает с `path_regex`, поэтому правило применится автоматически): + + ```bash + nix-shell -p sops --run "sops user/kirill/secrets.yaml" + ``` + + Если файла ещё нет, создай его сразу по целевому пути и зашифруй на месте: + + ```bash + printf 'tokens:\n apis:\n wallhaven: ЗНАЧЕНИЕ\n' > user/kirill/secrets.yaml + nix-shell -p sops --run "sops --encrypt --in-place user/kirill/secrets.yaml" + ``` + +3. Опиши секрет в `modules/user/secrets.nix`: + + ```nix + { config, ... }: { + sops = { + defaultSopsFile = ../../user/${config.home.username}/secrets.yaml; + age.keyFile = "${config.home.homeDirectory}/.config/sops/age/keys.txt"; + + secrets."tokens/apis/wallhaven" = {}; + }; + } + ``` + +4. Используй в home-manager конфиге через путь: + + ```nix + config.sops.secrets."tokens/apis/wallhaven".path + ``` + +5. `git add` — **Nix видит только файлы, добавленные в git** — и ребилд: + + ```bash + git add user/kirill/secrets.yaml modules/user/secrets.nix modules/user.nix + sudo nixos-rebuild switch --flake .#ThinkPad + ``` + +6. Проверка: + + ```bash + ls -l ~/.config/sops-nix/secrets/tokens/apis/wallhaven + ``` + +### Изменить или посмотреть существующий секрет + +```bash +nix-shell -p sops --run "sops user/kirill/secrets.yaml" +``` + +sops подхватывает `~/.config/sops/age/keys.txt` автоматически. Если путь другой — задай `SOPS_AGE_KEY_FILE`. ## Новое устройство -1. Убедись, что на устройстве есть приватный SSH-ключ, из которого выводится age-ключ. Ключ должен быть **без пароля** — иначе расшифровка при активации будет требовать ввода. +1. Убедись, что на устройстве есть приватный SSH-ключ, из которого выводится age-ключ. Ключ должен быть **без пароля** — иначе расшифровка при активации будет требовать ввода. На текущей машине это `~/.ssh/id_ed25519_sops`. -2. Создай age-ключ: +2. Создай age-ключ хоста (root читает его при активации): ```bash sudo nix-shell -p ssh-to-age --run \ - "ssh-to-age -private-key -i /home/kirill/.ssh/<ваш_ключ> -o /root/age.key" + "ssh-to-age -private-key -i /home/kirill/.ssh/id_ed25519_sops -o /root/age.key" sudo chmod 600 /root/age.key ``` -3. Проверь, что публичный age-реципиент устройства совпадает с тем, что в `.sops.yaml`: +3. Создай age-ключ пользователя (home-manager читает его при активации): + + ```bash + mkdir -p ~/.config/sops/age + nix-shell -p ssh-to-age --run \ + "ssh-to-age -private-key -i ~/.ssh/id_ed25519_sops -o ~/.config/sops/age/keys.txt" + chmod 700 ~/.config/sops/age + chmod 600 ~/.config/sops/age/keys.txt + ``` + +4. Проверь, что публичный age-реципиент устройства совпадает с тем, что в `.sops.yaml`: ```bash nix-shell -p ssh-to-age --run \ - "ssh-to-age -i /home/kirill/.ssh/<ваш_ключ>.pub" + "ssh-to-age -i ~/.ssh/id_ed25519_sops.pub" ``` Должно вывести: @@ -114,7 +209,7 @@ sudo SOPS_AGE_KEY_FILE=/root/age.key nix-shell -p sops --run \ Если вывод другой — добавь новый реципиент (см. следующий раздел). -4. Создай структуру секретов хоста: +5. Создай структуру секретов хоста: ``` host//secrets/secrets.nix @@ -125,46 +220,60 @@ sudo SOPS_AGE_KEY_FILE=/root/age.key nix-shell -p sops --run \ ```nix { - sops.age.keyFile = "/root/age.key"; - secrets = { - # ... + sops = { + age.keyFile = "/root/age.key"; + secrets = { }; }; } ``` И добавь `./secrets/secrets.nix` в `imports` в `host//configuration.nix`. -5. `git add` + ребилд: +6. Для пользовательских секретов создай `user//secrets.yaml` (см. раздел «User-секрет») и убедись, что `modules/user.nix` подключает `./user/secrets.nix`. + +7. `git add` + ребилд: ```bash - git add host//secrets/ + git add host//secrets/ user//secrets.yaml modules/user/secrets.nix sudo nixos-rebuild switch --flake .# ``` ## Добавить устройство к существующим секретам -1. Получи age-реципиент устройства (шаг 3 выше). +1. Получи age-реципиент устройства (шаг 4 выше). -2. Добавь его в `.sops.yaml`: +2. Добавь его в `.sops.yaml` в оба правила: ```yaml keys: - - &kirill age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg + - &key age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg - &newdevice age1...новый_реципиент... # замени на реальный creation_rules: - path_regex: host/[^/]+/secrets/.+$ key_groups: - age: - - *kirill + - *key + - *newdevice + - path_regex: user/[^/]+/secrets\.yaml$ + key_groups: + - age: + - *key - *newdevice ``` -3. Перешифруй существующие секреты под новый список реципиентов (нужен приватный ключ, значит root): +3. Перешифруй существующие секреты под новый список реципиентов (нужен приватный ключ). Для host-секретов — root и `/root/age.key`: ```bash sudo SOPS_AGE_KEY_FILE=/root/age.key nix-shell -p sops --run \ - "sops updatekeys host/ThinkPad/secrets/wallhaven.pem" + "sops updatekeys host/ThinkPad/secrets/mysecret.enc" + ``` + + Для user-секретов — пользовательский ключ: + + ```bash + SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt nix-shell -p sops --run \ + "sops updatekeys user/kirill/secrets.yaml" ``` 4. `git add` + ребилд на этом устройстве. diff --git a/user/kirill/secrets.yaml b/user/kirill/secrets.yaml new file mode 100644 index 0000000..f3ecc39 --- /dev/null +++ b/user/kirill/secrets.yaml @@ -0,0 +1,18 @@ +tokens: + apis: + wallhaven: ENC[AES256_GCM,data:pW9QiCB8yM2xSZQG96qCKOGOJbSFFrFxu8s1tHfmzWw=,iv:sezKkABxqVtd3mK8+WxEDPd+GtpyBcUJVLDlj/D68nU=,tag:HWE2QZf66dZGjK21WcxtiA==,type:str] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUVVBNekFNbWNhSEx1d0c0 + NHRNWW9iR3FSMnZIMkRKQy9pTTIzV2R6VG1BClJ4clI5SzRCTnJhZ25JSEJ3dkFF + QVE4VUt3YXBKZnRES3dXSjJxM2RJU0kKLS0tIExKci9iQWJ4VVdyU1dteVZXb21k + S1VhaW5rQVkzUWMweHdNaThmblRYWWMKrhP0P4ftPNKfjss5JDa4SESSYXdop/U/ + uIfI1RPTXbReF124NIIEvJ6AsiQKDDCjpgT0m/1i/U3yIF1UejPiQg== + -----END AGE ENCRYPTED FILE----- + recipient: age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg + lastmodified: "2026-10-17T13:36:46Z" + mac: ENC[AES256_GCM,data:stlsKKQhQL6+utokuXvNZH1dlWfEeUa5nORN5eQnbNcXbo1MrFVz9wVDqVtpgZOc3/v8p4YaHXLhQl8/RpawAwQH03gvcZ5zt1aA+Z7K6yh9ONRSnwz2lUXwCbpaf0xYRIlzv7pSPBJdLfYSN3kaEwVKrItAsPxuHEI/WEh8M3Y=,iv:W1vLWvQ7b635MpbUC7m3ATBPa/MujvAji2/MN9kldq4=,tag:DlVg1LCVvjPOg4nRP3rjAA==,type:str] + unencrypted_suffix: _unencrypted + version: 3.13.3