#!/bin/bash set -euo pipefail SCRIPT_NAME="teleport-installer" # default values ALIVE_CHECK_DELAY=3 CONNECTIVITY_TEST_METHOD="" COPY_COMMAND="cp" DISTRO_TYPE="" IGNORE_CONNECTIVITY_CHECK="${TELEPORT_IGNORE_CONNECTIVITY_CHECK:-false}" LAUNCHD_CONFIG_PATH="/Library/LaunchDaemons" LOG_FILENAME="$(mktemp -t ${SCRIPT_NAME}.log.XXXXXXXXXX)" MACOS_STDERR_LOG="/var/log/teleport-stderr.log" MACOS_STDOUT_LOG="/var/log/teleport-stdout.log" SYSTEMD_UNIT_PATH="/lib/systemd/system/teleport.service" TARGET_PORT_DEFAULT=443 TELEPORT_ARCHIVE_PATH='teleport' TELEPORT_BINARY_DIR="/usr/local/bin" TELEPORT_BINARY_LIST="teleport tctl tsh" TELEPORT_CONFIG_PATH="/etc/teleport.yaml" TELEPORT_DATA_DIR="/var/lib/teleport" TELEPORT_DOCS_URL="https://goteleport.com/docs/" TELEPORT_FORMAT="" # initialise variables (because set -u disallows unbound variables) f="" l="" DISABLE_TLS_VERIFICATION=false NODENAME=$(hostname) IGNORE_CHECKS=false OVERRIDE_FORMAT="" QUIET=false APP_INSTALL_DECISION="" INTERACTIVE=false # the default value of each variable is a templatable Go value so that it can # optionally be replaced by the server before the script is served up TELEPORT_VERSION='13.4.26' TELEPORT_PACKAGE_NAME='teleport' REPO_CHANNEL='' TARGET_HOSTNAME='dev.bramkelchtermans.be' TARGET_PORT='443' JOIN_TOKEN='70649dc2f5d16138ef278c368aeac0a3' JOIN_METHOD='' JOIN_METHOD_FLAG="" [ -n "$JOIN_METHOD" ] && JOIN_METHOD_FLAG="--join-method ${JOIN_METHOD}" # inject labels into the configuration LABELS='teleport.internal/resource-id=d082b3d8-e775-4572-ad6c-473b131c739e' LABELS_FLAG=() [ -n "$LABELS" ] && LABELS_FLAG=(--labels "${LABELS}") # When all stanza generators have been updated to use the new # `teleport configure` commands CA_PIN_HASHES can be removed along # with the script passing it in in `join_tokens.go`. CA_PIN_HASHES='sha256:cb88c2b06d5a1a6dbfb62ea77e0c968e71c6e0a0c71a791570c548622024dd7c' CA_PINS='sha256:cb88c2b06d5a1a6dbfb62ea77e0c968e71c6e0a0c71a791570c548622024dd7c' ARG_CA_PIN_HASHES="" APP_INSTALL_MODE='false' APP_NAME='' APP_URI='' DB_INSTALL_MODE='false' # usage message # shellcheck disable=SC2086 usage() { echo "Usage: $(basename $0) [-v teleport_version] [-h target_hostname] [-p target_port] [-j join_token] [-c ca_pin_hash]... [-q] [-l log_filename] [-a app_name] [-u app_uri] " 1>&2; exit 1; } while getopts ":v:h:p:j:c:f:ql:ika:u:" o; do case "${o}" in v) TELEPORT_VERSION=${OPTARG};; h) TARGET_HOSTNAME=${OPTARG};; p) TARGET_PORT=${OPTARG};; j) JOIN_TOKEN=${OPTARG};; c) ARG_CA_PIN_HASHES="${ARG_CA_PIN_HASHES} ${OPTARG}";; f) f=${OPTARG}; if [[ ${f} != "tarball" && ${f} != "deb" && ${f} != "rpm" ]]; then usage; fi;; q) QUIET=true;; l) l=${OPTARG};; i) IGNORE_CHECKS=true; COPY_COMMAND="cp -f";; k) DISABLE_TLS_VERIFICATION=true;; a) APP_INSTALL_MODE=true && APP_NAME=${OPTARG};; u) APP_INSTALL_MODE=true && APP_URI=${OPTARG};; *) usage;; esac done shift $((OPTIND-1)) if [[ "${ARG_CA_PIN_HASHES}" != "" ]]; then CA_PIN_HASHES="${ARG_CA_PIN_HASHES}" fi # function to construct a go template variable # go's template parser is a bit finicky, so we dynamically build the value one character at a time construct_go_template() { OUTPUT="{" OUTPUT+="{" OUTPUT+="." OUTPUT+="${1}" OUTPUT+="}" OUTPUT+="}" echo "${OUTPUT}" } # check whether we are root, exit if not assert_running_as_root() { if ! [ "$(id -u)" = 0 ]; then echo "This script must be run as root." 1>&2 exit 1 fi } # function to check whether variables are either blank or set to the default go template value # (because they haven't been set by the go script generator or a command line argument) # returns 1 if the variable is set to a default/zero value # returns 0 otherwise (i.e. it needs to be set interactively) check_variable() { VARIABLE_VALUE="${!1}" GO_TEMPLATE_NAME=$(construct_go_template "${2}") if [[ "${VARIABLE_VALUE}" == "" ]] || [[ "${VARIABLE_VALUE}" == "${GO_TEMPLATE_NAME}" ]]; then return 1 fi return 0 } # function to check whether a provided value is "truthy" i.e. it looks like you're trying to say "yes" is_truthy() { declare -a TRUTHY_VALUES TRUTHY_VALUES=("y" "Y" "yes" "YES" "ye" "YE" "yep" "YEP" "ya" "YA") CHECK_VALUE="$1" for ARRAY_VALUE in "${TRUTHY_VALUES[@]}"; do [[ "${CHECK_VALUE}" == "${ARRAY_VALUE}" ]] && return 0; done return 1 } # function to read input until the value you get is non-empty read_nonblank_input() { INPUT="" VARIABLE_TO_ASSIGN="$1" shift PROMPT="$*" until [[ "${INPUT}" != "" ]]; do echo -n "${PROMPT}" read -r INPUT done printf -v "${VARIABLE_TO_ASSIGN}" '%s' "${INPUT}" } # error if we're not root assert_running_as_root # set/read values interactively if not provided # users will be prompted to enter their own value if all the following are true: # - the current value is blank, or equal to the default Go template value # - the value has not been provided by command line argument ! check_variable TELEPORT_VERSION version && INTERACTIVE=true && read_nonblank_input TELEPORT_VERSION "Enter Teleport version to install (without v): " ! check_variable TARGET_HOSTNAME hostname && INTERACTIVE=true && read_nonblank_input TARGET_HOSTNAME "Enter target hostname to connect to: " ! check_variable TARGET_PORT port && INTERACTIVE=true && { echo -n "Enter target port to connect to [${TARGET_PORT_DEFAULT}]: "; read -r TARGET_PORT; } ! check_variable JOIN_TOKEN token && INTERACTIVE=true && read_nonblank_input JOIN_TOKEN "Enter Teleport join token as provided: " ! check_variable CA_PIN_HASHES caPins && INTERACTIVE=true && read_nonblank_input CA_PIN_HASHES "Enter CA pin hash (separate multiple hashes with spaces): " [ -n "${f}" ] && OVERRIDE_FORMAT=${f} [ -n "${l}" ] && LOG_FILENAME=${l} # if app service mode is not set (or is the default value) and we are running interactively (i.e. the user has provided some input already), # prompt the user to choose whether to enable app_service if [[ "${INTERACTIVE}" == "true" ]]; then if ! check_variable APP_INSTALL_MODE appInstallMode; then APP_INSTALL_MODE="false" echo -n "Would you like to enable and configure Teleport's app_service, to use Teleport as a reverse proxy for a web application? [y/n, default: n] " read -r APP_INSTALL_DECISION if is_truthy "${APP_INSTALL_DECISION}"; then APP_INSTALL_MODE="true" fi fi fi # prompt for extra needed values if we're running in app service mode if [[ "${APP_INSTALL_MODE}" == "true" ]]; then ! check_variable APP_NAME appName && read_nonblank_input APP_NAME "Enter app name to install (must be DNS-compatible; less than 63 characters, no spaces, only - or _ as punctuation): " ! check_variable APP_URI appURI && read_nonblank_input APP_URI "Enter app URI (the host running the Teleport app service must be able to connect to this): " # generate app public addr by concatenating values APP_PUBLIC_ADDR="${APP_NAME}.${TARGET_HOSTNAME}" fi # set default target port if value not provided if [[ "${TARGET_PORT}" == "" ]]; then TARGET_PORT=${TARGET_PORT_DEFAULT} fi # clear log file if provided if [[ "${LOG_FILENAME}" != "" ]]; then if [ -f "${LOG_FILENAME}" ]; then echo -n "" > "${LOG_FILENAME}" fi fi # log functions log_date() { echo -n "$(date '+%Y-%m-%d %H:%M:%S %Z')"; } log() { LOG_LINE="$(log_date) [${SCRIPT_NAME}] $*" if [[ ${QUIET} != "true" ]]; then echo "${LOG_LINE}" fi if [[ "${LOG_FILENAME}" != "" ]]; then echo "${LOG_LINE}" >> "${LOG_FILENAME}" fi } # writes a line with no timestamp or starting data, always prints log_only() { LOG_LINE="$*" echo "${LOG_LINE}" if [[ "${LOG_FILENAME}" != "" ]]; then echo "${LOG_LINE}" >> "${LOG_FILENAME}" fi } # writes a line by itself as a header log_header() { LOG_LINE="$*" echo "" echo "${LOG_LINE}" echo "" if [[ "${LOG_FILENAME}" != "" ]]; then echo "${LOG_LINE}" >> "${LOG_FILENAME}" fi } # important log lines, print even when -q (quiet) is passed log_important() { LOG_LINE="$(log_date) [${SCRIPT_NAME}] ---> $*" echo "${LOG_LINE}" if [[ "${LOG_FILENAME}" != "" ]]; then echo "${LOG_LINE}" >> "${LOG_FILENAME}" fi } log_cleanup_message() { log_only "This script does not overwrite any existing settings or Teleport installations." log_only "Please clean up by running any of the following steps as necessary:" log_only "- stop any running Teleport processes" log_only " - pkill -f teleport" log_only "- remove any data under ${TELEPORT_DATA_DIR}, along with the directory itself" log_only " - rm -rf ${TELEPORT_DATA_DIR}" log_only "- remove any configuration at ${TELEPORT_CONFIG_PATH}" log_only " - rm -f ${TELEPORT_CONFIG_PATH}" log_only "- remove any Teleport binaries (${TELEPORT_BINARY_LIST}) installed under ${TELEPORT_BINARY_DIR}" for BINARY in ${TELEPORT_BINARY_LIST}; do EXAMPLE_DELETE_COMMAND+="${TELEPORT_BINARY_DIR}/${BINARY} "; done log_only " - rm -f ${EXAMPLE_DELETE_COMMAND}" log_only "Run this installer again when done." log_only } # other functions # check whether a named program exists check_exists() { NAME=$1; if type "${NAME}" >/dev/null 2>&1; then return 0; else return 1; fi; } # checks for the existence of a list of named binaries and exits with error if any of them don't exist check_exists_fatal() { for TOOL in "$@"; do if ! check_exists "${TOOL}"; then log_important "Error: cannot find ${TOOL} - it needs to be installed" exit 1 fi done } # check connectivity to the given host/port and make a request to see if Teleport is listening # uses the global variable CONNECTIVITY_TEST_METHOD to return the name of the checker, as return # values aren't really a thing that exists in bash check_connectivity() { HOST=$1 PORT=$2 # check with nc if check_exists nc; then CONNECTIVITY_TEST_METHOD="nc" if nc -z -w3 "${HOST}" "${PORT}" >/dev/null 2>&1; then return 0; else return 1; fi # if there's no nc, check with telnet elif check_exists telnet; then CONNECTIVITY_TEST_METHOD="telnet" if echo -e '\x1dclose\x0d' | telnet "${HOST}" "${PORT}" >/dev/null 2>&1; then return 0; else return 1; fi # if there's no nc or telnet, try and use /dev/tcp elif [ -f /dev/tcp ]; then CONNECTIVITY_TEST_METHOD="/dev/tcp" if (head -1 < "/dev/tcp/${HOST}/${PORT}") >/dev/null 2>&1; then return 0; else return 1; fi else return 255 fi } # check whether a teleport DEB is already installed and exit with error if so check_deb_not_already_installed() { check_exists_fatal dpkg awk DEB_INSTALLED=$(dpkg -l | awk '{print $2}' | grep -E ^teleport || true) if [[ ${DEB_INSTALLED} != "" ]]; then log_important "It looks like there is already a Teleport DEB package installed (name: ${DEB_INSTALLED})." log_important "You will need to remove that package before using this script." exit 1 fi } # check whether a teleport RPM is already installed and exit with error if so check_rpm_not_already_installed() { check_exists_fatal rpm RPM_INSTALLED=$(rpm -qa | grep -E ^teleport || true) if [[ ${RPM_INSTALLED} != "" ]]; then log_important "It looks like there is already a Teleport RPM package installed (name: ${RPM_INSTALLED})." log_important "You will need to remove that package before using this script." exit 1 fi } # function to check if given variable is set check_set() { CHECK_KEY=${1} || true CHECK_VALUE=${!1} || true if [[ "${CHECK_VALUE}" == "" ]]; then log "Required variable ${CHECK_KEY} is not set" exit 1 else log "${CHECK_KEY}: ${CHECK_VALUE}" fi } # checks that teleport binary can be found in path and runs 'teleport version' check_teleport_binary() { FOUND_TELEPORT_VERSION=$(${TELEPORT_BINARY_DIR}/teleport version) if [[ "${FOUND_TELEPORT_VERSION}" == "" ]]; then log "Cannot find Teleport binary" return 1 else log "Found: ${FOUND_TELEPORT_VERSION}"; return 0 fi } # wrapper to download with curl download() { URL=$1 OUTPUT_PATH=$2 CURL_COMMAND="curl -fsSL --retry 5 --retry-delay 5" # optionally allow disabling of TLS verification (can be useful on older distros # which often have an out-of-date set of CA certificate bundle which won't validate) if [[ ${DISABLE_TLS_VERIFICATION} == "true" ]]; then CURL_COMMAND+=" -k" fi log "Running ${CURL_COMMAND} ${URL}" log "Downloading to ${OUTPUT_PATH}" # handle errors with curl if ! ${CURL_COMMAND} -o "${OUTPUT_PATH}" "${URL}"; then log_important "curl error downloading ${URL}" log "On an older OS, this may be related to the CA certificate bundle being too old." log "You can pass the hidden -k flag to this script to disable TLS verification - this is not recommended!" exit 1 fi # check that the file has a non-zero size as an extra validation check_exists_fatal wc xargs FILE_SIZE="$(wc -c <"${OUTPUT_PATH}" | xargs)" if [ "${FILE_SIZE}" -eq 0 ]; then log_important "The downloaded file has a size of 0 bytes, which means an error occurred. Cannot continue." exit 1 else log "Downloaded file size: ${FILE_SIZE} bytes" fi # if we have a hashing utility installed, also download and validate the checksum SHA_COMMAND="" # shasum is installed by default on macOS and some distros if check_exists shasum; then SHA_COMMAND="shasum -a 256" # sha256sum is installed by default in some other distros elif check_exists sha256sum; then SHA_COMMAND="sha256sum" fi if [[ "${SHA_COMMAND}" != "" ]]; then log "Will use ${SHA_COMMAND} to validate the checksum of the downloaded file" SHA_URL="${URL}.sha256" SHA_PATH="${OUTPUT_PATH}.sha256" ${CURL_COMMAND} -o "${SHA_PATH}" "${SHA_URL}" if ${SHA_COMMAND} --status -c "${SHA_PATH}"; then log "The downloaded file's checksum validated correctly" else SHA_EXPECTED=$(cat "${SHA_PATH}") SHA_ACTUAL=$(${SHA_COMMAND} "${OUTPUT_PATH}") if check_exists awk; then SHA_EXPECTED=$(echo "${SHA_EXPECTED}" | awk '{print $1}') SHA_ACTUAL=$(echo "${SHA_ACTUAL}" | awk '{print $1}') fi log_important "Checksum of the downloaded file did not validate correctly" log_important "Expected: ${SHA_EXPECTED}" log_important "Got: ${SHA_ACTUAL}" log_important "Try rerunning this script from the start. If the issue persists, contact Teleport support." exit 1 fi else log "shasum/sha256sum utilities not found, will skip checksum validation" fi } # gets the filename from a full path (https://target.site/path/to/file.tar.gz -> file.tar.gz) get_download_filename() { echo "${1##*/}"; } # gets the pid of any running teleport process (and converts newlines to spaces) get_teleport_pid() { check_exists_fatal pgrep xargs pgrep teleport | xargs echo } # returns a command which will start teleport using the config get_teleport_start_command() { echo "${TELEPORT_BINARY_DIR}/teleport start --config=${TELEPORT_CONFIG_PATH}" } # installs the teleport-provided launchd config install_launchd_config() { log "Installing Teleport launchd config to ${LAUNCHD_CONFIG_PATH}" ${COPY_COMMAND} ./${TELEPORT_ARCHIVE_PATH}/examples/launchd/com.goteleport.teleport.plist ${LAUNCHD_CONFIG_PATH}/com.goteleport.teleport.plist } # installs the teleport-provided systemd unit install_systemd_unit() { log "Installing Teleport systemd unit to ${SYSTEMD_UNIT_PATH}" ${COPY_COMMAND} ./${TELEPORT_ARCHIVE_PATH}/examples/systemd/teleport.service ${SYSTEMD_UNIT_PATH} log "Reloading unit files (systemctl daemon-reload)" systemctl daemon-reload } # formats the arguments as a yaml list get_yaml_list() { name="${1}" list="${2}" indentation="${3}" echo "${indentation}${name}:" for item in ${list}; do echo "${indentation}- ${item}" done } # installs the provided teleport config (for app service) install_teleport_app_config() { log "Writing Teleport app service config to ${TELEPORT_CONFIG_PATH}" CA_PINS_CONFIG=$(get_yaml_list "ca_pin" "${CA_PIN_HASHES}" " ") cat << EOF > ${TELEPORT_CONFIG_PATH} version: v3 teleport: nodename: ${NODENAME} auth_token: ${JOIN_TOKEN} ${CA_PINS_CONFIG} proxy_server: ${TARGET_HOSTNAME}:${TARGET_PORT} log: output: stderr severity: INFO auth_service: enabled: no ssh_service: enabled: no proxy_service: enabled: no app_service: enabled: yes apps: - name: "${APP_NAME}" uri: "${APP_URI}" public_addr: ${APP_PUBLIC_ADDR} EOF } # installs the provided teleport config (for database service) install_teleport_database_config() { log "Writing Teleport database service config to ${TELEPORT_CONFIG_PATH}" CA_PINS_CONFIG=$(get_yaml_list "ca_pin" "${CA_PIN_HASHES}" " ") # This file is processed by `shellschek` as part of the lint step # It detects an issue because of un-set variables - $index and $line. This check is called SC2154. # However, that's not an issue, because those variables are replaced when we run go's text/template engine over it. # When executing the script, those are no long variables but actual values. # shellcheck disable=SC2154 cat << EOF > ${TELEPORT_CONFIG_PATH} version: v3 teleport: nodename: ${NODENAME} auth_token: ${JOIN_TOKEN} ${CA_PINS_CONFIG} proxy_server: ${TARGET_HOSTNAME}:${TARGET_PORT} log: output: stderr severity: INFO auth_service: enabled: no ssh_service: enabled: no proxy_service: enabled: no db_service: enabled: "yes" resources: - labels: EOF } # installs the provided teleport config (for node service) install_teleport_node_config() { log "Writing Teleport node service config to ${TELEPORT_CONFIG_PATH}" ${TELEPORT_BINARY_DIR}/teleport node configure \ --token ${JOIN_TOKEN} \ ${JOIN_METHOD_FLAG} \ --ca-pin ${CA_PINS} \ --proxy ${TARGET_HOSTNAME}:${TARGET_PORT} \ "${LABELS_FLAG[@]}" \ --output ${TELEPORT_CONFIG_PATH} } # checks whether the given host is running macOS is_macos_host() { if [[ ${OSTYPE} == "darwin"* ]]; then return 0; else return 1; fi } # checks whether teleport is already running on the host is_running_teleport() { check_exists_fatal pgrep TELEPORT_PID=$(get_teleport_pid) if [[ "${TELEPORT_PID}" != "" ]]; then return 0; else return 1; fi } # checks whether the given host is running systemd as its init system is_using_systemd() { if [ -d /run/systemd/system ]; then return 0; else return 1; fi } # prints a warning if the host isn't running systemd no_systemd_warning() { log_important "This host is not running systemd, so Teleport cannot be started automatically when it exits." log_important "Please investigate an alternative way to keep Teleport running." log_important "You can find information in our documentation: ${TELEPORT_DOCS_URL}" log_important "For now, Teleport will be started in the foreground - you can press Ctrl+C to exit." log_only log_only "Run this command to start Teleport in future:" log_only "$(get_teleport_start_command)" log_only log_only "------------------------------------------------------------------------" log_only "| IMPORTANT: TELEPORT WILL STOP RUNNING AFTER YOU CLOSE THIS TERMINAL! |" log_only "| YOU MUST CONFIGURE A SERVICE MANAGER TO MAKE IT RUN ON STARTUP! |" log_only "------------------------------------------------------------------------" log_only } # print a message giving the name of the node and a link to the docs # gives some debugging instructions if the service didn't start successfully print_welcome_message() { log_only "" if is_running_teleport; then log_only "Teleport has been started." log_only "" if is_using_systemd; then log_only "View its status with 'sudo systemctl status teleport.service'" log_only "View Teleport logs using 'sudo journalctl -u teleport.service'" log_only "To stop Teleport, run 'sudo systemctl stop teleport.service'" log_only "To start Teleport again if you stop it, run 'sudo systemctl start teleport.service'" elif is_macos_host; then log_only "View Teleport logs in '${MACOS_STDERR_LOG}' and '${MACOS_STDOUT_LOG}'" log_only "To stop Teleport, run 'sudo launchctl unload ${LAUNCHD_CONFIG_PATH}/com.goteleport.teleport.plist'" log_only "To start Teleport again if you stop it, run 'sudo launchctl load ${LAUNCHD_CONFIG_PATH}/com.goteleport.teleport.plist'" fi log_only "" log_only "You can see this node connected in the Teleport web UI or 'tsh ls' with the name '${NODENAME}'" log_only "Find more details on how to use Teleport here: https://goteleport.com/docs/user-manual/" else log_important "The Teleport service was installed, but it does not appear to have started successfully." if is_using_systemd; then log_important "Check the Teleport service's status with 'systemctl status teleport.service'" log_important "View Teleport logs with 'journalctl -u teleport.service'" elif is_macos_host; then log_important "Check Teleport logs in '${MACOS_STDERR_LOG}' and '${MACOS_STDOUT_LOG}'" fi log_important "Contact Teleport support for further assistance." fi log_only "" } # start teleport in foreground (when there's no systemd) start_teleport_foreground() { log "Starting Teleport in the foreground" # shellcheck disable=SC2091 $(get_teleport_start_command) } # start teleport via launchd (after installing config) start_teleport_launchd() { log "Starting Teleport via launchctl. It will automatically be started whenever the system reboots." launchctl load ${LAUNCHD_CONFIG_PATH}/com.goteleport.teleport.plist sleep ${ALIVE_CHECK_DELAY} } # start teleport via systemd (after installing unit) start_teleport_systemd() { log "Starting Teleport via systemd. It will automatically be started whenever the system reboots." systemctl enable teleport.service systemctl start teleport.service sleep ${ALIVE_CHECK_DELAY} } # checks whether teleport binaries exist on the host teleport_binaries_exist() { for BINARY_NAME in teleport tctl tsh; do if [ -f ${TELEPORT_BINARY_DIR}/${BINARY_NAME} ]; then return 0; else return 1; fi done } # checks whether a teleport config exists on the host teleport_config_exists() { if [ -f ${TELEPORT_CONFIG_PATH} ]; then return 0; else return 1; fi; } # checks whether a teleport data dir exists on the host teleport_datadir_exists() { if [ -d ${TELEPORT_DATA_DIR} ]; then return 0; else return 1; fi; } # error out if any required values are not set check_set TELEPORT_VERSION check_set TARGET_HOSTNAME check_set TARGET_PORT check_set JOIN_TOKEN check_set CA_PIN_HASHES if [[ "${APP_INSTALL_MODE}" == "true" ]]; then check_set APP_NAME check_set APP_URI check_set APP_PUBLIC_ADDR fi ### # main script starts here ### # check connectivity to teleport server/port if [[ "${IGNORE_CONNECTIVITY_CHECK}" == "true" ]]; then log "TELEPORT_IGNORE_CONNECTIVITY_CHECK=true, not running connectivity check" else log "Checking TCP connectivity to Teleport server (${TARGET_HOSTNAME}:${TARGET_PORT})" if ! check_connectivity "${TARGET_HOSTNAME}" "${TARGET_PORT}"; then # if we don't have a connectivity test method assigned, we know we couldn't run the test if [[ ${CONNECTIVITY_TEST_METHOD} == "" ]]; then log "Couldn't find nc, telnet or /dev/tcp to do a connection test" log "Going to blindly continue without testing connectivity" else log_important "Couldn't open a connection to the Teleport server (${TARGET_HOSTNAME}:${TARGET_PORT}) via ${CONNECTIVITY_TEST_METHOD}" log_important "This issue will need to be fixed before the script can continue." log_important "If you think this is an error, add 'export TELEPORT_IGNORE_CONNECTIVITY_CHECK=true && ' before the curl command which runs the script." exit 1 fi else log "Connectivity to Teleport server (via ${CONNECTIVITY_TEST_METHOD}) looks good" fi fi # use OSTYPE variable to figure out host type/arch if [[ "${OSTYPE}" == "linux"* ]]; then # linux host, now detect arch TELEPORT_BINARY_TYPE="linux" ARCH=$(uname -m) log "Detected host: ${OSTYPE}, using Teleport binary type ${TELEPORT_BINARY_TYPE}" if [[ ${ARCH} == "armv7l" ]]; then TELEPORT_ARCH="arm" elif [[ ${ARCH} == "aarch64" ]]; then TELEPORT_ARCH="arm64" elif [[ ${ARCH} == "x86_64" ]]; then TELEPORT_ARCH="amd64" elif [[ ${ARCH} == "i686" ]]; then TELEPORT_ARCH="386" else log_important "Error: cannot detect architecture from uname -m: ${ARCH}" exit 1 fi log "Detected arch: ${ARCH}, using Teleport arch ${TELEPORT_ARCH}" # if the download format is already set, we have no need to detect distro if [[ ${TELEPORT_FORMAT} == "" ]]; then # detect distro # if /etc/os-release doesn't exist, we need to use some other logic if [ ! -f /etc/os-release ]; then if [ -f /etc/centos-release ]; then if grep -q 'CentOS release 6' /etc/centos-release; then log_important "Detected host type: CentOS 6 [$(cat /etc/centos-release)]" log_important "Teleport will not work on CentOS 6 -based servers due to the glibc version being too low." exit 1 fi elif [ -f /etc/redhat-release ]; then if grep -q 'Red Hat Enterprise Linux Server release 5' /etc/redhat-release; then log_important "Detected host type: RHEL5 [$(cat /etc/redhat-release)]" log_important "Teleport will not work on RHEL5-based servers due to the glibc version being too low." exit 1 elif grep -q 'Red Hat Enterprise Linux Server release 6' /etc/redhat-release; then log_important "Detected host type: RHEL6 [$(cat /etc/redhat-release)]" log_important "Teleport will not work on RHEL6-based servers due to the glibc version being too low." exit 1 fi fi # use ID_LIKE value from /etc/os-release (if set) # this is 'debian' on ubuntu/raspbian, 'centos rhel fedora' on amazon linux etc else check_exists_fatal cut DISTRO_TYPE=$(grep ID_LIKE /etc/os-release | cut -d= -f2) || true if [[ ${DISTRO_TYPE} == "" ]]; then # use exact ID value from /etc/os-release if ID_LIKE is not set DISTRO_TYPE=$(grep -w ID /etc/os-release | cut -d= -f2) fi if [[ ${DISTRO_TYPE} =~ "debian" ]]; then TELEPORT_FORMAT="deb" elif [[ "$DISTRO_TYPE" =~ "amzn"* ]] || [[ ${DISTRO_TYPE} =~ "centos"* ]] || [[ ${DISTRO_TYPE} =~ "rhel" ]] || [[ ${DISTRO_TYPE} =~ "fedora"* ]] || \ [[ ${DISTRO_TYPE} == *"suse"* ]] || [[ ${DISTRO_TYPE} =~ "sles"* ]]; then TELEPORT_FORMAT="rpm" else log "Couldn't match a distro type using /etc/os-release, falling back to tarball installer" TELEPORT_FORMAT="tarball" fi fi log "Detected distro type: ${DISTRO_TYPE}" #suse, also identified as sles, uses a different path for its systemd then other distro types like ubuntu if [[ ${DISTRO_TYPE} =~ "suse"* ]] || [[ ${DISTRO_TYPE} =~ "sles"* ]]; then SYSTEMD_UNIT_PATH="/etc/systemd/system/teleport.service" fi fi elif [[ "${OSTYPE}" == "darwin"* ]]; then # macOS host, now detect arch TELEPORT_BINARY_TYPE="darwin" ARCH=$(uname -m) log "Detected host: ${OSTYPE}, using Teleport binary type ${TELEPORT_BINARY_TYPE}" if [[ ${ARCH} == "arm64" ]]; then TELEPORT_ARCH="arm64" elif [[ ${ARCH} == "x86_64" ]]; then TELEPORT_ARCH="amd64" else log_important "Error: unsupported architecture from uname -m: ${ARCH}" exit 1 fi log "Detected macOS ${ARCH} architecture, using Teleport arch ${TELEPORT_ARCH}" TELEPORT_FORMAT="tarball" else log_important "Error - unsupported platform: ${OSTYPE}" exit 1 fi log "Using Teleport distribution: ${TELEPORT_FORMAT}" # create temporary directory and exit cleanup logic TEMP_DIR=$(mktemp -d -t teleport-XXXXXXXXXX) log "Created temp dir ${TEMP_DIR}" pushd "${TEMP_DIR}" >/dev/null 2>&1 finish() { popd >/dev/null 2>&1 rm -rf "${TEMP_DIR}" } trap finish EXIT # optional format override (mostly for testing) if [[ ${OVERRIDE_FORMAT} != "" ]]; then TELEPORT_FORMAT="${OVERRIDE_FORMAT}" log "Overriding TELEPORT_FORMAT to ${OVERRIDE_FORMAT}" fi # check whether teleport is running already # if it is, we exit gracefully with an error if is_running_teleport; then if [[ ${IGNORE_CHECKS} != "true" ]]; then TELEPORT_PID=$(get_teleport_pid) log_header "Warning: Teleport appears to already be running on this host (pid: ${TELEPORT_PID})" log_cleanup_message exit 1 else log "Ignoring is_running_teleport as requested" fi fi # check for existing config file if teleport_config_exists; then if [[ ${IGNORE_CHECKS} != "true" ]]; then log_header "Warning: There is already a Teleport config file present at ${TELEPORT_CONFIG_PATH}." log_cleanup_message exit 1 else log "Ignoring teleport_config_exists as requested" fi fi # check for existing data directory if teleport_datadir_exists; then if [[ ${IGNORE_CHECKS} != "true" ]]; then log_header "Warning: Found existing Teleport data directory (${TELEPORT_DATA_DIR})." log_cleanup_message exit 1 else log "Ignoring teleport_datadir_exists as requested" fi fi # check for existing binaries if teleport_binaries_exist; then if [[ ${IGNORE_CHECKS} != "true" ]]; then log_header "Warning: Found existing Teleport binaries under ${TELEPORT_BINARY_DIR}." log_cleanup_message exit 1 else log "Ignoring teleport_binaries_exist as requested" fi fi install_from_file() { # select correct URL/installation method based on distro if [[ ${TELEPORT_FORMAT} == "tarball" ]]; then URL="https://cdn.teleport.dev/${TELEPORT_PACKAGE_NAME}-v${TELEPORT_VERSION}-${TELEPORT_BINARY_TYPE}-${TELEPORT_ARCH}-bin.tar.gz" # check that needed tools are installed check_exists_fatal curl tar # download tarball log "Downloading Teleport ${TELEPORT_FORMAT} release ${TELEPORT_VERSION}" DOWNLOAD_FILENAME=$(get_download_filename "${URL}") download "${URL}" "${TEMP_DIR}/${DOWNLOAD_FILENAME}" # extract tarball tar -xzf "${TEMP_DIR}/${DOWNLOAD_FILENAME}" -C "${TEMP_DIR}" # install binaries to /usr/local/bin for BINARY in ${TELEPORT_BINARY_LIST}; do ${COPY_COMMAND} "${TELEPORT_ARCHIVE_PATH}/${BINARY}" "${TELEPORT_BINARY_DIR}/" done elif [[ ${TELEPORT_FORMAT} == "deb" ]]; then # convert teleport arch to deb arch if [[ ${TELEPORT_ARCH} == "amd64" ]]; then DEB_ARCH="amd64" elif [[ ${TELEPORT_ARCH} == "386" ]]; then DEB_ARCH="i386" elif [[ ${TELEPORT_ARCH} == "arm" ]]; then DEB_ARCH="arm" elif [[ ${TELEPORT_ARCH} == "arm64" ]]; then DEB_ARCH="arm64" fi URL="https://cdn.teleport.dev/${TELEPORT_PACKAGE_NAME}_${TELEPORT_VERSION}_${DEB_ARCH}.deb" check_deb_not_already_installed # check that needed tools are installed check_exists_fatal curl dpkg # download deb and register cleanup operation log "Downloading Teleport ${TELEPORT_FORMAT} release ${TELEPORT_VERSION}" DOWNLOAD_FILENAME=$(get_download_filename "${URL}") download "${URL}" "${TEMP_DIR}/${DOWNLOAD_FILENAME}" # install deb log "Using dpkg to install ${TEMP_DIR}/${DOWNLOAD_FILENAME}" dpkg -i "${TEMP_DIR}/${DOWNLOAD_FILENAME}" elif [[ ${TELEPORT_FORMAT} == "rpm" ]]; then # convert teleport arch to rpm arch if [[ ${TELEPORT_ARCH} == "amd64" ]]; then RPM_ARCH="x86_64" elif [[ ${TELEPORT_ARCH} == "386" ]]; then RPM_ARCH="i386" elif [[ ${TELEPORT_ARCH} == "arm" ]]; then RPM_ARCH="arm" elif [[ ${TELEPORT_ARCH} == "arm64" ]]; then RPM_ARCH="arm64" fi URL="https://cdn.teleport.dev/${TELEPORT_PACKAGE_NAME}-${TELEPORT_VERSION}-1.${RPM_ARCH}.rpm" check_rpm_not_already_installed # check for package managers if check_exists dnf; then log "Found 'dnf' package manager, using it" PACKAGE_MANAGER_COMMAND="dnf -y install" elif check_exists yum; then log "Found 'yum' package manager, using it" PACKAGE_MANAGER_COMMAND="yum -y localinstall" elif check_exists zypper; then log "Found 'zypper' package manager, using it" PACKAGE_MANAGER_COMMAND="zypper --non-interactive install" else PACKAGE_MANAGER_COMMAND="" log "Cannot find 'yum' or 'dnf' package manager commands, will try installing the rpm manually instead" fi # check that needed tools are installed check_exists_fatal curl log "Downloading Teleport ${TELEPORT_FORMAT} release ${TELEPORT_VERSION}" DOWNLOAD_FILENAME=$(get_download_filename "${URL}") download "${URL}" "${TEMP_DIR}/${DOWNLOAD_FILENAME}" # install with package manager if available if [[ ${PACKAGE_MANAGER_COMMAND} != "" ]]; then log "Installing Teleport release from ${TEMP_DIR}/${DOWNLOAD_FILENAME} using ${PACKAGE_MANAGER_COMMAND}" # install rpm with package manager ${PACKAGE_MANAGER_COMMAND} "${TEMP_DIR}/${DOWNLOAD_FILENAME}" # use rpm if we couldn't find a package manager else # install RPM (in upgrade mode) log "Using rpm to install ${TEMP_DIR}/${DOWNLOAD_FILENAME}" rpm -Uvh "${TEMP_DIR}/${DOWNLOAD_FILENAME}" fi else log_important "Can't figure out what Teleport format to use" exit 1 fi } install_from_repo() { if [[ "${REPO_CHANNEL}" == "" ]]; then # By default, use the current version's channel. REPO_CHANNEL=stable/v"${TELEPORT_VERSION//.*/}" fi # Populate $ID, $VERSION_ID, $VERSION_CODENAME and other env vars identifying the OS. # shellcheck disable=SC1091 . /etc/os-release PACKAGE_LIST=$(package_list) if [ "$ID" == "debian" ] || [ "$ID" == "ubuntu" ]; then # old versions of ubuntu require that keys get added by `apt-key add`, without # adding the key apt shows a key signing error when installing teleport. if [[ ($ID == "ubuntu" && $VERSION_ID == "16.04") || \ ($ID == "debian" && $VERSION_ID == "9" ) ]]; then apt install apt-transport-https gnupg -y curl -fsSL https://apt.releases.teleport.dev/gpg | apt-key add - echo "deb https://apt.releases.teleport.dev/${ID} ${VERSION_CODENAME} ${REPO_CHANNEL}" > /etc/apt/sources.list.d/teleport.list else curl -fsSL https://apt.releases.teleport.dev/gpg \ -o /usr/share/keyrings/teleport-archive-keyring.asc echo "deb [signed-by=/usr/share/keyrings/teleport-archive-keyring.asc] \ https://apt.releases.teleport.dev/${ID} ${VERSION_CODENAME} ${REPO_CHANNEL}" > /etc/apt/sources.list.d/teleport.list fi apt-get update apt-get install -y ${PACKAGE_LIST} elif [ "$ID" = "amzn" ] || [ "$ID" = "rhel" ] || [ "$ID" = "centos" ]; then if [ "$ID" = "rhel" ]; then VERSION_ID="${VERSION_ID//.*/}" # convert version numbers like '7.2' to only include the major version fi yum install -y yum-utils yum-config-manager --add-repo \ "$(rpm --eval "https://yum.releases.teleport.dev/$ID/$VERSION_ID/Teleport/%{_arch}/${REPO_CHANNEL}/teleport.repo")" # Remove metadata cache to prevent cache from other channel (eg, prior version) # See: https://github.com/gravitational/teleport/issues/22581 yum --disablerepo="*" --enablerepo="teleport" clean metadata yum install -y ${PACKAGE_LIST} elif [ "$ID" = "sles" ] || [ "$ID" = "opensuse-tumbleweed" ] || [ "$ID" = "opensuse-leap" ]; then if [ "$ID" = "opensuse-tumbleweed" ]; then VERSION_ID="15" # tumbleweed uses dated VERSION_IDs like 20230702 else VERSION_ID="${VERSION_ID//.*/}" # convert version numbers like '7.2' to only include the major version fi sudo rpm --import "https://zypper.releases.teleport.dev/gpg" sudo zypper --non-interactive addrepo "$(rpm --eval "https://zypper.releases.teleport.dev/sles/$VERSION_ID/Teleport/%{_arch}/${REPO_CHANNEL}/teleport.repo")" sudo zypper --gpg-auto-import-keys refresh sudo zypper --non-interactive install ${PACKAGE_LIST} else echo "Unsupported distro: $ID" exit 1 fi } # package_list returns the list of packages to install. # The list of packages can be fed into yum or apt because they already have the expected format when pinning versions. package_list() { TELEPORT_PACKAGE_PIN_VERSION=${TELEPORT_PACKAGE_NAME} TELEPORT_UPDATER_PIN_VERSION="${TELEPORT_PACKAGE_NAME}-updater" if [[ "${TELEPORT_FORMAT}" == "deb" ]]; then TELEPORT_PACKAGE_PIN_VERSION+="=${TELEPORT_VERSION}" TELEPORT_UPDATER_PIN_VERSION+="=${TELEPORT_VERSION}" elif [[ "${TELEPORT_FORMAT}" == "rpm" ]]; then TELEPORT_YUM_VERSION="${TELEPORT_VERSION//-/_}" TELEPORT_PACKAGE_PIN_VERSION+="-${TELEPORT_YUM_VERSION}" TELEPORT_UPDATER_PIN_VERSION+="-${TELEPORT_YUM_VERSION}" fi PACKAGE_LIST=${TELEPORT_PACKAGE_PIN_VERSION} # (warning): This expression is constant. Did you forget the $ on a variable? # Disabling the warning above because expression is templated. # shellcheck disable=SC2050 if is_using_systemd && [[ "false" == "true" ]]; then # Teleport Updater requires systemd. PACKAGE_LIST+=" ${TELEPORT_UPDATER_PIN_VERSION}" fi echo ${PACKAGE_LIST} } is_repo_available() { if [[ "${OSTYPE}" != "linux"* ]]; then return 1 fi # Populate $ID, $VERSION_ID and other env vars identifying the OS. # shellcheck disable=SC1091 . /etc/os-release # The following distros+version have a Teleport repository to install from. case "${ID}-${VERSION_ID}" in ubuntu-16.04* | ubuntu-18.04* | ubuntu-20.04* | ubuntu-22.04* | \ debian-9* | debian-10* | debian-11* | debian-12* | \ rhel-7* | rhel-8* | rhel-9* | \ centos-7* | centos-8* | centos-9* | \ amzn-2 | amzn-2023 | \ opensuse-tumbleweed* | sles-12* | sles-15* | opensuse-leap-15*) return 0;; esac return 1 } if is_repo_available; then log "Installing repo for distro $ID." install_from_repo else log "Installing from binary file." install_from_file fi # check that teleport binary can be found and runs if ! check_teleport_binary; then log_important "The Teleport binary could not be found at ${TELEPORT_BINARY_DIR} as expected." log_important "This usually means that there was an error during installation." log_important "Check this log for obvious signs of error and contact Teleport support" log_important "for further assistance." exit 1 fi # install teleport config # check the mode and write the appropriate config type if [[ "${APP_INSTALL_MODE}" == "true" ]]; then install_teleport_app_config elif [[ "${DB_INSTALL_MODE}" == "true" ]]; then install_teleport_database_config else install_teleport_node_config fi # Used to track whether a Teleport agent was installed using this method. export TELEPORT_INSTALL_METHOD_NODE_SCRIPT="true" # install systemd unit if applicable (linux hosts) if is_using_systemd; then log "Host is using systemd" # we only need to manually install the systemd config if teleport was installed via tarball # all other packages will deploy it automatically if [[ ${TELEPORT_FORMAT} == "tarball" ]]; then install_systemd_unit fi start_teleport_systemd print_welcome_message # install launchd config on macOS hosts elif is_macos_host; then log "Host is running macOS" install_launchd_config start_teleport_launchd print_welcome_message # not a macOS host and no systemd available, print a warning # and temporarily start Teleport in the foreground else log "Host does not appear to be using systemd" no_systemd_warning fi