#!/bin/bash # # Script to edit secrets for a host. # # This script is used by an administrator on his/hers local machine. The # general principle is for this script to ssh to the target host, decrypt # the secrets and allow changes to be made, and then fetch the encrypted # secrets from the host and add it to the Cosmos repository on the # administrators machine. # # Funnily enough, this script will execute itself (with the argument # '--on-host') on the target host in order to do the decryption etc. Don't # allow this to confuse you and everything will be fine. # set -e umask 077 LAST_OUTPUT_FILENAME="/root/.last_edit-secrets_output" test -d /dev/shm && export TMPDIR='/dev/shm' TMPFILE=$(mktemp edit-secrets.XXXXXXXXXX) TMPFILE2=$(mktemp edit-secrets.XXXXXXXXXX) if [ ! -f $TMPFILE ]; then echo "$TMPFILE" echo "$0: Failed creating temporary file" exit 1 fi if [ ! -f $TMPFILE2 ]; then echo "$TMPFILE2" echo "$0: Failed creating temporary file 2" exit 1 fi trap "rm -f $TMPFILE $TMPFILE2" EXIT if [[ ! $1 ]]; then # deliberately don't mention the --on-host argument echo "Syntax: $0 fqdn" exit 1 fi function patch_broken_eyaml { # # Ubuntu 22.04 (jammy) has a broken hiera-eyaml package, a bug report # exists here: https://bugs.launchpad.net/ubuntu/+source/hiera-eyaml/+bug/1974059 # if [ "$(lsb_release -cs)" == "jammy" ]; then plugins_file="/usr/share/rubygems-integration/all/gems/hiera-eyaml-3.2.2/lib/hiera/backend/eyaml/plugins.rb" if [ -f $plugins_file ]; then # We only want to try patching the file if it is the known broken version bad_sum="1d0f14765ebcfcdae300d8ac5d715845ef9b283345d19114a23d96161556618f" sum=$(sha256sum $plugins_file | awk '{print $1}') if [ "$sum" == "$bad_sum" ]; then patch --fuzz=0 --directory=/ --strip=0 <<'EOF' --- /usr/share/rubygems-integration/all/gems/hiera-eyaml-3.2.2/lib/hiera/backend/eyaml/plugins.rb.orig 2023-01-18 08:20:22.140338419 +0000 +++ /usr/share/rubygems-integration/all/gems/hiera-eyaml-3.2.2/lib/hiera/backend/eyaml/plugins.rb 2023-01-18 08:21:05.654053501 +0000 @@ -32,6 +32,7 @@ specs = Gem::VERSION >= "1.6.0" ? source.latest_specs(true) : source.latest_specs specs.each do |spec| + spec = spec.to_spec if spec.respond_to?(:to_spec) next if @@plugins.include? spec dependency = spec.dependencies.find { |d| d.name == "hiera-eyaml" } EOF fi fi fi } function patch_broken_eyaml_noble { # # Ubuntu 24.04 (noble) has a hiera-eyaml version that is incompatible with ruby 3.2+ (default in ubuntu24). # This is fixed in hiera-eyaml version 3.3.0: https://github.com/voxpupuli/hiera-eyaml/pull/340/files # https://github.com/voxpupuli/hiera-eyaml/blob/master/CHANGELOG.md # But there is no modern version of hiera-eyanl packaged in debian or ubuntu. # https://github.com/puppetlabs/puppet/wiki/Puppet-8-Compatibility#filedirexists-removed # . /etc/os-release if [ "${VERSION_CODENAME}" == "noble" ]; then plugins_file="/usr/share/rubygems-integration/all/gems/hiera-eyaml-3.3.0/lib/hiera/backend/eyaml/subcommands/edit.rb" if [ -f $plugins_file ]; then # We only want to try patching the file if it is the known broken version bad_sum="59c6eb910ab2eb44f8c75aeaa79bff097038feb673b5c6bdccde23d9b2a393e2" sum=$(sha256sum $plugins_file | awk '{print $1}') if [ "$sum" == "$bad_sum" ]; then patch --fuzz=0 --directory=/ --strip=0 <<'EOF' --- /usr/share/rubygems-integration/all/gems/hiera-eyaml-3.3.0/lib/hiera/backend/eyaml/subcommands/edit.rb.orig 2022-06-11 16:30:10.000000000 +0000 +++ /usr/share/rubygems-integration/all/gems/hiera-eyaml-3.3.0/lib/hiera/backend/eyaml/subcommands/edit.rb 2024-09-09 14:13:19.306342025 +0000 @@ -59,7 +59,7 @@ Optimist::die "You must specify an eyaml file" if ARGV.empty? options[:source] = :eyaml options[:eyaml] = ARGV.shift - if File.exists? options[:eyaml] + if File.exist? options[:eyaml] begin options[:input_data] = File.read options[:eyaml] rescue EOF fi fi fi } function edit_copy_and_commit() { # # This code runs on the administrators local machine # local host=$1 if [[ ${EDITOR} ]]; then declare -r REMOTE_EDITOR="${EDITOR}" else declare -r REMOTE_EDITOR='/usr/bin/vim.tiny' fi # Execute this script, on a remote host ssh -t root@"${host}" EDITOR="${REMOTE_EDITOR}" /var/cache/cosmos/repo/edit-secrets --on-host scp -q root@"${host}:${LAST_OUTPUT_FILENAME}" ${TMPFILE} local save_to if grep ^"STATUS=UPDATED" $TMPFILE > /dev/null; then save_to="${host}/overlay/etc/hiera/data/secrets.yaml.asc" # extract the GPG output perl -e '$a = 0; while (<>) { $a = 1 if ($_ =~ /-+BEGIN PGP MESSAGE-+/); print $_ if $a; $a = 0 if ($_ =~ /-+END PGP MESSAGE-+/); }' < $TMPFILE > $TMPFILE2 if ! grep "END PGP MESSAGE" $TMPFILE2 > /dev/null; then echo "$0: Failed extracting PGP output from file $TMPFILE into $TMPFILE2" exit 1 fi elif grep ^"STATUS=EYAML_UPDATED" $TMPFILE > /dev/null; then save_to="${host}/overlay/etc/hiera/data/local.eyaml" # extract the eyaml output perl -e '$a = 0; while (<>) { $a = 1 if ($_ =~ /^---$/); print $_ if $a }' < $TMPFILE > $TMPFILE2 if ! grep "^---$" $TMPFILE2 > /dev/null; then echo "$0: Failed extracting yaml output from file $TMPFILE into $TMPFILE2" exit 1 fi else echo "" echo "Not updated" echo "" exit 0 fi # use cat to preserve permissions etc. mkdir -p "`dirname ${save_to}`" cat $TMPFILE2 > "${save_to}" git add "${save_to}" if grep ^"STATUS=EYAML_UPDATED" $TMPFILE > /dev/null; then git diff --cached "${save_to}" fi echo "" echo "$save_to updated" echo "" exit 0 } function edit_file_on_host() { # # Local execution on a host # local SECRETFILE=/etc/hiera/data/secrets.yaml.asc local EYAMLFILE=/etc/hiera/data/local.eyaml if [ -f "${EYAMLFILE}" ]; then edit_eyaml_file ${EYAMLFILE} elif [ -f "${SECRETFILE}" ]; then edit_gpg_file ${SECRETFILE} elif [ -f /etc/hiera/eyaml/public_certkey.pkcs7.pem ]; then # default to eyaml if the key exists and none of the secrets-file above exist echo "---" > ${EYAMLFILE} edit_eyaml_file ${EYAMLFILE} fi } function edit_gpg_file() { local SECRETFILE=$1 GNUPGHOME=/etc/hiera/gpg/ export GNUPGHOME local GPG=`which gpg2 || true` if [ ! -x "$GPG" ]; then GPG=`which gpg || true` if [ ! -x "$GPG" ]; then echo "$0: gpg2 or gpg not found" exit 1 fi fi if ! $GPG --list-secret-keys | grep -q ^"sec\s"; then echo "$0: Secret key does not exist (in $GNUPGHOME)." echo "" echo "Generate it with /var/cache/cosmos/model/pre-tasks.d/040hiera-gpg" echo "" exit 1 fi if [ -s $SECRETFILE ]; then $GPG -d $SECRETFILE > $TMPFILE fi cp $TMPFILE $TMPFILE2 sensible-editor $TMPFILE rm -f ${TMPFILE}~ ${TMPFILE2}~ echo "" echo "" local status=0 cmp -s $TMPFILE $TMPFILE2 || status=1 if [ $status -eq 0 ]; then ( echo "STATUS=NOT_CHANGED" ) > $LAST_OUTPUT_FILENAME echo "" echo "$0: No changes detected" else # figure out this hosts gpg key id if lsb_release -r | grep -qE '(18|20).04'; then recipient=$($GPG --list-secret-keys | grep -A1 '^sec' | tail -1 | awk '{print $1}') else recipient=$($GPG --list-secret-key | grep ^sec | head -1 | awk '{print $2}' | cut -d / -f 2) fi save_to="`hostname --fqdn`/overlay${SECRETFILE}" echo "" ( echo "STATUS=UPDATED" echo "" ) > $LAST_OUTPUT_FILENAME $GPG --output - --armor --recipient $recipient --sign --encrypt $TMPFILE >> $LAST_OUTPUT_FILENAME echo "" echo "GPG output saved in $LAST_OUTPUT_FILENAME - save it in Cosmos as" echo "" echo " $save_to" echo "" fi } function edit_eyaml_file() { local EYAMLFILE=$1 local FQDN=$(hostname --fqdn) local privkey='/etc/hiera/eyaml/private_key.pkcs7.pem' local pubkey='/etc/hiera/eyaml/public_certkey.pkcs7.pem' for f in $privkey $pubkey; do test -f "${f}" || { echo "$0: eyaml key file ${f} not found"; exit 1; } done patch_broken_eyaml patch_broken_eyaml_noble # save source file for comparision afterwards cp "${EYAMLFILE}" "${TMPFILE}" eyaml edit --pkcs7-private-key "${privkey}" --pkcs7-public-key "${pubkey}" "${EYAMLFILE}" local status=0 cmp -s "${EYAMLFILE}" $TMPFILE || status=1 if [ $status -eq 0 ]; then ( echo "STATUS=NOT_CHANGED" ) > $LAST_OUTPUT_FILENAME echo "" echo "$0: No changes detected" else echo "" ( echo "STATUS=EYAML_UPDATED" echo "" ) > $LAST_OUTPUT_FILENAME cat "${EYAMLFILE}" >> $LAST_OUTPUT_FILENAME fi } if [[ $1 == '--on-host' ]]; then edit_file_on_host else host=$(echo $1 | sed -e 's!/*$!!') # remove trailing slashes if [ ! -d $host ]; then echo "$0: No host-directory for '$host' found - execute in top-level cosmos dir" exit 1 fi edit_copy_and_commit $host fi exit 0