install.fairie/docs/scripts/utility/cloudflared-ssh.sh.md
2023-08-08 03:06:49 -04:00

229 lines
11 KiB
Markdown

---
title: CloudFlare SSO SSH Tunnel Setup
description: Installs and configures cloudflared for short-lived SSH certificates authenticated via SSO
sidebar_label: CloudFlare SSO SSH Tunnel Setup
slug: /scripts/utility/cloudflared-ssh.sh
githubLocation: https://github.com/megabyte-labs/install.doctor/blob/master/scripts/cloudflared-ssh.sh
scriptLocation: https://github.com/megabyte-labs/install.doctor/raw/master/scripts/cloudflared-ssh.sh
repoLocation: scripts/cloudflared-ssh.sh
---
# CloudFlare SSO SSH Tunnel Setup
Installs and configures cloudflared for short-lived SSH certificates authenticated via SSO
## Overview
This script ensures Homebrew is installed and then uses Homebrew to ensure `cloudflared` is installed.
After that, it connects `cloudflared` to CloudFlare Teams and sets up short-lived SSH certificates so
that you do not have to manage SSH keys and instead use SSO (Single Sign-On) via CloudFlare Teams.
**Note**: `https://install.doctor/cloudflared` points to this file.
## Variables
The `SSH_DOMAIN` variable should be set to the endpoint you want to be able to SSH into. The SSH endpoint(s)
that are created depend on what type of system is being configured. Some device types include multiple
properties that need multiple unique SSH endpoints. The `SSH_DOMAIN` must be passed to this script or else
it will default to `ssh.megabyte.space`.
* For most installations, the configured domain will be `$(hostname).${SSH_DOMAIN}`
* If Qubes is being configured, then the configured domain will be `$(hostname)-qube.${SSH_DOMAIN}`
* If [EasyEngine](https://easyengine.io/) is installed, then each domain setup with EasyEngine is configured to have an `ssh` subdomain (i.e. `ssh.example.com` for `example.com`)
There are other optional variables that can be customized as well:
* `CF_TUNNEL_NAME` - The ID to assign to the tunnel that `cloudflared` creates (`default-ssh-tunnel` by default)
## Notes
Since the certificates are "short-lived", you will have to periodically re-authenticate against the
SSO authentication endpoint that is hosted by CloudFlare Teams (or an identity provider of your choosing).
This script will likely only work on AMD x64 devices.
Some of the commands are conditionally run based on whether or not the `CRONTAB_JOB` environment variable is set.
This is to accomodate EasyEngine installations where the list of SSH endpoints is variable. Both the initial
setup and updates are applied using this script (via a cronjob that does not need to run initialization tasks during
the cronjobs).
## Links
[SSH with short-lived certificates](https://developers.cloudflare.com/cloudflare-one/tutorials/ssh-cert-bastion/)
## Source Code
```
#!/usr/bin/env bash
# @file CloudFlare SSO SSH Tunnel Setup
# @brief Installs and configures cloudflared for short-lived SSH certificates authenticated via SSO
# @description
# This script ensures Homebrew is installed and then uses Homebrew to ensure `cloudflared` is installed.
# After that, it connects `cloudflared` to CloudFlare Teams and sets up short-lived SSH certificates so
# that you do not have to manage SSH keys and instead use SSO (Single Sign-On) via CloudFlare Teams.
#
# **Note**: `https://install.doctor/cloudflared` points to this file.
#
# ## Variables
#
# The `SSH_DOMAIN` variable should be set to the endpoint you want to be able to SSH into. The SSH endpoint(s)
# that are created depend on what type of system is being configured. Some device types include multiple
# properties that need multiple unique SSH endpoints. The `SSH_DOMAIN` must be passed to this script or else
# it will default to `ssh.megabyte.space`.
#
# * For most installations, the configured domain will be `$(hostname).${SSH_DOMAIN}`
# * If Qubes is being configured, then the configured domain will be `$(hostname)-qube.${SSH_DOMAIN}`
# * If [EasyEngine](https://easyengine.io/) is installed, then each domain setup with EasyEngine is configured to have an `ssh` subdomain (i.e. `ssh.example.com` for `example.com`)
#
# There are other optional variables that can be customized as well:
#
# * `CF_TUNNEL_NAME` - The ID to assign to the tunnel that `cloudflared` creates (`default-ssh-tunnel` by default)
#
# ## Notes
#
# Since the certificates are "short-lived", you will have to periodically re-authenticate against the
# SSO authentication endpoint that is hosted by CloudFlare Teams (or an identity provider of your choosing).
# This script will likely only work on AMD x64 devices.
#
# Some of the commands are conditionally run based on whether or not the `CRONTAB_JOB` environment variable is set.
# This is to accomodate EasyEngine installations where the list of SSH endpoints is variable. Both the initial
# setup and updates are applied using this script (via a cronjob that does not need to run initialization tasks during
# the cronjobs).
#
# ## Links
#
# [SSH with short-lived certificates](https://developers.cloudflare.com/cloudflare-one/tutorials/ssh-cert-bastion/)
# @description Ensure dependencies like `git` and `curl` are installed (among a few other lightweight system packages)
if ! command -v curl > /dev/null || ! command -v git > /dev/null || ! command -v expect > /dev/null || ! command -v rsync > /dev/null; then
if command -v apt-get > /dev/null; then
# @description Ensure `build-essential`, `curl`, `expect`, `git`, and `rsync` are installed on Debian / Ubuntu
sudo apt-get update
sudo apt-get install -y build-essential curl expect git rsync
elif command -v dnf > /dev/null; then
# @description Ensure `curl`, `expect`, `git`, and `rsync` are installed on Fedora
sudo dnf install -y curl expect git rsync
elif command -v yum > /dev/null; then
# @description Ensure `curl`, `expect`, `git`, and `rsync` are installed on CentOS
sudo yum install -y curl expect git rsync
elif command -v pacman > /dev/null; then
# @description Ensure `curl`, `expect`, `git`, and `rsync` are installed on Archlinux
sudo pacman update
sudo pacman -Sy curl expect git rsync
elif command -v zypper > /dev/null; then
# @description Ensure `curl`, `expect`, `git`, and `rsync` are installed on OpenSUSE
sudo zypper install -y curl expect git rsync
elif command -v apk > /dev/null; then
# @description Ensure `curl`, `expect`, `git`, and `rsync` are installed on Alpine
apk add curl expect git rsync
elif [ -d /Applications ] && [ -d /Library ]; then
# @description Ensure CLI developer tools are available on macOS (via `xcode-select`)
sudo xcode-select -p >/dev/null 2>&1 || xcode-select --install
elif [[ "$OSTYPE" == 'cygwin' ]] || [[ "$OSTYPE" == 'msys' ]] || [[ "$OSTYPE" == 'win32' ]]; then
# @description Ensure `curl`, `expect`, `git`, and `rsync` are installed on Windows
choco install -y curl expect git rsync
fi
fi
# @description Ensure Homebrew is installed and available in the `PATH`
if ! command -v brew > /dev/null; then
if [ -d /home/linuxbrew/.linuxbrew/bin ]; then
eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
if ! command -v brew > /dev/null; then
echo "The /home/linuxbrew/.linuxbrew directory exists but something is not right. Try removing it and running the script again." && exit 1
fi
else
# @description Installs Homebrew and addresses a couple potential issues
if command -v sudo > /dev/null && sudo -n true; then
echo | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
else
echo "Homebrew is not installed. The script will attempt to install Homebrew and you might be prompted for your password."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || BREW_EXIT_CODE="$?"
if [ -n "$BREW_EXIT_CODE" ]; then
if command -v brew > /dev/null; then
echo "Homebrew was installed but part of the installation failed. Trying a few things to fix the installation.."
BREW_DIRS="share/man share/doc share/zsh/site-functions etc/bash_completion.d"
for BREW_DIR in $BREW_DIRS; do
if [ -d "$(brew --prefix)/$BREW_DIR" ]; then
sudo chown -R "$(whoami)" "$(brew --prefix)/$BREW_DIR"
fi
done
brew update --force --quiet
fi
fi
fi
# @description Ensures the `brew` binary is available on Linux machines. macOS installs `brew` into the default `PATH`
# so nothing needs to be done for macOS.
if [ -d /home/linuxbrew/.linuxbrew/bin ]; then
eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
fi
fi
fi
# @description Ensures `cloudflared` is installed via Homebrew
if ! command -v cloudflared > /dev/null; then
brew install cloudflared
fi
# @description Detect the SSH port being used
SSH_PORT="22"
if sudo cat /etc/ssh/sshd_config | grep '^Port ' > /dev/null; then
SSH_PORT="$(sudo cat /etc/ssh/sshd_config | grep '^Port ' | sed 's/Port //')"
fi
# @description Login to the CloudFlare network (if running outside a cronjob) and acquire the tunnel ID
if [ -z "$CRONTAB_JOB" ]; then
sudo cloudflared tunnel login
sudo cloudflared tunnel create "${CF_TUNNEL_NAME:=default-ssh-tunnel}"
fi
TUNNEL_ID="$(sudo cloudflared tunnel list | grep " ${CF_TUNNEL_NAME} " | sed 's/^\([^ ]*\).*$/\1/')"
# @description Ensure CloudFlare tunnel ID was acquired, then add tunnel route, and create
# tunnel configuration based on what type of machine is being configured (i.e. regular, Qubes, or EasyEngine)
if [ -n "$TUNNEL_ID" ]; then
if [ -z "$CRONTAB_JOB" ]; then
sudo cloudflared tunnel route ip add 100.64.0.0/10 "${TUNNEL_ID}"
fi
# @description Create the `/root/.cloudflared/config.yml`
CONFIG_TMP="$(mktemp)"
echo "tunnel: ${TUNNEL_ID}" > "$CONFIG_TMP"
echo "credentials-file: /root/.cloudflared/${TUNNEL_ID}.json" >> "$CONFIG_TMP"
echo "" >> "$CONFIG_TMP"
echo "ingress:" >> "$CONFIG_TMP"
if [ -d '/opt/easyengine/sites' ]; then
DOMAINS_HOSTED="$(find /opt/easyengine/sites -maxdepth 1 -mindepth 1 -type d | sed 's/.*sites\///' | xargs)"
else
if [ -d /etc/qubes ]; then
DOMAINS_HOSTED="$(hostname)-qube.${SSH_DOMAIN:-ssh.megabyte.space}"
else
DOMAINS_HOSTED="$(hostname).${SSH_DOMAIN:-ssh.megabyte.space}"
fi
fi
for DOMAIN in $DOMAINS_HOSTED; do
echo " - hostname: ${DOMAIN}" >> "$CONFIG_TMP"
echo " service: ssh://localhost:$SSH_PORT" >> "$CONFIG_TMP"
done
echo " - service: http_status:404" >> "$CONFIG_TMP"
echo "" >> "$CONFIG_TMP"
sudo mkdir -p /root/.cloudflared
sudo mv "$CONFIG_TMP" /root/.cloudflared/config.yml
# @description Install the `cloudflared` system service (if it is not a cronjob)
if [ -z "$CRONTAB_JOB" ]; then
sudo cloudflared service install
fi
else
echo "ERROR: Unable to acquire CloudFlare tunnel ID" && exit 1
fi
# @description Restart if the config file changed
if [ ! -f /root/.cloudflared/config-previous.yml ] || [ "$(cat /root/.cloudflared/config-previous.yml)" != "$(cat /root/.cloudflared/config.yml)" ]; then
# @todo Add restart command for non-systemctl devices (i.e. macOS)
if command -v systemctl > /dev/null; then
sudo systemctl restart cloudflared
fi
sudo cp /root/.cloudflared/config.yml /root/.cloudflared/config-previous.yml
fi
```