Jelajahi Sumber

Hand the application its secrets from systemd's store

An application often needs a key or a password that has no business living in
its own checkout, where it goes into every backup alongside the thing it
protects.  The unit imports those from systemd's credential store --
ImportCredential rather than LoadCredential, so an installation with nothing to
hand over starts normally rather than failing on a missing file.

tpsgi.sh reads them before tarbaby chroots, because the store is a ramdisk at
/run/credentials and the workers end up somewhere that is not it, and passes
them on in the environment, which does survive the chroot.  An application
taking one is expected to read it out of its own environment at startup, so that
nothing it forks afterwards inherits it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADwKSaBSvUTdA3RtGQeNau
George Baugh 1 bulan lalu
induk
melakukan
7f4240582d
3 mengubah file dengan 55 tambahan dan 0 penghapusan
  1. 29 0
      Readme.md
  2. 13 0
      service/tpsgi.sh
  3. 13 0
      service/tpsgi.tmpl

+ 29 - 0
Readme.md

@@ -203,3 +203,32 @@ In general the operation is like so:
 Generally you'll just run bin/build\_service and then systemctl start $domain.
 It's configured to start as root, then drop privs thanks to Net::Server's capabilities inherited in starman.
 
+### Secrets the application needs
+
+An application often needs a key or a password that has no business living in
+its own checkout, where it would go into every backup alongside whatever it
+protects.  The unit imports those from systemd's credential store and
+service/tpsgi.sh hands them on.
+
+Put one there, sealed to this machine so that the file is useless anywhere else:
+
+    openssl rand -base64 32 \
+      | sudo systemd-creds encrypt --name=tcms-vault - /etc/credstore.encrypted/tcms-vault
+
+...or unencrypted, if the machine has no TPM, in which case it is a root-owned
+file rather than a sealed one:
+
+    openssl rand -base64 32 | sudo tee /etc/credstore/tcms-vault > /dev/null
+    sudo chmod 600 /etc/credstore/tcms-vault
+
+systemd decrypts it into a ramdisk at `/run/credentials/$service` that only this
+service can read.  Nothing is written down in the unit, and nothing breaks if it
+is not there: `ImportCredential=` is quiet about a credential that does not
+exist, so an installation with no secrets to hand over starts normally.
+
+The workers are chrooted into the application directory and that ramdisk is
+outside it, so service/tpsgi.sh reads the credential before the chroot happens
+and exports it -- `tcms-vault` becomes `TCMS_VAULT_KEY`.  An application taking
+one of these is expected to read it out of its environment as it starts and
+delete it there and then, so that nothing it forks afterwards inherits it.
+

+ 13 - 0
service/tpsgi.sh

@@ -11,6 +11,19 @@ echo "tPSGI running with EGID $GROUP";
 
 [[ -e run/tpsgi.pid ]] && sudo pkill -F run/tpsgi.pid
 
+# Hand the application whatever systemd put in the credential store.  It has to
+# happen here: tarbaby chroots the workers into this directory, and the store is
+# a ramdisk at /run/credentials which is not inside it.  So the file is read
+# while there is still a path to it, and what was in it goes on in the
+# environment, which survives the chroot.
+#
+# The application is expected to take it back out of its own environment as it
+# starts, so that nothing it forks later inherits it.
+if [[ -n $CREDENTIALS_DIRECTORY && -r "$CREDENTIALS_DIRECTORY/tcms-vault" ]]; then
+    export TCMS_VAULT_KEY=$(cat "$CREDENTIALS_DIRECTORY/tcms-vault")
+    echo "Loaded the tcms-vault credential"
+fi
+
 # Bind the various dirs we need for chroot to work
 readarray -t BIND_DIRS <<< $(bin/tpsgi-config --binds)
 for bind in "${BIND_DIRS[@]}"; do

+ 13 - 0
service/tpsgi.tmpl

@@ -11,6 +11,19 @@ Type=forking
 PIDFile=__REPLACEME__/run/tpsgi.pid
 Environment=PATH='__PERL5BIN__' HOME=__REPLACEME__/
 ExecStart=__REPLACEME__/service/tpsgi.sh
+
+# Secrets the application needs and should not be storing next to itself.  Taken
+# from the system credential store -- /etc/credstore, or /etc/credstore.encrypted
+# for one sealed to this machine's TPM -- and put in a ramdisk under
+# /run/credentials that only this service can read.
+#
+# Not fatal when there is nothing there, which is the point of ImportCredential
+# over LoadCredential: an installation with no secrets to hand over is an
+# installation that starts normally.
+#
+# The workers end up chrooted into WorkingDirectory and /run/credentials is
+# outside it, so service/tpsgi.sh reads what it needs before that happens.
+ImportCredential=tcms-vault
 WorkingDirectory=__REPLACEME__/
 Restart=always
 OOMPolicy=stop