Compare commits
6 Commits
cb7cd3d4b0
...
11d7cb053c
| Author | SHA1 | Date | |
|---|---|---|---|
| 11d7cb053c | |||
| ec40892560 | |||
| a6ada9dac3 | |||
| 42ff195556 | |||
| 60738f65c2 | |||
| 61ce9cc1be |
@@ -18,10 +18,43 @@ These secrets decrypt in the ordinary activation stage rather than the early pre
|
|||||||
|
|
||||||
## Acceptance criteria
|
## Acceptance criteria
|
||||||
|
|
||||||
- [ ] A secrets file for `neogaia` exists, encrypted to the admin identity and `neogaia` only — not to any other recipient
|
- [x] A secrets file for `neogaia` exists, encrypted to the admin identity and `neogaia` only — not to any other recipient
|
||||||
- [ ] `neogaia`'s SSH host private keys are stored in it
|
- [x] `neogaia`'s SSH host private keys are stored in it
|
||||||
- [ ] The corresponding host public keys are committed in plaintext
|
- [x] The corresponding host public keys are committed in plaintext
|
||||||
- [ ] The SSH daemon no longer generates its own host keys and reads the decrypted paths
|
- [x] The SSH daemon no longer generates its own host keys and reads the decrypted paths
|
||||||
- [ ] The host key secrets are declared beside the SSH daemon configuration that consumes them
|
- [x] The host key secrets are declared beside the SSH daemon configuration that consumes them
|
||||||
- [ ] `nix flake check` builds the `neogaia` toplevel
|
- [x] `nix flake check` builds the `neogaia` toplevel
|
||||||
- [ ] Manual confirmation: after activation the secrets materialize with the declared ownership and mode, the daemon adopts the restored keys, and the host fingerprint presented to a client is unchanged
|
- [x] Manual confirmation: after activation the secrets materialize with the declared ownership and mode, the daemon adopts the restored keys, and the host fingerprint presented to a client is unchanged
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
**Both key types were preserved, not just ed25519.**
|
||||||
|
The running daemon served an ed25519 and an RSA host key, and a client that pinned either would break if only one were carried over.
|
||||||
|
Both private halves are in `secrets/neogaia.yaml`.
|
||||||
|
|
||||||
|
**The decrypted keys stay at their default `/run/secrets/` paths.**
|
||||||
|
The first attempt set each secret's `path` to the conventional `/etc/ssh/ssh_host_*_key`, which has sops plant a symlink inside a directory NixOS otherwise manages through `setup-etc`.
|
||||||
|
It worked, but it buys nothing: `sshd` reads whatever `HostKey` names, and the extra `/etc` interaction depends on activation ordering that nothing in the config pins.
|
||||||
|
The `HostKey` lines now interpolate `config.sops.secrets.<name>.path`, so the daemon and the secret cannot disagree about where the key is.
|
||||||
|
`/etc/ssh` ends up holding no key material at all.
|
||||||
|
|
||||||
|
**`restartUnits = [ "sshd.service" ]` is not in the plan and is needed.**
|
||||||
|
`sshd` reads its host keys once at startup.
|
||||||
|
Without this, re-keying the host would rewrite the decrypted files while the daemon kept serving the old keys from memory until some unrelated restart — silently, and precisely the identity drift this task exists to prevent.
|
||||||
|
The plan's manual criterion would not have caught it, since it was verified on a switch where the keys had not changed.
|
||||||
|
|
||||||
|
**The committed public keys have no consumer yet.**
|
||||||
|
An intermediate version deployed them to `/etc/ssh` via `environment.etc`.
|
||||||
|
That was dropped as scope the task did not ask for: `sshd` derives the public half from the private key at load, so nothing read them.
|
||||||
|
They are committed, per the criterion, and the task that distributes `known_hosts` to clients is where they acquire a use.
|
||||||
|
|
||||||
|
**Verification was stronger than a before/after comparison.**
|
||||||
|
After activation the leftover `/etc/ssh/ssh_host_*_key` symlinks from the first attempt were removed and `sshd` restarted with no key material anywhere in `/etc/ssh`.
|
||||||
|
It came back active and presented `SHA256:2ysuBX0+Z6GbdCTujz5JHX6rqnJzIyWhYNrxdhhGwEM` (ed25519) and `SHA256:y6Tl3P/FvfufblfG059BfCsSkMYX8Zk2EpFQvWzCAew` (RSA) — identical to the pre-change fingerprints.
|
||||||
|
The generated `sshd-keygen.service` has no `ExecStart` at all, which is what confirms generation is off rather than merely idle.
|
||||||
|
Separately, the encrypted file was decrypted with the host identity and diffed against the live private keys before anything was changed.
|
||||||
|
|
||||||
|
**Task 0010's handoff about `authorizedKeys` is deliberately left open.**
|
||||||
|
That note proposed settling it here, on the grounds that SSH is not a recovery route while no key is authorized.
|
||||||
|
It is not an acceptance criterion of this task, and choosing which public key to trust is the operator's call rather than one to infer.
|
||||||
|
It wants its own task, and remains a real gap: a decryption failure that locks the console still has no network fallback.
|
||||||
|
|||||||
51
.claude/tasks/0019-user-ssh-keys-and-access-policy.md
Normal file
51
.claude/tasks/0019-user-ssh-keys-and-access-policy.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
blocked-by: 0011-neogaia-ssh-host-keys
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The operator's SSH client key becomes a secret, and which machines may reach which becomes a declared policy rather than a hand-edited list.
|
||||||
|
|
||||||
|
Today the key that authenticates pushes to the remote exists only as a file created by hand on one laptop.
|
||||||
|
It is in no secrets file and no module, so a reimage destroys it.
|
||||||
|
That is worse than losing a host key: a lost host key makes clients complain about `known_hosts`, whereas a lost client key locks the operator out of the remote until a new one is generated and registered through the forge's web interface.
|
||||||
|
|
||||||
|
Each machine gets its **own** client identity rather than one shared across the fleet.
|
||||||
|
The private half lives in that machine's own secrets file, so it is readable by that machine and the admin identity alone.
|
||||||
|
A compromised machine therefore surrenders only its own key, and withdrawing a machine's access means removing one public key rather than re-keying every other machine.
|
||||||
|
The public halves are committed in plaintext, as the host public keys are, since publishing them is their function.
|
||||||
|
|
||||||
|
The private half decrypts at activation and is readable only by the primary user.
|
||||||
|
Following the host keys, the client is pointed at the decrypted path rather than having a copy written into the user's home, so there is one authoritative location for the key and no copy to drift.
|
||||||
|
|
||||||
|
Access is expressed as a policy over machine roles, not as a per-host list of authorized keys.
|
||||||
|
A **workstation** may reach every machine in the fleet.
|
||||||
|
A **server** may reach other servers only.
|
||||||
|
Consequently every machine authorizes the workstation keys, and servers additionally authorize the server keys, while a workstation never authorizes a server's key — so a compromised server cannot reach the operator's own machines.
|
||||||
|
|
||||||
|
This wants a single declaration of the fleet, naming each machine's role and its client public key, from which every host derives the set it authorizes.
|
||||||
|
Registering a new machine is then declaring its role in one place, rather than an edit to every other host's configuration.
|
||||||
|
|
||||||
|
Only `neogaia` exists today, so the server half of the policy has nothing to act on and cannot be exercised.
|
||||||
|
It is built and recorded now so that the desktop and the three planned servers are a role declaration rather than a redesign.
|
||||||
|
|
||||||
|
Adopt the key already present on `neogaia` rather than generating a fresh one.
|
||||||
|
It is already registered with the remote, so adopting it keeps pushes working, whereas replacing it would require registering the new key through the web interface before the old one stops being used — an ordering that locks the operator out if it goes wrong.
|
||||||
|
Machines that do not exist yet generate their own key during provisioning, alongside the age identity.
|
||||||
|
|
||||||
|
This also settles the gap left open by task 0010, where the daemon accepts connections but authorizes no key, so a failed decryption that locks the console has no network fallback.
|
||||||
|
Note that the fallback only becomes real once a second machine exists to connect from.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `neogaia` has its own client SSH identity, distinct from its host keys, adopted from the key already on the machine
|
||||||
|
- [ ] Its private half is stored in `neogaia`'s own secrets file, encrypted to the admin identity and `neogaia` alone
|
||||||
|
- [ ] Its public half is committed in plaintext
|
||||||
|
- [ ] The private half decrypts at activation, readable only by the primary user and not by other accounts
|
||||||
|
- [ ] The SSH client uses the decrypted key with no hand-placed copy in the user's home directory
|
||||||
|
- [ ] Each machine declares a role, and the keys it authorizes follow from that role rather than from a per-host list
|
||||||
|
- [ ] Workstation keys are authorized on every machine
|
||||||
|
- [ ] Server keys are authorized on servers only, and on no workstation
|
||||||
|
- [ ] Registering a new machine is a role declaration in one place, requiring no edit to any other host
|
||||||
|
- [ ] `nix flake check` builds the `neogaia` toplevel
|
||||||
|
- [ ] Manual confirmation: the key materializes with the declared ownership and mode, an authenticated push to the remote still succeeds, and `neogaia` accepts an SSH connection offering the adopted key
|
||||||
@@ -9,6 +9,15 @@ keys:
|
|||||||
- &neogaia age14a04vphzjq74epfrz9a09wjw8lzchtru84awzuq2n45d8f42ychqjs89qe
|
- &neogaia age14a04vphzjq74epfrz9a09wjw8lzchtru84awzuq2n45d8f42ychqjs89qe
|
||||||
|
|
||||||
creation_rules:
|
creation_rules:
|
||||||
|
# Material belonging to one machine.
|
||||||
|
# No machine other than the one named is a recipient, so a host that is
|
||||||
|
# compromised cannot decrypt another's material.
|
||||||
|
- path_regex: secrets/neogaia\.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *neogaia
|
||||||
|
|
||||||
# Material common to every machine, so it is stored once rather than per host.
|
# Material common to every machine, so it is stored once rather than per host.
|
||||||
- path_regex: secrets/shared\.yaml$
|
- path_regex: secrets/shared\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
|
|||||||
15
CLAUDE.md
15
CLAUDE.md
@@ -47,9 +47,18 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
|
|||||||
That is what lets the `nyx-cache.chaotic.cx` binary cache hit instead of compiling the CachyOS kernel from source; the tradeoff is that chaotic packages do not see our `unstable`/`stable` overlays.
|
That is what lets the `nyx-cache.chaotic.cx` binary cache hit instead of compiling the CachyOS kernel from source; the tradeoff is that chaotic packages do not see our `unstable`/`stable` overlays.
|
||||||
- The remote is self-hosted Gitea (`git.alexion.dev`), and the intended CLI is `gitea-axi` rather than `tea`.
|
- The remote is self-hosted Gitea (`git.alexion.dev`), and the intended CLI is `gitea-axi` rather than `tea`.
|
||||||
`gitea-axi` resolves the repository from the `origin` remote and takes credentials from the `axi` tea login, so both are implicit inside a checkout.
|
`gitea-axi` resolves the repository from the `origin` remote and takes credentials from the `axi` tea login, so both are implicit inside a checkout.
|
||||||
**None of it is installed on the NixOS build.** No `gitea-axi`, no `tea`, no `gh`, no tea login under `~/.config/tea`, and no `GITEA_*` environment — the flake names `gitea-axi` only as the claude-code module's `SessionStart` hook command and never packages it, so that hook invokes a binary that is not on `PATH`.
|
**No forge CLI is installed on the NixOS build.** No `gitea-axi`, no `tea`, no `gh` on `PATH` — the flake names `gitea-axi` only as the claude-code module's `SessionStart` hook command and never packages it, so that hook invokes a binary that is not there.
|
||||||
Pull requests therefore cannot be opened from this machine until a module provides the tool and its credentials; branches can only be pushed.
|
Credentials, however, do exist: `~/.config/tea/config.yml` holds a token-bearing login named `alexion` (not `axi`), so pull requests **can** be opened with `nix run nixpkgs#tea -- pr create --login alexion --repo alexion/dotfiles --base main --head <branch> ...`.
|
||||||
The earlier claim that `tea` remains installed described the machine while it still ran CachyOS with these tools installed by hand.
|
The `--repo` flag is required on that path, since `tea` resolves `origin` only for a login whose SSH host matches.
|
||||||
|
The same token reads PR discussion, which `tea` itself does poorly: `tea pr <n> --comments` prints only the body, and `-f comments` returns no comments field at all.
|
||||||
|
Use the API instead, taking the token from `.logins[] | select(.name=="alexion") | .token`.
|
||||||
|
Review comments are **not** at `/issues/<n>/comments` — that endpoint holds only top-level discussion and is usually empty.
|
||||||
|
Inline comments need two calls: `/pulls/<n>/reviews` for the review ids, then `/pulls/<n>/reviews/<id>/comments` for the bodies, whose `path` and `diff_hunk` fields say what each one is attached to.
|
||||||
|
A review row with an empty `body` is the normal shape when the operator left only inline comments.
|
||||||
|
- SSH **host** keys (`ssh_host_<type>_key`, served by the daemon from `/etc/ssh` or a secret) are not user authentication keys (`~/.ssh/id_ed25519`, offered to a remote server).
|
||||||
|
The `ssh_host_` prefix is OpenSSH's own name for the former, and the `root@<host>` trailing field in a `.pub` is a free-text comment stamped by `ssh-keygen` at generation time, not a claim about which account uses the key.
|
||||||
|
On this machine the two are provably distinct: the daemon presents `SHA256:2ysuBX0+Z6GbdCTujz5JHX6rqnJzIyWhYNrxdhhGwEM`, while pushes to `git.alexion.dev` authenticate with `SHA256:nEhHwtHDnLlsuFxyfp+cETgHUZ8xDMxaPVmYM5vuCkA`.
|
||||||
|
Renaming host keys after user keys, or vice versa, is therefore always wrong.
|
||||||
- `~/.claude/skills` is generated by home-manager with `recursive = true`, so the directories are real and writable but every leaf file is a read-only symlink into the store.
|
- `~/.claude/skills` is generated by home-manager with `recursive = true`, so the directories are real and writable but every leaf file is a read-only symlink into the store.
|
||||||
Editing a skill in place fails; its source is `modules/claude-code/skills/<name>/` here, applied by a rebuild.
|
Editing a skill in place fails; its source is `modules/claude-code/skills/<name>/` here, applied by a rebuild.
|
||||||
Creating a new file under `~/.claude/skills/` succeeds silently and is the trap — it stays outside the repo and reaches no other machine.
|
Creating a new file under `~/.claude/skills/` succeeds silently and is the trap — it stays outside the repo and reaches no other machine.
|
||||||
|
|||||||
@@ -27,7 +27,10 @@
|
|||||||
networking.networkmanager.enable = true;
|
networking.networkmanager.enable = true;
|
||||||
|
|
||||||
# So setup can be driven over the network.
|
# So setup can be driven over the network.
|
||||||
services.openssh.enable = true;
|
# The matching host public keys sit beside this file in plaintext, since
|
||||||
|
# publishing them is their purpose.
|
||||||
|
modules.ssh.enable = true;
|
||||||
|
modules.ssh.hostKeys.sopsFile = ../../secrets/neogaia.yaml;
|
||||||
|
|
||||||
# fish as the login shell.
|
# fish as the login shell.
|
||||||
modules.fish.enable = true;
|
modules.fish.enable = true;
|
||||||
|
|||||||
1
hosts/neogaia/ssh_host_ed25519_key.pub
Normal file
1
hosts/neogaia/ssh_host_ed25519_key.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJS+wp7K123+4BT6G4f954R6WyrbWveY7VlpoBUf6I5p neogaia
|
||||||
1
hosts/neogaia/ssh_host_rsa_key.pub
Normal file
1
hosts/neogaia/ssh_host_rsa_key.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCo2wWUKxyAS4J5TqbWf8glDhJvS5XmdRqFhMeJwG3pOB+4AccZ1T8LU7ZN+RjtRi3j2qXBJvIHuzhtQNtmT59TxocvfobYiqOgJpvVO5K6yD8ZoUJs6ziDkIduI9w9mdRIESoi+dBbVu8n24r61cKDVh+jWX+yjzkOcWcOzqDyQhhkjqblZ1WMAdujEMuEPvif1i2LCxStUaZqRGcx09m/ME2fYcaJrpuxxxvX2+CPJNicoo6Rx9i7ZjAoNuvH+jui4KT62DzlQtQtCl2CFUOM0gCPSa+MbNQ9elfHPvGzEcwOIMo2cuy9KURUkQu+sAgaG8S1PEniDDTecskHtuRdmPZawnQGpIhzo919Q6wUgjT8scK4mmSXRWmGmkMt0GNA2tfj5tDks6r5Q8XsYqtWs4rsOEvfmxVSdM771w+fqDBAil99Jsh0ksPK9+Bwgg8cMDzLLFDn8JA5y2G1HocMMom+u5DYKwPXEKnCILkasB8y24+O3PhSu1EuWw277w6EUEXvU03rCf0Ak/ULjxp9a00EGlloEwSmFI7Aub9XHDr87IdbGInEn+PMqyBYADiN+3h6nE2JO+nMa6i/CHdebmT+T7YJvuTKHD9sjFmQsYaghlq03DZrhHcm4hgUvE1dqGojHrhk/WgA3EWTWtK/+BP0Vy2jXaaz+qAx+EGnhQ== neogaia
|
||||||
61
modules/ssh.nix
Normal file
61
modules/ssh.nix
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
# The OpenSSH daemon, serving host keys restored from secrets.
|
||||||
|
let
|
||||||
|
cfg = config.modules.ssh;
|
||||||
|
|
||||||
|
secretName = type: "ssh-host-${type}-key";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.modules.ssh = {
|
||||||
|
enable = lib.mkEnableOption "the OpenSSH daemon, with host keys restored from secrets";
|
||||||
|
|
||||||
|
hostKeys.sopsFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
Encrypted file holding this host's SSH host private keys, one entry per
|
||||||
|
key type, named `ssh-host-<type>-key`.
|
||||||
|
|
||||||
|
These are the keys the daemon presents to identify itself to connecting
|
||||||
|
clients, not keys used to authenticate anyone to a remote server.
|
||||||
|
Restoring them from secrets rather than generating them keeps the host's
|
||||||
|
fingerprint across a reimage, so every client's `known_hosts` entry
|
||||||
|
stays valid.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
hostKeys.types = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [
|
||||||
|
"ed25519"
|
||||||
|
"rsa"
|
||||||
|
];
|
||||||
|
description = ''
|
||||||
|
Key types to restore, naming both the entries read from the encrypted
|
||||||
|
file and the algorithms the daemon offers. Dropping a type a client has
|
||||||
|
already pinned makes the host unrecognisable to it.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.openssh.enable = true;
|
||||||
|
|
||||||
|
# The daemon reads its host keys once at startup, so a re-key has to restart
|
||||||
|
# it to take effect.
|
||||||
|
sops.secrets = lib.genAttrs (map secretName cfg.hostKeys.types) (_: {
|
||||||
|
inherit (cfg.hostKeys) sopsFile;
|
||||||
|
mode = "0400";
|
||||||
|
restartUnits = [ "sshd.service" ];
|
||||||
|
});
|
||||||
|
|
||||||
|
# An empty list is what stops the daemon generating keys of its own.
|
||||||
|
services.openssh.hostKeys = [ ];
|
||||||
|
services.openssh.extraConfig = lib.concatMapStrings (
|
||||||
|
type: "HostKey ${config.sops.secrets.${secretName type}.path}\n"
|
||||||
|
) cfg.hostKeys.types;
|
||||||
|
};
|
||||||
|
}
|
||||||
26
secrets/neogaia.yaml
Normal file
26
secrets/neogaia.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
ssh-host-ed25519-key: ENC[AES256_GCM,data:K6F9JrmmnK0EKqe9wsjGKOM01ygHvKRUj/a65IdWQsnR8MHNKR90xxviwZa8BdJOjhfOnZYua66h/folPYFT8jiR4HRXRTcZ25LNrGK+oOoxnvN2ES/9X0NDRhTbfxRUfBYb6gf38cyJUoCxoVps+NQ19eCO+kbj/4vfrYyiiZRdL6Bf7xMrftzkMZ4Fxr2xK0NmARvh39Q8tYWJX+57H0bNsRdTQkZkRsLl8l8kQyD8U1g5OsMkNCYZchHZyp6YIzaSoZyAZRBGNxKJmSwzC7ewP/JA7S5nqErtrsZIlS4s27yn1ERT7VfJwhx4xGZgnCF5HQDz/eW6/EhdF8MOTA2HkZqz8u0QpamtgGi0X/K/Ip+ntYIyAa4VtmKcgghPrftZ4xc0TeeOVPU1KgbW7HBnqeDiNQd5k/878FDmsoKWt3CrdL1RszeUCJH1DY+kN44cMlbKwo8P3vp7oNTV1Sn5NUuWv6JxCYX4HTYlW92Wt//mqjtX2gWrMB0R63aPlJNmcIVjDN2mNGXn/Uyn,iv:q6chCkDdccg6pyH7fr8yMvNDcGjl5ZhO4CN25M7ydkk=,tag:JSuTMuG+0fSEjvG8uLG56A==,type:str]
|
||||||
|
ssh-host-rsa-key: ENC[AES256_GCM,data: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,iv:xbUR7YIZKQd6qlzsEZT4aur5xZuRBgQvmSSTdpc05ag=,tag:UIuoHsgMYWmhdJR/GDB0iA==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzM29VSk83QzZFV3h0Q0Rl
|
||||||
|
bGhiVytqN1VWZVpacWIvdVZUU2pHUWVhK1RZCmtEeGNMVXEvLzZEWmFOR1Z3dDQ4
|
||||||
|
N0FCZFZPeUlIN0s1dXBxcDBSemZvQnMKLS0tIEFjS0RNVG5rZDhhVkV3ak92T2dG
|
||||||
|
WEd4cXRzZHp5VWRqOStwRkc0VUZ1MzQKV9qG7NT1T3IsZT3i7Qurf8+7Uc7wRya7
|
||||||
|
Cx5A/EWyiHqkWB5/m66TAk0VA4Yd0Qc++uqQCRbxFdliBO++RVjg2A==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1m0pk94ysjlw3lmf6pyuv5l5pepvdjss8w0vxjv90dq6ndp02tdgsdwdvue
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvSjVTdzNlenpvcHZvZFZH
|
||||||
|
UHB2OTZPNzZERDFRRkpEZ09sOW5ZSEoyWm5RCnpIWlloOXdXVk5jalRRK1BmV0gr
|
||||||
|
bi9UaGhneGJBeko5K2hWWGROdE1qMGsKLS0tIDRYdXp5Unp4cWZtUTZ5bVNQVlY5
|
||||||
|
S2RxYldqa2w3QUp5SjRkdFlDVXA0T1kKslKZMI4PwbdD6T+hV6KDlmEsBAg9AaDO
|
||||||
|
cbQ5nwrYjI7FapucJQQFebB7FrLKCDgZRyw+/nhCmEJSWFz7iZKLQA==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age14a04vphzjq74epfrz9a09wjw8lzchtru84awzuq2n45d8f42ychqjs89qe
|
||||||
|
lastmodified: "2026-07-20T03:45:30Z"
|
||||||
|
mac: ENC[AES256_GCM,data:ThhDV+a054Z32fFkgkJ40I/Wr9EY9gMa/Wc8uToMaqGGdMT87xJH1QEJxdKHuzgWAXFLH9+4Rl/t2uRFC6AVyOGyBou/NDy0G3WzIfrJn1raTYwIvNm4I/0vycRvLyOi4ERPpDERqgoDczeE9i7U7Qves/KdLFPqOlGHDBI/QWE=,iv:oBNu3jeM7S41+mhKYzpYUzyynrJ8hQ7uA8Msd0UrIcA=,tag:HWV32TmxfKZlLcDWo4iTiA==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.13.2
|
||||||
Reference in New Issue
Block a user