tcms-vault-key 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. #!/usr/bin/env perl
  2. use 5.040;
  3. use re '/aasx';
  4. use warnings FATAL => 'all';
  5. use FindBin::libs;
  6. use Crypt::Misc ();
  7. use Crypt::PRNG ();
  8. use Fcntl qw{O_CREAT O_EXCL O_WRONLY};
  9. use Getopt::Long qw{GetOptions};
  10. use Trog::Vault ();
  11. =head1 SYNOPSIS
  12. Write a vault key into config/secrets.key, for a tCMS that is not being handed
  13. one by the service that starts it.
  14. =head2 USAGE
  15. bin/tcms-vault-key --file
  16. =head2 WHEN YOU WANT THIS
  17. Developing, mostly. Running tPSGI by hand -- HOME=. bin/tpsgi -p 5001, in a
  18. tmux, so you can watch it and restart it -- is the ordinary way to work on tCMS,
  19. and nothing in that path hands over a vault key: there is no unit importing a
  20. systemd credential, and service/tpsgi.sh, which is what would have put it in the
  21. environment, never ran. So /secrets does not work until you put a key where
  22. Trog::Vault can find one, and this is how.
  23. The other time is a machine with no systemd at all, and there it is a compromise
  24. rather than a convenience.
  25. =head2 WHEN YOU DO NOT
  26. On a production host. tPSGI's bin/build_service makes the key and puts it in
  27. systemd's credential store, sealed to this machine if it has a TPM to seal it to,
  28. and its unit hands it to tCMS before it chroots -- so the key is never on a disk
  29. that tCMS can see, and never in a backup of tCMS.
  30. A key file in the checkout gives up exactly that. It sits next to the database
  31. it protects, so whatever copies one copies the other, and the encryption stops
  32. being worth anything the moment both are somewhere they should not be.
  33. .gitignore keeps it out of the repository; keeping it out of your backups is on
  34. you.
  35. =head2 WHAT THE KEY IS
  36. Every secret a user stores through /secrets is encrypted under a key derived
  37. from this one. It is not in the database, which is the whole point: without it
  38. config/auth.db is ciphertext.
  39. There is one key per installation and it does not need rotating on a schedule.
  40. It is meant to live and die with the machine -- nothing backs it up, and
  41. trog-provisioner does not carry it onto a rebuilt guest -- so a rebuild loses
  42. every stored secret and each user stores theirs again. That is the intended
  43. cost of the key not being anywhere the database is.
  44. Replacing it has the same effect. Everything sealed under the old one stops
  45. opening and says so, on /secrets and wherever it was going to be used, rather
  46. than presenting as corruption or as a thing worth trying again.
  47. =cut
  48. my ( $file, $help );
  49. GetOptions(
  50. 'file' => \$file,
  51. 'help' => \$help,
  52. ) or exit 2;
  53. if ( $help || !$file ) {
  54. print <<"USAGE";
  55. Usage: bin/tcms-vault-key --file
  56. Writes $Trog::Vault::key_file, for a tCMS nothing is handing a vault key to:
  57. running tPSGI by hand while developing, or a machine with no systemd.
  58. On a production host tPSGI's bin/build_service does this properly, and better;
  59. see perldoc bin/tcms-vault-key.
  60. USAGE
  61. exit( $help ? 0 : 2 );
  62. }
  63. # One call that creates the key, private from the start, and fails if anything,
  64. # even a dangling symlink, is already at that path.
  65. my $fh;
  66. if ( !sysopen( $fh, $Trog::Vault::key_file, O_WRONLY | O_CREAT | O_EXCL, oct('600') ) ) {
  67. die "Could not write $Trog::Vault::key_file: $!\n" unless $!{EEXIST};
  68. # Printed and exited rather than died, so that it exits 2: refusing to
  69. # clobber the one thing that cannot be regenerated is not a crash.
  70. 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"; ## no critic (ProhibitPrintSTDERR) -- for whoever ran this
  71. exit 2;
  72. }
  73. print {$fh} Crypt::Misc::encode_b64( Crypt::PRNG::random_bytes($Trog::Vault::key_bytes) ) . "\n";
  74. close($fh) or die "Could not write $Trog::Vault::key_file: $!\n";
  75. print <<"SAID";
  76. Wrote $Trog::Vault::key_file. Restart tCMS to pick it up.
  77. Fine for development. On a production host, let tPSGI's bin/build_service hand one
  78. over instead, so the key is not sitting next to the database it protects.
  79. SAID