blob: 3e49b14a275470c48c0b877c2b66f572a8069334 [file] [log] [blame]
#!/bin/bash
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
set -e
_SSH_IDENTITY='.ssh/fuchsia_ed25519'
_SSH_AUTH_KEYS='.ssh/fuchsia_authorized_keys'
readonly LOCAL_KEY_MISMATCH_ERROR=64
readonly REMOTE_KEY_MISMATCH_ERROR=65
readonly MIXED_KEY_MISMATCH_ERROR=66
readonly KEY_GENERATION_ERROR=67
function fail_with_mixed_mismatch {
local extra_path_local=""
local extra_path_remote=""
if is_local_a_tree; then
extra_path_local="
${local_fuchsia_dir}/.ssh/pkey*
${local_fuchsia_dir}/.fx-ssh-path"
fi
if is_remote_a_tree; then
extra_path_remote="
${remote_fuchsia_dir}/.ssh/pkey*
${remote_fuchsia_dir}/.fx-ssh-path"
fi
fx-error "
You have different SSH credentials for Fuchsia devices in your local machine
and on the remote server.
Local and remote ~/.ssh/fuchsia_ed25519 and ~/.ssh/fuchsia_ed25519.pub need to match.
Before you continue, update your local and remote source code.
This ensures that the host tools on both machines expect the SSH credentials
in a consistent location.
If you have been using local tools like 'fx pave-remote' or 'fserve-remote.sh',
the local credentials are probably most recent and you can backup and delete
the remote keys on ${host}:
~/.ssh/fuchsia_ed25519*${extra_path_remote}
and then run this command again.
If you want to keep the remote credentials, backup and delete the local keys:
~/.ssh/fuchsia_ed25519*${extra_path_local}
and then run this command again.
If you manage your SSH credentials manually, use the --no-check-ssh-keys flag to skip this check.
"
exit ${MIXED_KEY_MISMATCH_ERROR}
}
# Check if there is a Fuchsia SSH key in HOME (used for the GN SDK)
function normalize_local_gn_key {
if [[ ! -f "$HOME/${_SSH_IDENTITY}" ]]; then
return 1
fi
return 0
}
# Check whether a Fuchsia SSH key is present on the remote host when
# the remote is not a Fuchsia tree.
function normalize_remote_gn_key {
# shellcheck disable=SC2029
ssh ${ssh_base_args[@]+"${ssh_base_args[@]}"} "${host}" \
"[[ -f \"$_SSH_IDENTITY\" ]]" || return ${_ERROR_NO_KEY}
}
# If a key exists in either //.ssh or HOME/.ssh, copy to the other location
# (normalize) and return 0.
# If no key is found on either location, return 1.
# If keys exist on both locations, return 0 if they are the same, or exit
# with LOCAL_KEY_MISMATCH_ERROR if they are different.
#
function normalize_local_intree_key {
# run in a subshell to avoid exiting
(
"${local_fuchsia_dir}/tools/ssh-keys/gen-ssh-keys.sh" --no-new-key >/dev/null 2>&1
)
status=$?
if [[ $status -eq $_ERROR_NO_KEY ]] ; then
return 1
elif [[ $status -eq 0 ]]; then
return 0
elif [[ $status -eq $_ERROR_MISMATCHED_KEYS ]]; then
fx-error "ERROR: mismatched key in local machine."
fx-error "Run '//tools/ssh-keys/gen-ssh-keys.sh' to learn how to fix it."
exit $LOCAL_KEY_MISMATCH_ERROR
else
fx-error "ERROR: unexpected error code $status from local //tools/ssh-keys/gen-ssh-keys.sh"
fx-error "Update your local source tree and run '//tools/ssh-keys/gen-ssh-keys.sh --no-new-key' manually to check."
exit $LOCAL_KEY_MISMATCH_ERROR
fi
}
# If a key exists in either //.ssh or HOME/.ssh on the remote server, copy to
# the other location (normalize) and return 0.
# If no key is found on either location, return 1.
# If keys exist on both locations, return 0 if they are the same, or exit
# with LOCAL_KEY_MISMATCH_ERROR if they are different.
function normalize_remote_intree_key {
# run in a subshell to avoid exiting
(
# normalize remote keys accross //.ssh and HOME/.ssh using the remote
# gen-ssh-keys.sh with --no-new-key argument, so it doesn't generate a new
# key if one doesn't exist.
# shellcheck disable=SC2029
ssh ${ssh_base_args[@]+"${ssh_base_args[@]}"} "${host}" \
"${remote_fuchsia_dir}/tools/ssh-keys/gen-ssh-keys.sh --no-new-key >/dev/null 2>&1"
)
status=$?
if [[ $status -eq $_ERROR_NO_KEY ]] ; then
return 1
elif [[ $status -eq 0 ]]; then
return 0
elif [[ $status -eq $_ERROR_MISMATCHED_KEYS ]]; then
fx-error "ERROR: mismatched SSH keys in the remote machine."
fx-error "Run '//tools/ssh-keys/gen-ssh-keys.sh' on ${host} to learn how to fix it."
exit $REMOTE_KEY_MISMATCH_ERROR
elif [[ $status -eq 1 ]]; then
# error code "1" is from an old gen-ssh-keys.sh source.
fx-error "ERROR: you have an old //tools/ssh-keys/gen-ssh-keys.sh in ${host}:${remote_fuchsia_dir}."
fx-error "Please update your remote source tree and run again, or use --no-check-ssh-keys to skip SSH key checking."
exit $REMOTE_KEY_MISMATCH_ERROR
else
fx-error "ERROR: unexpected error code $status from remote //tools/ssh-keys/gen-ssh-keys.sh"
fx-error "Please update your remote source tree in ${host} and run '//tools/ssh-keys/gen-ssh-keys.sh --no-new-key' manually to check."
exit $REMOTE_KEY_MISMATCH_ERROR
fi
}
# Arguments
# *: Public key comment.
function sync_remote_keys_intree {
# shellcheck disable=SC2029
ssh ${ssh_base_args[@]+"${ssh_base_args[@]}"} "${host}" \
"${remote_fuchsia_dir}/tools/ssh-keys/gen-ssh-keys.sh --description \"$*\""
}
function compare_remote_and_local {
local -r remote_key="\${HOME}/${_SSH_IDENTITY}"
local -r local_key="${HOME}/${_SSH_IDENTITY}"
# shellcheck disable=SC2029
ssh ${ssh_base_args[@]+"${ssh_base_args[@]}"} "${host}" "cat ${remote_key}" \
| cmp -s "${local_key}" -
}
function copy_local_to_remote {
scp ${ssh_base_args[@]+"${ssh_base_args[@]}"} -q -p "${HOME}/${_SSH_IDENTITY}" "${HOME}/${_SSH_IDENTITY}.pub" "${host}:.ssh/"
# shellcheck disable=SC2029
ssh ${ssh_base_args[@]+"${ssh_base_args[@]}"} "${host}" "cat >> ${_SSH_AUTH_KEYS}" < "${HOME}/${_SSH_AUTH_KEYS}"
}
function copy_remote_to_local {
(
# force subshell to limit scope of umask
umask 077
mkdir -p "$HOME/.ssh"
scp ${ssh_base_args[@]+"${ssh_base_args[@]}"} -q -p "${host}:${_SSH_IDENTITY}" "${host}:${_SSH_IDENTITY}.pub" "$HOME/.ssh"
umask 133
# shellcheck disable=SC2029
ssh ${ssh_base_args[@]+"${ssh_base_args[@]}"} "${host}" cat "${_SSH_AUTH_KEYS}" >> "${HOME}/${_SSH_AUTH_KEYS}"
)
}
function is_local_a_tree {
test -n "$local_fuchsia_dir"
}
function is_remote_a_tree {
test -n "$remote_fuchsia_dir"
}
# Verify if the Fuchsia SSH keys in the current host and on a remote host
# are the same.
#
# If no default identity is available on one of the local or remote machine,
# this script copies that identity into place on the other machine. If
# mismatched identities exist, this script prints an actionable error
# message and returns a status code of 113 (_ERROR_MISMATCHED_KEYS).
#
# Callers should have an user-facing '--no-check-ssh-keys' flag that
# skips this method if the user prefers to manage SSH credentials manually.
#
# Args:
# 1: local_fuchsia_dir (empty string if GN SDK)
# 2: Remote host
# 3: Remote local_fuchsia_dir (empty string if GN SDK)
# 4-*: Additional args for `ssh`
function verify_default_keys {
local -r local_fuchsia_dir="$1"
local -r host="$2"
local -r remote_fuchsia_dir="$3"
shift 3
local temp_ssh_args=()
while [[ $# -gt 0 ]]; do
if [[ "$1" == "-S" ]]; then
# "-S" doesn't work for scp, so we transform it to its equivalent -o ControlPath=...
shift
if [[ $# -gt 0 ]]; then
temp_ssh_args+=("-o" "ControlPath=$1")
fi
else
temp_ssh_args+=( $1 )
fi
shift
done
local -r ssh_base_args=( "${temp_ssh_args[@]+"${temp_ssh_args[@]}"}" )
local has_local_key=false
local has_remote_key=false
# keep in sync with //tools/ssh-keys/gen-ssh-keys.sh
local -r _ERROR_NO_KEY=112
local -r _ERROR_MISMATCHED_KEYS=113
# // keep in sync with //tools/ssh-keys/gen-ssh-keys.sh
if is_local_a_tree; then
if normalize_local_intree_key; then
has_local_key=true
fi
else
if normalize_local_gn_key; then
has_local_key=true
fi
fi
if is_remote_a_tree; then
if normalize_remote_intree_key; then
has_remote_key=true
fi
else
if normalize_remote_gn_key; then
has_remote_key=true
fi
fi
if ! $has_local_key && ! $has_remote_key; then
if is_remote_a_tree; then
fx-warn "No SSH credentials found, generating on the remote server first."
sync_remote_keys_intree "triggered by $0 from $(hostname -f)" \
|| exit $KEY_GENERATION_ERROR
has_remote_key=true
else
# if remote is not a Fuchsia tree, it is easier to generate the
# key locally and copy to the remote than the opposite.
fx-warn "No SSH credentials found, generating one locally first."
# check-fuchsia-ssh-config is defined in fuchsia-common.sh,
# assume it is already loaded by the caller
check-fuchsia-ssh-config || exit $KEY_GENERATION_ERROR
has_local_key=true
fi
fi
if $has_local_key && $has_remote_key ; then
# check if they match
compare_remote_and_local || fail_with_mixed_mismatch
return 0
elif $has_local_key && ! $has_remote_key; then
fx-warn "Copying local SSH credentials to the remote server"
copy_local_to_remote
if is_remote_a_tree; then
sync_remote_keys_intree
fi
elif ! $has_local_key && $has_remote_key; then
fx-warn "Copying remote SSH credentials to the local machine"
copy_remote_to_local
if is_local_a_tree; then
"${local_fuchsia_dir}/tools/ssh-keys/gen-ssh-keys.sh"
fi
fi
}