#!/usr/bin/env nix-shell #!nix-shell -i bash -p jp nix openssh # # Tell all servers belonging to a certain grid that they should update # themselves to the latest configuration associated with that grid. # set -euxo pipefail # Find the location of this script so we can refer to data files with a known # relative location. HERE=$(dirname $0) # Get the path to the ssh key which authorizes us to deliver this # notification. This path contains a base64-encoded key because of # limitations placed on the values of GitLab job environment variables. We'll # decode it later. ENCODED_DEPLOY_KEY_PATH=$1 shift # Get the name of the grid to which we're going to deliver notification. This # corresponds to the name of one of the directories in the top-level `morph` # directory. GRIDNAME=$1 shift # Tell one server to update itself. update_one_node() { grid_name=$1 shift deploy_key_path=$1 shift node=$1 shift # Avoid both the "host key unknown" prompt and the possibility for a # man-in-the-middle attack (on every single deploy!) by referring to a # pre-initialized known hosts file for this grid. # # Then use the specified deploy key to authenticate as the deployment user # and trigger the update on the host. There's no command here because the # deployment key is restricted *only* the deloyment update command and the # ssh server will supply that command itself. ssh -o "UserKnownHostsFile=${HERE}/known_hosts.${grid_name}" -i "${deploy_key_path}" "deployment@${node}" } # Tell all servers belonging to one grid to update themselves. update_grid_nodes() { deploy_key_path=$1 shift gridname=$1 shift case "$gridname" in "production") grid_dir=./morph/grid/production domain=private.storage ;; "staging") grid_dir=./morph/grid/testing domain=privatestorage-staging.com ;; *) echo "Unknown grid: ${gridname}" exit 1 esac # Find the names of all hosts that belong to this grid. This list includes # one extra string, "network", which is morph configuration stuff and we need # to filter out later. nodes=$(nix eval --json "(builtins.concatStringsSep \" \" (builtins.attrNames (import $grid_dir/grid.nix)))" | jp --unquoted @) # Tell every server in the network to update itself. for node in ${nodes}; do if [ "${node}" = "network" ]; then # This isn't a server, it's part of the morph configuration. continue fi update_one_node "${gridname}" "${deploy_key_path}" "${node}.${domain}" done } decode_deploy_key() { encoded_key_path=$1 shift # Make sure the deploy key file is not readable by anyone else. Not # that there should be anyone else looking - but OpenSSH won't even read # it if it looks like it is too open. umask 077 # Make up a safe-ish place on the filesystem to write the key. decoded_key_path="$(mktemp -d)/deploy_key" # Decode the contents of the encoded key path into a decoded key path. base64 --decode "${encoded_key_path}" > "${decoded_key_path}" # If OpenSSH doesn't find a newline after the last line of the key # material then it fails to parse it. So, make sure there is one. If # there was already one, it's fine to have an extra. echo >> "${decoded_key_path}" # Remove the key from the filesystem to reduce the chance of unintentional # disclosure. Overall our handling of this key is still not *particulary* # safe or secure but that's why the key is only authorized to perform a # single very specific operation. rm -v "${encoded_key_path}" >/dev/stderr echo -n "${decoded_key_path}" } # Announce our intentions. show_banner() { echo "Hello $GITLAB_USER_LOGIN from $CI_JOB_NAME. I was triggered by $CI_PIPELINE_SOURCE" echo "and I am deploying the $CI_COMMIT_BRANCH branch to the $GRIDNAME environment." } show_banner DEPLOY_KEY_PATH="$(decode_deploy_key "${ENCODED_DEPLOY_KEY_PATH}")" # Update the deployment update_grid_nodes "${DEPLOY_KEY_PATH}" "${GRIDNAME}" # Remove the decoded key from the filesystem as well. rm -v "${DEPLOY_KEY_PATH}"