Skip to content
Snippets Groups Projects
update-grid-servers 4.07 KiB
#!/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}"