feat(sops): Restructuring of sops keys
This commit is contained in:
1 parent
e1672d7cbe
commit
9d9c429033
7 files changed
+172
-53
No files matched your search
+6
-2
@@ -1,7 +1,11 @@
|
|||||||
keys:
|
keys:
|
||||||
- &kirill age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg
|
- &key age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg
|
||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: host/[^/]+/secrets/.+$
|
- path_regex: host/[^/]+/secrets/.+$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *kirill
|
- *key
|
||||||
|
- path_regex: user/[^/]+/secrets\.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *key
|
||||||
@@ -2,13 +2,6 @@
|
|||||||
sops = {
|
sops = {
|
||||||
age.keyFile = "/root/age.key";
|
age.keyFile = "/root/age.key";
|
||||||
|
|
||||||
secrets = {
|
secrets = { };
|
||||||
wallhaven = {
|
|
||||||
format = "binary";
|
|
||||||
sopsFile = ./wallhaven.pem;
|
|
||||||
owner = "kirill";
|
|
||||||
mode = "0400";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,5 +14,6 @@
|
|||||||
./user/packages/kdenlive.nix
|
./user/packages/kdenlive.nix
|
||||||
./user/quickshell.nix
|
./user/quickshell.nix
|
||||||
./user/wob.nix
|
./user/wob.nix
|
||||||
|
./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" = {};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,21 +5,36 @@
|
|||||||
## Как устроено
|
## Как устроено
|
||||||
|
|
||||||
- `.sops.yaml` в корне репозитория — список age-реципиентов и правила выбора ключа по пути файла.
|
- `.sops.yaml` в корне репозитория — список age-реципиентов и правила выбора ключа по пути файла.
|
||||||
- Зашифрованные секреты хоста: `host/<Host>/secrets/<файл>`.
|
- Секреты делятся на два уровня:
|
||||||
- Метаданные секретов: `host/<Host>/secrets/secrets.nix`, подключается из `host/<Host>/configuration.nix` через `imports`.
|
- **host** (системные, читает root): зашифрованные файлы в `host/<Host>/secrets/<файл>`, метаданные в `host/<Host>/secrets/secrets.nix`, подключается из `host/<Host>/configuration.nix` через `imports`. При активации расшифровываются в `/run/secrets/<имя>`.
|
||||||
- При активации sops-nix расшифровывает секреты в `/run/secrets/<имя>` (runtime, в `/nix/store` ничего не попадает).
|
- **user** (пользовательские, читает home-manager): один файл `user/<username>/secrets.yaml`, метаданные в `modules/user/secrets.nix`, подключается из `modules/user.nix` через `imports`. При активации расшифровываются в `~/.config/sops-nix/secrets/<имя>`.
|
||||||
- age-ключ выводится из существующего SSH-ключа.
|
- Расшифровка идёт в runtime, в `/nix/store` секреты не попадают.
|
||||||
|
- age-ключи выводятся из существующих SSH-ключей:
|
||||||
|
- host — `/root/age.key` (читает root при активации),
|
||||||
|
- user — `~/.config/sops/age/keys.txt` (читает пользователь при активации home-manager).
|
||||||
|
|
||||||
Текущее правило в `.sops.yaml`:
|
Текущие правила в `.sops.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
keys:
|
||||||
|
- &key age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg
|
||||||
|
|
||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: host/[^/]+/secrets/.+$
|
- path_regex: host/[^/]+/secrets/.+$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *key
|
||||||
|
- path_regex: user/[^/]+/secrets\.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *key
|
||||||
```
|
```
|
||||||
|
|
||||||
То есть правило выбирается по пути файла: всё, что лежит в `host/<Host>/secrets/`, шифруется на перечисленных реципиентов.
|
Правило выбирается по пути файла. Важно: `path_regex` проверяется против пути **входного** файла относительно корня репозитория, поэтому открытое значение нужно создавать по целевому пути (см. примеры ниже).
|
||||||
|
|
||||||
## Добавить новый секрет
|
## Host-секрет (системный)
|
||||||
|
|
||||||
|
### Добавить новый
|
||||||
|
|
||||||
1. Придумай имя, дальше для примера — `mysecret`.
|
1. Придумай имя, дальше для примера — `mysecret`.
|
||||||
|
|
||||||
@@ -29,13 +44,13 @@ creation_rules:
|
|||||||
printf '%s' 'СЕКРЕТНОЕ_ЗНАЧЕНИЕ' > host/ThinkPad/secrets/mysecret.plain
|
printf '%s' 'СЕКРЕТНОЕ_ЗНАЧЕНИЕ' > host/ThinkPad/secrets/mysecret.plain
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Зашифруй (приватный ключ не нужен — sops берёт публичного реципиента из `.sops.yaml`):
|
3. Зашифруй (приватный ключ не нужен — sops берёт публичного реципиента из `.sops.yaml`). Флаги указываются **до** позиционных аргументов:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix-shell -p sops --run \
|
nix-shell -p sops --run \
|
||||||
"sops --input-type binary --output-type binary \
|
"sops --input-type binary --output-type binary \
|
||||||
--encrypt host/ThinkPad/secrets/mysecret.plain \
|
--encrypt --output host/ThinkPad/secrets/mysecret.enc \
|
||||||
--output host/ThinkPad/secrets/mysecret.enc"
|
host/ThinkPad/secrets/mysecret.plain"
|
||||||
rm host/ThinkPad/secrets/mysecret.plain
|
rm host/ThinkPad/secrets/mysecret.plain
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -76,34 +91,114 @@ creation_rules:
|
|||||||
sudo ls -l /run/secrets/mysecret
|
sudo ls -l /run/secrets/mysecret
|
||||||
```
|
```
|
||||||
|
|
||||||
## Изменить или посмотреть существующий секрет
|
### Изменить или посмотреть существующий секрет
|
||||||
|
|
||||||
Используется приватный ключ (`/root/age.key`), поэтому нужен root:
|
Используется приватный ключ (`/root/age.key`), поэтому нужен root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo SOPS_AGE_KEY_FILE=/root/age.key nix-shell -p sops --run \
|
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`.
|
`sops` сам не ищет `/root/age.key`, путь передаётся через `SOPS_AGE_KEY_FILE`.
|
||||||
|
Для бинарных файлов нужны `--input-type`/`--output-type`, иначе sops не сможет определить формат.
|
||||||
|
|
||||||
|
## User-секрет (home-manager)
|
||||||
|
|
||||||
|
Все пользовательские секреты лежат в одном файле `user/<username>/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
|
```bash
|
||||||
sudo nix-shell -p ssh-to-age --run \
|
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
|
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
|
```bash
|
||||||
nix-shell -p ssh-to-age --run \
|
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/<Host>/secrets/secrets.nix
|
host/<Host>/secrets/secrets.nix
|
||||||
@@ -125,46 +220,60 @@ sudo SOPS_AGE_KEY_FILE=/root/age.key nix-shell -p sops --run \
|
|||||||
|
|
||||||
```nix
|
```nix
|
||||||
{
|
{
|
||||||
sops.age.keyFile = "/root/age.key";
|
sops = {
|
||||||
secrets = {
|
age.keyFile = "/root/age.key";
|
||||||
# ...
|
secrets = { };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
И добавь `./secrets/secrets.nix` в `imports` в `host/<Host>/configuration.nix`.
|
И добавь `./secrets/secrets.nix` в `imports` в `host/<Host>/configuration.nix`.
|
||||||
|
|
||||||
5. `git add` + ребилд:
|
6. Для пользовательских секретов создай `user/<username>/secrets.yaml` (см. раздел «User-секрет») и убедись, что `modules/user.nix` подключает `./user/secrets.nix`.
|
||||||
|
|
||||||
|
7. `git add` + ребилд:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add host/<Host>/secrets/
|
git add host/<Host>/secrets/ user/<username>/secrets.yaml modules/user/secrets.nix
|
||||||
sudo nixos-rebuild switch --flake .#<Host>
|
sudo nixos-rebuild switch --flake .#<Host>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Добавить устройство к существующим секретам
|
## Добавить устройство к существующим секретам
|
||||||
|
|
||||||
1. Получи age-реципиент устройства (шаг 3 выше).
|
1. Получи age-реципиент устройства (шаг 4 выше).
|
||||||
|
|
||||||
2. Добавь его в `.sops.yaml`:
|
2. Добавь его в `.sops.yaml` в оба правила:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
keys:
|
keys:
|
||||||
- &kirill age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg
|
- &key age1tx6jdr6535xxeygpwkkgvdwc24tulr420cm7gk56xzfp0slzeqks2nwpyg
|
||||||
- &newdevice age1...новый_реципиент... # замени на реальный
|
- &newdevice age1...новый_реципиент... # замени на реальный
|
||||||
|
|
||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: host/[^/]+/secrets/.+$
|
- path_regex: host/[^/]+/secrets/.+$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *kirill
|
- *key
|
||||||
|
- *newdevice
|
||||||
|
- path_regex: user/[^/]+/secrets\.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *key
|
||||||
- *newdevice
|
- *newdevice
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Перешифруй существующие секреты под новый список реципиентов (нужен приватный ключ, значит root):
|
3. Перешифруй существующие секреты под новый список реципиентов (нужен приватный ключ). Для host-секретов — root и `/root/age.key`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo SOPS_AGE_KEY_FILE=/root/age.key nix-shell -p sops --run \
|
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` + ребилд на этом устройстве.
|
4. `git add` + ребилд на этом устройстве.
|
||||||
@@ -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
|
||||||
Reference in new issue
Block a user