#!/usr/bin/env perl use v5.36; use re '/aa'; use FindBin::libs; use Crypt::Misc (); use Crypt::PRNG (); use Getopt::Long qw{GetOptions}; use Trog::Vault (); =head1 SYNOPSIS Make the key that seals this installation's stored secrets, and say where to put it. =head2 USAGE bin/tcms-vault-key # print a key and the commands to install it bin/tcms-vault-key --file # write config/secrets.key instead =head2 WHAT THIS IS Every secret a user stores through /secrets is encrypted under a key derived from this one. It is not in the database, which is the whole point: without it config/auth.db is ciphertext. Losing it loses every stored secret, and there is no way around that -- they go back to typing the password, which is what they did before. There is one key per installation, and it does not need rotating on a schedule. If you do replace it, everything sealed under the old one stops opening and says so rather than presenting as corruption; the fix is for each user to store theirs again. =head2 WHERE TO PUT IT In systemd's credential store, which is what the default output tells you how to do. tPSGI's unit imports it from there and hands it to tCMS in the environment, tCMS takes it back out of the environment as it loads, and it never touches a disk anywhere on the way. Encrypted with systemd-creds it is also sealed to this machine, so the file is useless if it walks. --file is the fallback for a machine that is not running systemd. It writes config/secrets.key, mode 600, and that is genuinely weaker: a key beside the database it protects is a key in the same backup as the database it protects. =head2 CAVEATS Prints a key to your terminal, which is a place keys end up in scrollback. The default output is meant to be piped rather than read. =cut my ( $file, $help ); GetOptions( 'file' => \$file, 'help' => \$help, ) or exit 2; if ($help) { print "Usage: bin/tcms-vault-key [--file]\n"; exit 0; } my $key = Crypt::Misc::encode_b64( Crypt::PRNG::random_bytes(32) ); if ($file) { # Printed and exited rather than died: Carp::Always is in the dependency # chain, and refusing to clobber a key is not a crash. if ( -e $Trog::Vault::key_file ) { ## no critic (ProhibitFiletest_e) -- refusing to overwrite the one thing that cannot be regenerated print STDERR "There is already a key at $Trog::Vault::key_file. Move it out of the way if you really mean to replace it, and read what that costs first: perldoc bin/tcms-vault-key\n"; exit 2; } open( my $fh, '>', $Trog::Vault::key_file ) or die "Could not write $Trog::Vault::key_file: $!\n"; print {$fh} "$key\n"; close($fh); chmod( oct('600'), $Trog::Vault::key_file ) or die "Could not chmod $Trog::Vault::key_file: $!\n"; print "Wrote $Trog::Vault::key_file. Restart tCMS to pick it up.\n"; print "This is the weaker of the two places to keep it: see perldoc bin/tcms-vault-key.\n"; exit 0; } print <<"COMMANDS"; A new vault key. Put it in systemd's credential store, sealed to this machine: echo '$key' | sudo systemd-creds encrypt --name=tcms-vault - /etc/credstore.encrypted/tcms-vault ...or unsealed, if this machine has no TPM: echo '$key' | sudo tee /etc/credstore/tcms-vault > /dev/null sudo chmod 600 /etc/credstore/tcms-vault Then restart the service. tPSGI's unit imports it and hands it over before it chroots; see the Service configuration section of its Readme. Losing this key loses every secret stored under it, and it is not in any backup you take of tCMS, which is the point. Keep it wherever you keep the things you cannot regenerate. COMMANDS