#!/bin/sh # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. # Reads and echoes EC2 Metadata to get the authorized keys blob for the user $1 set -e # Set umask so only we can touch temp files umask 077 IMDS="http://169.254.169.254/latest/meta-data" # cURL wrapper to ensure we always use the desired flags curl_cmd () { /usr/bin/curl -s -f -m 1 -H "${1}" "${2}" } # Fetch the IMDSv2 access token. 5 seconds is overall AKC timeout so we use that. IMDS_TOKEN="$(/usr/bin/curl -s -f -m 1 -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 5")" token_exit="${?}" if [ "${token_exit}" -ne 0 ] ; then /usr/bin/logger -i -p authpriv.info "EC2 Instance Connect failed to establish trust with Instance Metadata Service" exit 255 fi if [ -z "${IMDS_TOKEN}" ] ; then # Fast fail /usr/bin/logger -i -p authpriv.info "EC2 Instance Connect failed to get a token to invoke Instance Metadata Service" exit 255 fi IMDS_TOKEN_HEADER="X-aws-ec2-metadata-token: ${IMDS_TOKEN}" # Verify the instance ID itself # Note: if IMDSv1 is disabled here and IMDS_TOKEN is unset we fast-fail out right now. instance=$(curl_cmd "${IMDS_TOKEN_HEADER}" "${IMDS}/instance-id/") if [ -z "${instance}" ] ; then exit 0 fi # Validate the instance ID is i-abcd1234 (8 or 17 char, hex) # We have it buffered to 32 chars to futureproof any further EC2 format changes (given some other EC2 resources are already 32 char) /bin/echo "${instance}" | /usr/bin/head -n 1 | /bin/grep -Eq "^i-[0-9a-f]{8,32}$" || exit 0 # Verify we have an EC2 uuid if [ ! -f /sys/hypervisor/uuid ] ; then # Nitro, switch to DMI check if [ ! -f /sys/devices/virtual/dmi/id/board_asset_tag ] ; then # We're out of options. This is definitely not an instance. /usr/bin/logger -i -p authpriv.info "EC2 Instance Connect was invoked on a non-instance and will do nothing." exit 0 elif [ "$(/bin/cat /sys/devices/virtual/dmi/id/board_asset_tag)" != "${instance}" ] ; then # The board_asset_tag does not match the instance id. This is not a valid instance. /usr/bin/logger -i -p authpriv.info "EC2 Instance Connect was invoked on a non-instance and will do nothing." exit 0 fi elif [ "$(/usr/bin/cut -c1-3 < /sys/hypervisor/uuid)" != "ec2" ] ; then # Leading bytes are not "ec2" /usr/bin/logger -i -p authpriv.info "EC2 Instance Connect was invoked on a non-instance and will do nothing." exit 0 fi # At this point we're reasonably confident we're running on an EC2 instance. OPENSSL=/usr/bin/openssl if [ -z "${1}" ] ; then # No user provided, not really anything to query for. Fail out. /usr/bin/logger -i -p authpriv.info "EC2 Instance Connect was invoked without a user to authorize and will do nothing." exit 1 fi /usr/bin/id -u "${1}" > /dev/null 2>&1 id_exit="${?}" if [ "${id_exit}" -ne 0 ] ; then # User doesn't actually exist. Let sshd deal with it. exit 0 fi # Verify that we have active keys. Fast-exit if we do not. keys_status="$(/usr/bin/curl -s -m 1 -H "${IMDS_TOKEN_HEADER}" -o /dev/null -I -w %{http_code} "${IMDS}/managed-ssh-keys/active-keys/${1}/")" if [ "${keys_status}" != "200" ] then # No keys for this user. Nothing to do. exit 0 fi # We are not checking format here - that is parse_authorized_keys's job zone=$(curl_cmd "${IMDS_TOKEN_HEADER}" "${IMDS}/placement/availability-zone/") zone_exit="${?}" if [ "${zone_exit}" -ne 0 ] then exit "${zone_exit}" fi # Validate the zone is aa-bb-#c (or aa-bb-cc-#d for special partitions like AWS GovCloud) /bin/echo "${zone}" | /usr/bin/head -n 1 | /bin/grep -Eq "^([a-z]+-){2,3}[0-9][a-z]$" || exit 255 region=$(/bin/echo "${zone}" | /bin/sed -n 's/\(\([a-z]\+-\)\+[0-9]\+\).*/\1/p') domain=$(curl_cmd "${IMDS_TOKEN_HEADER}" "${IMDS}/services/domain/") domain_exit="${?}" if [ "${domain_exit}" -ne 0 ] then exit "${domain_exit}" fi is_domain_valid=1 for valid_domain in amazonaws.com amazonaws.com.cn c2s.ic.gov sc2s.sgov.gov; do if [ "$domain" = "$valid_domain" ]; then is_domain_valid=0 break fi done if [ $is_domain_valid -eq 1 ]; then /usr/bin/logger -i -p authpriv.info "EC2 Instance Connect found an invalid domain and will do nothing." exit 255 fi expected_signer=$(/usr/bin/printf 'managed-ssh-signer.%s.%s' "${region}" "${domain}") userpath=$(/bin/mktemp -d /dev/shm/eic-XXXXXXXX) trap 'rm -rf "${userpath:?}"' EXIT # Read the current signer cert # This will overwrite whatever currently exists, so it will remain up-to-date certificate=$(curl_cmd "${IMDS_TOKEN_HEADER}" "${IMDS}/managed-ssh-keys/signer-cert/") cert_exit="${?}" if [ "${cert_exit}" -ne 0 ] || [ -z "${certificate}" ] then exit "${cert_exit}" fi # parse_authorized_keys will verify this # Read the signer OCSP staples staple_paths=$(curl_cmd "${IMDS_TOKEN_HEADER}" "${IMDS}/managed-ssh-keys/signer-ocsp/") staple_exit="${?}" if [ "${staple_exit}" -ne 0 ] then exit "${staple_exit}" fi ocsp_path=$(/bin/mktemp -d "${userpath}/eic-ocsp-XXXXXXXX") for word in $staple_paths do curl_cmd "${IMDS_TOKEN_HEADER}" "${IMDS}/managed-ssh-keys/signer-ocsp/${word}" | /usr/bin/base64 -d > "${ocsp_path}/${word}" staple_exit="${?}" if [ "${staple_exit}" -ne 0 ] then exit "${staple_exit}" fi /bin/chmod 400 "${ocsp_path}/${word}" # Disable access to staple file done # parse_authorized_keys will verify these # Invoke key parser (will automagically echo the results) keys_file="${userpath}/eic-keys" curl_cmd "${IMDS_TOKEN_HEADER}" "${IMDS}/managed-ssh-keys/active-keys/${1}/" > "${keys_file}" DIR="$( cd "$( dirname "${0}" )" && pwd )" ca_path=/etc/ssl/certs if [ -z "${2}" ] ; then output="$("${DIR}/eic_parse_authorized_keys" -x false -p "${keys_file}" -o "${OPENSSL}" -d "${userpath}" -s "${certificate}" -i "${instance}" -c "${expected_signer}" -a "${ca_path}" -v "${ocsp_path}")" exitcode=$? # not quote-escaped since this must be numeric 0-255 else output="$("${DIR}/eic_parse_authorized_keys" -x false -p "${keys_file}" -o "${OPENSSL}" -d "${userpath}" -s "${certificate}" -i "${instance}" -c "${expected_signer}" -a "${ca_path}" -v "${ocsp_path}" -f "${2}")" exitcode=$? # not quote-escaped since this must be numeric 0-255 fi /bin/echo "${output}" exit $exitcode