381 lines
No EOL
12 KiB
Text
381 lines
No EOL
12 KiB
Text
---
|
|
title: soppy_wet_nix log[002]
|
|
description: how it stores secrets alongside its code
|
|
---
|
|
|
|
&soppy wet nix (16MAY2025) ^a
|
|
|
|
what if you read [code:/secrets/dont_leak] but it said [codeblock::
|
|
dont_leak: ENC[AES256_GCM,data:psyelHNBMy+xglw=,iv:UhxfqAqVbCgMRMqRMA1MmvgIO18zTrVtQdFywupZyYA=,tag:Legj+njC3z8jX16n1pZszg==,type:str]
|
|
]
|
|
|
|
that's cool but what?
|
|
|
|
let's face it; sometimes stuff is best left to one's own eyes. like auth tokens, passwords, private keys...
|
|
|
|
most of the time, handling these manually once somewhere, dropping it in a file somewhere on a
|
|
remote machine or UI and that's the end of it.
|
|
|
|
but what if you're like. the type of lazy that doing a lot of work now means not doing the work a second time?
|
|
[em:that's like this doll.]
|
|
|
|
lets talk about our bestie [em(b):[link(https://github.com/Mic92/sops-nix):sops-nix]]
|
|
|
|
&but first, sops.
|
|
|
|
sops-nix is based on [link(https://github.com/getsops/sops):sops], so unfortunately we need to start there.
|
|
|
|
[em(b):sops] is a way to automate encryption and decryption of secret data you might want tightly coupled to a use case.
|
|
|
|
irl, one might pair this to AWS KMS or another type of distributed keystore; and that's cool. but. this isn't irl.
|
|
this is a video game, dolly.
|
|
|
|
instead we'll focus on sops's [link(https://github.com/FiloSottile/age):age] encryption method, which is based on ed25519 SSH keys you already use (right?)
|
|
|
|
[quote::
|
|
it can hear those thoughts,
|
|
|
|
[em:> aki wtf, this is 3 whole things, this one is getting overwhelmed]
|
|
]
|
|
|
|
yeah real
|
|
|
|
we're going to store all of our secrets in this sort of tree,
|
|
|
|
[codeblock::
|
|
project
|
|
├── secrets
|
|
│ ├── global.yaml
|
|
│ ├── machine-1
|
|
│ │ └── secrets.yaml
|
|
│ ├── machine-2
|
|
│ │ └── secrets.yaml
|
|
└── .sops.yaml
|
|
]
|
|
|
|
each machine gets a folder under secrets, and there's a global one for every machine too.
|
|
|
|
there's also a [code:.sops.yaml] file to mention, to support our needs, we'll do...
|
|
|
|
[codeblock::
|
|
creation_rules:
|
|
- path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
|
|
key_groups:
|
|
- age: ??? # for doll
|
|
- age: ??? # for machine-1
|
|
- age: ??? # for machine-2
|
|
- path_regex: secrets/machine-1/[^/]+\.(yaml|json|env|ini)$
|
|
key_groups:
|
|
- age: ??? # for doll
|
|
- age: ??? # for machine-1
|
|
- path_regex: secrets/machine-2/[^/]+\.(yaml|json|env|ini)$
|
|
key_groups:
|
|
- age: ??? # for doll
|
|
- age: ??? # for machine-2
|
|
]
|
|
|
|
[em:wait. age. that ag-] - shh. be Still.
|
|
|
|
&hold on ,...
|
|
|
|
dolly,,, listen. yaml is. [em(b):[em:v]e[em:r]y [em:f]u[em:n]]. and so lets make it fun.
|
|
|
|
[codeblock::
|
|
keys: &all
|
|
- &op_doll ???
|
|
- &m_machine-1 ???
|
|
- &m_machine-2 ???
|
|
|
|
creation_rules:
|
|
- path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
|
|
key_groups:
|
|
- age: *all
|
|
- path_regex: secrets/machine-1/[^/]+\.(yaml|json|env|ini)$
|
|
key_groups:
|
|
- age: *op_doll
|
|
- age: *m_machine-1
|
|
- path_regex: secrets/machine-2/[^/]+\.(yaml|json|env|ini)$
|
|
key_groups:
|
|
- age: *op_doll
|
|
- age: *m_machine-2
|
|
]
|
|
|
|
now this isn't a sops thing, this is a yaml thing. yaml has anchors......
|
|
|
|
sops doesn't know what that [code:keys] key is, it gracefully ignores stuff it doesn't care about, so we can exploit this!!!
|
|
|
|
this solves three huge problems with sops + age
|
|
|
|
- which key is machine [code:age1lq5q5g5qjsdcc3k] anyway? - we solve this by naming every key
|
|
- the "global" group should always be every key... so we have that too
|
|
- a secret third problem
|
|
|
|
ok. back to whatever we had before
|
|
|
|
&how old is one's encryption
|
|
|
|
age answers that question by letting you use ed25519 keys to encrypt and decrypt your data.
|
|
|
|
[quote:ed25519 would be a good name for a doll... ^n]
|
|
|
|
since we're deploying to machines with presumably an SSH server running, and presumably
|
|
the doll also has an SSH client, all of these ends have SSH keys we can use
|
|
|
|
&lets start with the doll end,
|
|
|
|
[codeblock::
|
|
$> ssh-to-age -i ~/.ssh/id_ed25519.pub
|
|
|
|
#|> age13c5wv623jxjja5mjz7fajg9qqwvypzgsfqrs4tmk7rpgyzu7aufs4ul9f9
|
|
]
|
|
|
|
wow that's a string! yay!
|
|
|
|
we'll plop that in our keys section.
|
|
|
|
[codeblock::
|
|
keys: &all
|
|
- &op_doll age13c5wv623jxjja5mjz7fajg9qqwvypzgsfqrs4tmk7rpgyzu7aufs4ul9f9
|
|
- &m_machine-1 ???
|
|
- &m_machine-2 ???
|
|
]
|
|
|
|
sops will automatically use the [code:~/.ssh/id_ed25519] file, we're good to go!!!
|
|
|
|
&the machines,,
|
|
|
|
so SSH servers also have host keys, so when sops is ran on a specific host, it
|
|
can also decrypt like the doll can.
|
|
|
|
we'll fetch machine-1's ssh public keys and slap those in too.
|
|
|
|
[codeblock::
|
|
$> ssh-keyscan -qt ed25519 machine-1.local | ssh-to-age
|
|
|
|
#=> machine-1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOGae8gFqSsfezM/ul7LnCmv9GAk6AIah18+tc212LQW
|
|
#=> age1jc6ghxfgxe3gx53xa55azxan447cfxaqfqeh5y5yzqapj7mw7ajql8kv02
|
|
]
|
|
|
|
and yeet into the keys block, and machine-2's too.
|
|
|
|
[codeblock::
|
|
keys: &all
|
|
- &op_doll age13c5wv623jxjja5mjz7fajg9qqwvypzgsfqrs4tmk7rpgyzu7aufs4ul9f9
|
|
- &m_machine-1 age1jc6ghxfgxe3gx53xa55azxan447cfxaqfqeh5y5yzqapj7mw7ajql8kv02
|
|
- &m_machine-2 age1438lvn7gh4he0rnj0xnvnx56l97mpz0vsv3wktj8utk65kqs8ycqftcxze
|
|
]
|
|
|
|
&so.. lets sops!!!!!!!!!
|
|
|
|
we did all the hard parts. lets do the secret parts.
|
|
|
|
[codeblock::
|
|
$> sops secrets/global.yaml
|
|
|
|
# secret, set EDITOR to something..
|
|
# like `EDITOR="code -w"` will open a vscode tab.... (close tab to save)
|
|
]
|
|
|
|
this usually shows a nice default yaml file, yeet all that into the garbage.
|
|
|
|
we wanna store something useful like... an authentication token to a certain doll's
|
|
multicelluar digital cortex.
|
|
|
|
[codeblock::
|
|
doll_token: awawawa
|
|
]
|
|
|
|
lets save that file, and go inspect it.
|
|
|
|
[codeblock::
|
|
doll_token: ENC[AES256_GCM,data:DryFfMa/dQ==,iv:IdqvGEKKTSOaATlqMqyHQ3PEAwC6mJqjHbO3KwfTlHc=,tag:5ByySoYZu6qZexHBmnQBKA==,type:str]
|
|
sops:
|
|
age:
|
|
- recipient: age13c5wv623jxjja5mjz7fajg9qqwvypzgsfqrs4tmk7rpgyzu7aufs4ul9f9
|
|
enc: |
|
|
-----BEGIN AGE ENCRYPTED FILE-----
|
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvdTBVb0FvM2xocWFVclJt
|
|
L05aRmpKOHh6d3hXcE9Vem9SL1hrSjFMRng4CmNrTmROTFVMRlc4QjllWVo1S3lv
|
|
MXo5UVFuazVoeWQ5TEE1NXJIUzl0K1EKLS0tICtrSTdoWHIrSlJLRTUweWpoVWlY
|
|
ZTNXNUM1ZEpsQlMzRlNoZHpkU2lhVzAKpYJAfihRyG/ok0tgJg4FjnN8vj6Bz/+z
|
|
0Y/P21JJp/SnXq4LjivBCT4XJ0s6XoffUEqw/uxLzsY1wwox003pOA==
|
|
-----END AGE ENCRYPTED FILE-----
|
|
- recipient: age1jc6ghxfgxe3gx53xa55azxan447cfxaqfqeh5y5yzqapj7mw7ajql8kv02
|
|
enc: |
|
|
-----BEGIN AGE ENCRYPTED FILE-----
|
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGWVUrWmIvOXUzYmJyWm9G
|
|
dmhNQ0lUNFh5ZTdXMmMxNWJENXlMU1daN3lzCnh5cmQyMFlSWDBQV1VSOCtoTUcv
|
|
anpmWVFVUmpmZ2svUFpFMUI0YUlwclkKLS0tIFc5NDRQWTdseXdyUE5BbXUwSFk4
|
|
VXptdjk3cHFEV0twaXhSYzY2R0JrUzQK5RNS2XdR1m7/SGfpNFh5Z9Q2YGsJT1Cw
|
|
iJyW+7EseiuWEkFa2JFul6nsP8W1TmDobk2VXiYpc/BTm78hBlUhyg==
|
|
-----END AGE ENCRYPTED FILE-----
|
|
- recipient: age1438lvn7gh4he0rnj0xnvnx56l97mpz0vsv3wktj8utk65kqs8ycqftcxze
|
|
enc: |
|
|
-----BEGIN AGE ENCRYPTED FILE-----
|
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtOUtYUXhOTzJlS2lHUTFz
|
|
TGwyQ2Y2aEZWWnNmWjVqWWtXeHdmQlFPTFFzCnNSU2FDVFdsTC9hWWV4NEtNQ1FF
|
|
bDRvOXJiR3hRb1Ryc0VuTkExQkxoMHMKLS0tIE1ETGJZc04yOHJkaTkwaTlyZW1j
|
|
N1pacGpGZGtnNWdFSjZxbjVuVlRZVzAKjDaMO/oKlq4D1QHTlD9lBY+0w81/gybv
|
|
2+BSo93LG+bN+cNI9jYc9FU+t4GdlyhEcKQ4MTczgZyMaMOYkhgKcg==
|
|
-----END AGE ENCRYPTED FILE-----
|
|
]
|
|
|
|
sops completely obfuscates this file.
|
|
|
|
this is now safe to send into git. or even over the internet.
|
|
or even a website like this one. haha.
|
|
|
|
really, it is. these are real keys, and good encryption, and hopefully only intended targets can decrypt them.
|
|
the reader can even send this doll encrypted messages with that op_doll age key, if one wanted.
|
|
|
|
what's happening is each "recipient" is getting an individual copy of the file encrypted with
|
|
the recipient's public key.
|
|
|
|
the only way to get the data back out is to have one of these machine's private keys.
|
|
|
|
&further sopping
|
|
|
|
we can do [code: sops secrets/machine-1/secrets.yaml] and set up machine-specific secrets too.
|
|
|
|
those won't be able to be decrypted by machine-2, nor vice versa, due to how we
|
|
set up [code:.sops.yaml] earlier.
|
|
|
|
also, if one happens to rotate SSH keys or. delete/remake a server or somethin',
|
|
we can ask sops to redo its work with
|
|
|
|
[codeblock::
|
|
$> sops updatekeys -y secrets/global.yaml
|
|
]
|
|
|
|
[quote(protip)::
|
|
put in a op_doll_2 as a new key in all the same spots, then run the command above
|
|
|
|
finally, remove the old key, and run the above command again.
|
|
|
|
get rotated !
|
|
]
|
|
|
|
&nix sauce
|
|
|
|
260 or so lines in and we haven't even talked about nix yet.
|
|
|
|
sops-nix lets us use sops. from nix. incredible.
|
|
|
|
we use flakes, so we add [code:github:Mic92/sops-nix] as an input, and work it into our machines.
|
|
|
|
[codeblock::
|
|
{ inputs, ... }: {
|
|
imports = [
|
|
inputs.sops-nix.nixosModules.sops
|
|
];
|
|
|
|
# lets us shorthand for global secrets
|
|
sops.defaultSopsFile = ../path/to/../secrets/global.yaml;
|
|
|
|
# allows sops-nix to decrypt using machine keys
|
|
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
|
}
|
|
]
|
|
|
|
now say we wanna use that token... there's a few different ways.
|
|
|
|
&reference as a file directly
|
|
|
|
nix is. weird. strings aren't strings. strings are files. and files are strings. and files are files. and secrets are files...
|
|
|
|
for example, if we want to set up an openvpn client, doll's provider would give an .ovpn file. toss that in sops as a
|
|
yaml value (or json or whatever it guess)!! make it sopping. instantly.
|
|
|
|
our [code:secrets/machine-1/secrets.yaml]:
|
|
[codeblock::
|
|
ovpn_file: |
|
|
client
|
|
dev tun
|
|
proto udp
|
|
remote 41.66.66.66 41666
|
|
#...
|
|
]
|
|
|
|
our [code:modules/openvpn.nix]:
|
|
[codeblock::
|
|
{ config, ... }: {
|
|
# the "ovpn_file" here matches the YAML key above.
|
|
sops.secrets.ovpn_file = {
|
|
sopsFile = ../machine-1/secrets.yaml
|
|
};
|
|
|
|
services.openvpn.servers.doll = {
|
|
config = ''
|
|
config ${config.sops.secrets.ovpn_file.path}
|
|
'';
|
|
}
|
|
}
|
|
]
|
|
|
|
&templates!
|
|
|
|
but wait haha there's usually like a login username and password right?
|
|
|
|
good catch, lets add those to our [code:secrets/machine-1/secrets.yaml]:
|
|
[codeblock::
|
|
ovpn_username: doll
|
|
ovpn_password: witchesRme4n
|
|
ovpn_file: |
|
|
#...
|
|
]
|
|
|
|
and lets make a template!
|
|
[codeblock::
|
|
{ config, ... }: {
|
|
sops.secrets.ovpn_file = {
|
|
sopsFile = ../machine-1/secrets.yaml
|
|
};
|
|
|
|
# our two new friends
|
|
sops.secrets.ovpn_username = {
|
|
sopsFile = ../machine-1/secrets.yaml
|
|
};
|
|
sops.secrets.ovpn_password = {
|
|
sopsFile = ../machine-1/secrets.yaml
|
|
};
|
|
|
|
# this is just how openvpn likes it. no typos.
|
|
sops.templates.ovpn_credentials = {
|
|
content = ''
|
|
${config.sops.placeholder.ovpn_username}
|
|
${config.sops.placeholder.ovpn_password}
|
|
'';
|
|
};
|
|
|
|
services.openvpn.servers.doll = {
|
|
config = ''
|
|
config ${config.sops.secrets.ovpn_file.path}
|
|
|
|
auth-user-pass ${config.sops.templates.ovpn_credentials.path}
|
|
'';
|
|
}
|
|
}
|
|
]
|
|
|
|
wow. it can do everything.
|
|
|
|
doll might notice the "consumer" side of sops-nix uses [code:config] a lot. yep. its a little self-referential.
|
|
|
|
&secrets........ but what about automation
|
|
|
|
that silly [code:.sops.yaml] thing huh, c:
|
|
|
|
why did we tag the age keys with [code:op_] and [code:m_]? so we can find them. in a script.
|
|
|
|
and then generate the yaml so when one adds a new machine its like one command. :>
|
|
|
|
we did this with some simple JS script
|
|
[link(https://git.sapphic.engineer/noe/nixos/src/branch/main/tools/onboard-machine.js):doll could reference.]
|
|
(it runs via bun so no node_modules!!!)
|
|
|
|
this allows us to run a little [code:onboard-machine.js 41.66.66.66 machine-1] and pull the right
|
|
key from a remote machine
|
|
|
|
&is doll soppy yet
|
|
|
|
sure hope so. |