mirror of
https://github.com/ryan4yin/nix-config.git
synced 2026-11-13 12:13:46 +01:00
feat(secrets): add work gitconfig secret and a no-sudo replace flow (#346)
* feat(secrets): add work gitconfig secret, symlink it in home-manager * feat(secrets): add a no-sudo replace recipe for new secrets * refactor(secrets): move work gitconfig symlink into the desktop layer
This commit is contained in:
1 parent
9c44117f75
commit
0c8f0fe0c3
8 files changed
+84
-15
No files matched your search
@@ -50,14 +50,17 @@ The private repository groups the `.age` files: `desktop/` (only desktops decryp
|
||||
## 2. Add or change a secret
|
||||
|
||||
Do the mechanical work yourself: edit `secrets.nix` in `~/codes/nix-secrets`, run the
|
||||
non-interactive steps, bump the lock, and add the declaration and consumer. Hand back only what
|
||||
needs a human: the `edit`/`rekey` recipes in [`secrets/Justfile`](../../../secrets/Justfile) (they
|
||||
handle key material and `$EDITOR`) and anything else under `sudo`. Commits and pushes follow the
|
||||
global git rules.
|
||||
non-interactive steps, bump the lock, and add the declaration and consumer. For a new secret, or a
|
||||
full replacement, pipe the plaintext into the `replace` recipe: it removes the target first, so
|
||||
agenix only encrypts and needs no `sudo`. Hand back only what needs a human: the partial `edit` and
|
||||
the `rekey` recipes in [`secrets/Justfile`](../../../secrets/Justfile) (they decrypt the current
|
||||
value, so they handle key material, `$EDITOR`, and `sudo`) and anything else under `sudo`. Commits
|
||||
and pushes follow the global git rules.
|
||||
|
||||
1. In `~/codes/nix-secrets`, add the file under `desktop/` or `server/` with a matching
|
||||
`secrets.nix` entry, keyed by that exact path (e.g. `"./desktop/xxx.age"`), and the recipient set
|
||||
from core rule 3. Then ask the user to create or edit it on a desktop with the command in §3.
|
||||
from core rule 3. Then encrypt it with the `replace` recipe (§3); ask the user only when the
|
||||
plaintext has to come from an interactive session.
|
||||
2. Here: `just upp mysecrets` (commits the lock) or `nix flake update mysecrets` (leaves it for you
|
||||
to commit). `git diff flake.lock` should show only `mysecrets` moving.
|
||||
3. Declare it in `secrets/nixos.nix` or `secrets/darwin.nix` under the right
|
||||
@@ -65,7 +68,8 @@ global git rules.
|
||||
4. Point the consumer at the runtime path, from a module behind the same gate (core rule 4).
|
||||
5. `just test` and `just build-host <host>`, deploy, then verify as in §5.
|
||||
|
||||
Changing only a secret's value is steps 1, 2, and the deploy.
|
||||
Changing only a secret's value is steps 1, 2, and the deploy: use `replace` for a full replacement
|
||||
(the agent can run it) or `edit` for a partial change (needs the host key).
|
||||
|
||||
## 3. The private repository
|
||||
|
||||
@@ -75,12 +79,15 @@ The agenix operations run against `~/codes/nix-secrets`. Their single source is
|
||||
`secrets.nix`:
|
||||
|
||||
```bash
|
||||
just -f secrets/Justfile edit ./desktop/xxx.age # edit or create (interactive)
|
||||
just -f secrets/Justfile replace ./desktop/xxx.age < plaintext # add or fully replace (no sudo)
|
||||
just -f secrets/Justfile edit ./desktop/xxx.age # partial edit (sudo, $EDITOR)
|
||||
just -f secrets/Justfile rekey # re-encrypt after a recipient change
|
||||
```
|
||||
|
||||
`edit` passes `EDITOR=hx` to the root `agenix` process, because `sudo` resets the environment and
|
||||
the invoking user's `EDITOR` would not reach it; do not add `sudo -E` or set `EDITOR` yourself.
|
||||
`edit` and `rekey` decrypt the current value with the host key, so they need `sudo`; `replace` only
|
||||
encrypts to the recipients in `secrets.nix`, so it needs no `sudo`. `edit` passes `EDITOR=hx` to the
|
||||
root `agenix` process, because `sudo` resets the environment and the invoking user's `EDITOR` would
|
||||
not reach it; do not add `sudo -E` or set `EDITOR` yourself.
|
||||
|
||||
The repository keeps a single amended commit: `git commit --amend -a --no-edit`,
|
||||
`git reflog expire --expire-unreachable=now --all`, `git gc --prune=now`, then force push. Treat
|
||||
|
||||
Generated
+3
-3
@@ -484,10 +484,10 @@
|
||||
"mysecrets": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1790915701,
|
||||
"narHash": "sha256-ES+PUPSCJBE4pwhTBwgKsoBit3oXjrI/OJt5xxngeT4=",
|
||||
"lastModified": 1790919428,
|
||||
"narHash": "sha256-04+A6Hmjj2lXGanEWfqoQkkAGiQgVT0CMY5BTOlfcBo=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "37e3c3ee87ac649fc52f9314db1cdf672a3c62b0",
|
||||
"rev": "7b78dbb128be0ca52ac2815d5d8a73c5f883dada",
|
||||
"shallow": true,
|
||||
"type": "git",
|
||||
"url": "ssh://git@github.com/ryan4yin/nix-secrets.git"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
lib,
|
||||
pkgs,
|
||||
myvars,
|
||||
osConfig,
|
||||
...
|
||||
}:
|
||||
{
|
||||
@@ -14,6 +15,14 @@
|
||||
rm -f ${config.home.homeDirectory}/.gitconfig
|
||||
'';
|
||||
|
||||
# Work identity (email/name) lives encrypted in nix-secrets and is symlinked
|
||||
# to ~/work/.gitconfig, which `programs.git.includes` below makes git read for
|
||||
# repos under ~/work. `work-gitconfig` is only declared on desktops, so the
|
||||
# symlink follows the secret.
|
||||
home.file."work/.gitconfig" = lib.mkIf ((osConfig.age.secrets or { }) ? "work-gitconfig") {
|
||||
source = config.lib.file.mkOutOfStoreSymlink "/etc/agenix/work-gitconfig";
|
||||
};
|
||||
|
||||
# GitHub CLI tool
|
||||
# https://cli.github.com/manual/
|
||||
home.packages = [ pkgs.gh ];
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
# Work git identity (email/name), decrypted from nix-secrets. ~/work/.gitconfig
|
||||
# is what `programs.git.includes` (home/base/core/git.nix) tells git to read
|
||||
# for repos under ~/work.
|
||||
#
|
||||
# This lives in the desktop layer rather than core: base/gui is imported only
|
||||
# by hosts that declare the desktop secret (the NixOS desktops via
|
||||
# home/linux/gui.nix and macOS via home/darwin), so the consumer and the
|
||||
# secret share the same gate.
|
||||
home.file."work/.gitconfig".source =
|
||||
config.lib.file.mkOutOfStoreSymlink "/etc/agenix/work-gitconfig";
|
||||
}
|
||||
+6
-1
@@ -13,10 +13,15 @@ editor := "hx"
|
||||
default:
|
||||
@just --list --justfile {{justfile()}}
|
||||
|
||||
# Edit or create a secret (interactive); pipe the plaintext to write non-interactively
|
||||
# Partially edit an existing secret (interactive, needs sudo)
|
||||
edit file:
|
||||
sudo env EDITOR={{quote(editor)}} agenix -i {{identity}} -e {{quote(file)}}
|
||||
|
||||
# Create a secret, or fully replace one, from plaintext on stdin (no sudo)
|
||||
replace file:
|
||||
rm -f {{quote(file)}}
|
||||
agenix -e {{quote(file)}}
|
||||
|
||||
# Re-encrypt every secret after adding or changing a recipient key
|
||||
[confirm("Re-encrypt every secret in ~/codes/nix-secrets?")]
|
||||
rekey:
|
||||
|
||||
+11
-2
@@ -107,8 +107,17 @@ in
|
||||
}
|
||||
```
|
||||
|
||||
3. Create and edit the secret file `xxx.age` with the [`Justfile`](./Justfile) `edit` recipe
|
||||
(interactive, from the repository root); pipe the plaintext to write it non-interactively:
|
||||
3. Create the secret file `xxx.age` from the repository root with the [`Justfile`](./Justfile)
|
||||
`replace` recipe, piping the plaintext on stdin:
|
||||
|
||||
```shell
|
||||
just -f secrets/Justfile replace ./xxx.age < plaintext
|
||||
```
|
||||
|
||||
`replace` deletes the target first, so agenix only encrypts to the public keys in `secrets.nix` and
|
||||
never decrypts the old value — it needs no `sudo`. Use `edit` instead for a partial change to an
|
||||
existing secret: it decrypts the current value first with the host key
|
||||
(`/etc/ssh/ssh_host_ed25519_key`), so it needs `sudo`.
|
||||
|
||||
```shell
|
||||
just -f secrets/Justfile edit ./xxx.age
|
||||
|
||||
@@ -82,6 +82,12 @@
|
||||
file = "${mysecrets}/desktop/nushell-secrets.nu.age";
|
||||
}
|
||||
// user_readable;
|
||||
|
||||
# work git identity, symlinked to ~/work/.gitconfig by home-manager
|
||||
"work-gitconfig" = {
|
||||
file = "${mysecrets}/desktop/work-gitconfig.age";
|
||||
}
|
||||
// user_readable;
|
||||
};
|
||||
|
||||
# place secrets in /etc/
|
||||
@@ -105,6 +111,10 @@
|
||||
"agenix/nushell-secrets.nu" = {
|
||||
source = config.age.secrets."nushell-secrets.nu".path;
|
||||
};
|
||||
|
||||
"agenix/work-gitconfig" = {
|
||||
source = config.age.secrets."work-gitconfig".path;
|
||||
};
|
||||
};
|
||||
|
||||
# both the original file and the symlink should be readable and executable by the user
|
||||
|
||||
@@ -120,6 +120,12 @@ in
|
||||
file = "${mysecrets}/desktop/nushell-secrets.nu.age";
|
||||
}
|
||||
// user_readable;
|
||||
|
||||
# work git identity, symlinked to ~/work/.gitconfig by home-manager
|
||||
"work-gitconfig" = {
|
||||
file = "${mysecrets}/desktop/work-gitconfig.age";
|
||||
}
|
||||
// user_readable;
|
||||
};
|
||||
|
||||
# place secrets in /etc/
|
||||
@@ -148,6 +154,16 @@ in
|
||||
mode = "0400";
|
||||
user = myvars.username;
|
||||
};
|
||||
|
||||
# Used by a home-manager module, so it must be readable by the user.
|
||||
# Setting `mode` makes environment.etc copy the file instead of
|
||||
# symlinking the runtime secret, so the owner must be set as well —
|
||||
# otherwise it becomes a world-readable, root-owned copy of the secret.
|
||||
"agenix/work-gitconfig" = {
|
||||
source = config.age.secrets."work-gitconfig".path;
|
||||
mode = "0400";
|
||||
user = myvars.username;
|
||||
};
|
||||
};
|
||||
})
|
||||
|
||||
|
||||
Reference in new issue
Block a user