-
Notifications
You must be signed in to change notification settings - Fork 126
SSH Connect to Mezz PC #3685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
SSH Connect to Mezz PC #3685
Changes from all commits
cbe3bd4
7d42717
30e0cd9
bbfceb5
b625696
97f2cdc
17ba258
8f2e4dc
f26fefc
19b2b38
5da44d2
d4703a0
8a4ba50
94e47b8
d8bba65
b6cecf5
6cb0fbe
e67fa4a
b7ae3a3
310a7e0
7ef0e0d
5fda9cf
b00018f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| #!/bin/bash | ||
|
|
||
| # Connects to the Mezz PC | ||
| # Receives safety check results from the server, and warns | ||
| # the user if other users are currently connected remotely or using in person | ||
| # Allows user to force a connection | ||
|
|
||
| SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) | ||
|
|
||
| PC_NAME="thunderbots" | ||
| TAILSCALE_HOSTNAME="$PC_NAME" | ||
|
|
||
| bash "$SCRIPT_DIR/utils/check_tailscale.sh" | ||
|
|
||
| # Get the Tailscale IP of the Main PC | ||
| TARGET_IP=$(tailscale ip -4 $TAILSCALE_HOSTNAME) | ||
|
|
||
| if [ -z "$TARGET_IP" ]; then | ||
| echo "Could not find Main PC on Tailscale. Are you logged in?" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # flag used to force a connection despite warnings | ||
| FORCE_FLAG="NORMAL" | ||
| SSH_TARGET="thunderbots@$TARGET_IP" | ||
|
|
||
| # first, check server status (if other users are using the PC) | ||
| RESPONSE=$(SSH_CHECK_MODE=1 ssh -o SendEnv=SSH_CHECK_MODE -t $SSH_TARGET "check_status" 2>&1) | ||
|
|
||
| RED='\e[1;31m' | ||
| NC='\e[0m' | ||
|
|
||
| # someone is using IRL | ||
| if [[ "$RESPONSE" == *"STATUS_BUSY_LOCAL"* ]]; then | ||
| echo -e "⚠️ ${RED}WARNING${NC}: Someone is physically logged into the PC onsite." | ||
| read -p "Do you want to force the connection? (y/n): " choice | ||
|
|
||
| # exits if user does not want to force | ||
| [[ "$choice" == [yY] ]] && FORCE_FLAG="FORCE" || exit 1 | ||
|
|
||
| # someone is connected remotely | ||
| elif [[ "$RESPONSE" == *"STATUS_BUSY_REMOTE"* ]]; then | ||
| echo -e "⚠️ ${RED}WARNING${NC}: Other SSH users are connected:" | ||
| echo "$RESPONSE" | grep "List" | ||
| read -p "Do you want to force the connection? (y/n): " choice | ||
|
|
||
| # exits if user does not want to force | ||
| [[ "$choice" == [yY] ]] && FORCE_FLAG="FORCE" || exit 1 | ||
| fi | ||
|
|
||
| # if the user wanted to force the connection | ||
| if [ "$FORCE_FLAG" == "FORCE" ]; then | ||
| echo "Force connecting..." | ||
| FORCE_CONNECT=1 ssh -o SendEnv=FORCE_CONNECT -t $SSH_TARGET | ||
| else | ||
| ssh -t $SSH_TARGET | ||
| fi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| #!/bin/bash | ||
|
|
||
| # Generates a private / public key pair for connecting via ssh | ||
| # Adds the Mezz PC's IP to the ssh config file, and sets the new key as the identity to use | ||
| # And sets the key file's permissions correctly | ||
| # Each key also has a username attached to it for easier identification | ||
|
|
||
| PC_NAME="thunderbots" | ||
| ALIAS="mezzsh" | ||
|
|
||
| GREEN='\e[1;32m' | ||
| NC='\e[0m' | ||
|
|
||
| # get the actual current user despite running in sudo | ||
| USER_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6) | ||
|
|
||
| # Check for root privileges | ||
| if [[ $EUID -ne 0 ]]; then | ||
| echo "This script must be run as root (use sudo)" | ||
| exit 1 | ||
| fi | ||
|
|
||
| SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) | ||
|
|
||
| bash "$SCRIPT_DIR/utils/check_tailscale.sh" | ||
|
|
||
| # Get the Tailscale IP of the Main PC | ||
| TARGET_IP=$(tailscale ip -4 $PC_NAME) | ||
|
|
||
| if [ -z "$TARGET_IP" ]; then | ||
| echo "Could not find Main PC on Tailscale. Are you logged in?" | ||
| exit 1 | ||
| fi | ||
|
|
||
| read -p "Enter a username for yourself. Please make it somewhat recognizable, preferably just your first name: " USERNAME | ||
|
|
||
| KEY_NAME="id_rsa_$ALIAS" | ||
| KEY_PATH="/.ssh/$KEY_NAME" | ||
| USER_KEY_PATH="$USER_HOME/.ssh/$KEY_NAME" | ||
|
|
||
| echo -e "\n--- Generating SSH Key Pair ---" | ||
|
|
||
| # username is appended as a comment to the key | ||
| ssh-keygen -t rsa -b 4096 -f "$USER_KEY_PATH" -C "$USERNAME" -N "" | ||
|
|
||
| # ssh is particular about permissions on the key file | ||
| # specifically, the private key file should be owned and should only be readable by the current user | ||
| sudo chown $SUDO_USER:$SUDO_USER $USER_KEY_PATH | ||
| sudo chmod 400 $USER_KEY_PATH | ||
| sudo chmod 400 "$USER_KEY_PATH.pub" | ||
|
|
||
| echo -e "\n--- Configuring SSH Alias ---" | ||
| cat <<EOF >> "$USER_HOME/.ssh/config" | ||
|
|
||
| Host $TARGET_IP | ||
| User $PC_NAME | ||
| IdentityFile ~$KEY_PATH | ||
| IdentitiesOnly yes | ||
| SendEnv SSH_CHECK_MODE FORCE_CONNECT | ||
| EOF | ||
|
|
||
| echo -e "\n--- PUBLIC KEY ---" | ||
| cat "${KEY_PATH}.pub" | ||
| echo "--------------------------------------------" | ||
|
|
||
| echo -e "${GREEN}Success!${NC} Please provide the whole public key above (at "$KEY_PATH.pub") to a software lead to finish setup\n" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| #!/bin/bash | ||
|
|
||
| # Adds a single public key to the Mezz PC's authorized_keys file | ||
| # along with other necessary config options | ||
|
|
||
| # the script is run as sudo, but we want to modify the current user's authorized_keys | ||
| # defaults to the "thunderbots" user | ||
| USER_HOME=$(tmp=$(getent passwd "$SUDO_USER" | cut -d: -f6); echo "${tmp:-/home/thunderbots}") | ||
|
|
||
| AUTH_KEYS="$USER_HOME/.ssh/authorized_keys" | ||
| SERVER_SCRIPT="/home/thunderbots/Software/scripts/mezzsh/server/mezzsh_server.sh" | ||
|
|
||
| if [ "$#" -ne 1 ]; then | ||
| echo "Usage: sudo ./mezzsh_keystore.sh \"PUBLIC_KEY_STRING\"" | ||
| exit 1 | ||
| fi | ||
|
|
||
| PUB_KEY="$1" | ||
|
|
||
| echo "--- Registering New Remote User ---" | ||
|
|
||
| # Add to authorized_keys with a command restriction | ||
| ENTRY="$PUB_KEY | ||
| command=\"$SERVER_SCRIPT\" | ||
| environment=\"SSH_CHECK_MODE=1 FORCE_CONNECT=0\"" | ||
|
|
||
| if grep -q "$PUB_KEY" "$AUTH_KEYS"; then | ||
| echo "Error: This public key is already registered." | ||
| else | ||
| echo "$ENTRY" >> "$AUTH_KEYS" | ||
| chmod 600 "$AUTH_KEYS" | ||
| echo "Key added to authorized_keys." | ||
| fi | ||
|
|
||
| echo "Registration complete." | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||
| #!/bin/bash | ||||||
|
|
||||||
| # Script to intercept all incoming ssh connections to the Mezz PC | ||||||
| # Checks for both IRL users and any connected remote users who are currently using the PC | ||||||
| # and warns the new connection accordingly | ||||||
| # If the new connection wants to force, opens the connection | ||||||
| # and also warns any IRL users if present | ||||||
|
|
||||||
| # The `who` command returns all logged in users (IRL and remote) | ||||||
| # Finds IRL users by looking for the physical monitor (tty2) | ||||||
| LOCAL_USER=$(who | grep -E '(tty2)') | ||||||
|
|
||||||
| # Finds remote users currently connected | ||||||
| # for each one, finds their username, to make identifying them easier | ||||||
| # excludes the current user, otherwise server will always seem busy | ||||||
| REMOTE_USERS_LIST=$(bash /home/thunderbots/Software/scripts/mezzsh/utils/get_connected_users.sh) | ||||||
|
|
||||||
| # if the client requested a check, return the user info from above | ||||||
| if [ "$SSH_CHECK_MODE" == "1" ]; then | ||||||
| # local user check takes priority | ||||||
| if [ ! -z "$LOCAL_USER" ]; then | ||||||
| echo "STATUS_BUSY_LOCAL" | ||||||
| exit 0 | ||||||
| elif [ ! -z "$REMOTE_USERS_LIST" ]; then | ||||||
| echo "STATUS_BUSY_REMOTE" | ||||||
| echo "List of Users: $REMOTE_USERS_LIST" | ||||||
| exit 0 | ||||||
| fi | ||||||
| exit 0 | ||||||
| fi | ||||||
|
|
||||||
| # If the user actually wanted to connect, make sure: | ||||||
| # 1. no other users (IRL or remote) are using the PC | ||||||
| # OR | ||||||
| # 2. the force flag is provided | ||||||
| if ([ ! -z "$LOCAL_USER" ] || [ ! -z "$REMOTE_USERS" ]) && [ "$FORCE_CONNECT" != "1" ]; then | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| echo "The Mezz Computer is in use! Please use the connect script to see who is currently using it." | ||||||
|
|
||||||
| # we technically hae already have an active connection at this point | ||||||
| # just no shell is provided | ||||||
| # close the connection after 1 min | ||||||
| sleep 60 | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to wait for 60s here. |
||||||
| echo "Connection timed out." | ||||||
| exit 1 | ||||||
| fi | ||||||
|
|
||||||
| # Trigger the visual warning dialog if someone is using the PC IRL | ||||||
| bash /home/thunderbots/Software/scripts/mezzsh/utils/connection_warn.sh & | ||||||
|
Comment on lines
+47
to
+48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So when connection is successful, this script will still run even if there is no local user? |
||||||
|
|
||||||
| # if we get here, start a normal shell | ||||||
| exec $SHELL | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| #!/bin/bash | ||
|
|
||
| # Sets up the Mezz PC to accept new SSH connections | ||
| # Uses Tailscale as a VPN to get around connections being blocked at the router level | ||
| # Sets up Tailscale, ssh, and links the server script to intercept new connections | ||
|
|
||
| SCRIPT_PATH="/home/thunderbots/Software/scripts/mezzsh/server/mezzsh_server.sh" | ||
| SERVER_SCRIPT="/usr/local/bin/mezzsh_server.sh" | ||
| TIMEOUT_SECONDS=3600 # 1 hour | ||
| SSHD_CONFIG="/etc/ssh/sshd_config" | ||
| TARGET_USER="thunderbots" | ||
|
|
||
| # Check for root privileges | ||
| if [[ $EUID -ne 0 ]]; then | ||
| echo "This script must be run as root (use sudo)" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "--- Initializing On-Site PC SSH Security Setup ---" | ||
|
|
||
| echo "[1/5] Installing Dependencies" | ||
| sudo apt update | ||
| sudo apt install openssh-server | ||
| sudo apt install zenity | ||
|
|
||
| # Install Tailscale | ||
| curl -fsSL https://tailscale.com/install.sh | sh | ||
| sudo tailscale up --hostname=$TARGET_USER --operator=$USER | ||
|
|
||
| echo "[2/5] Configuring SSH service to start on boot..." | ||
| systemctl enable --now ssh | ||
| systemctl start ssh | ||
|
|
||
| echo "[3/5] Modifying sshd_config for custom Environment variables, and links script to perform safety checks on new connections......" | ||
|
|
||
| # Backup original config | ||
| cp $SSHD_CONFIG "${SSHD_CONFIG}.bak" | ||
|
|
||
| # This step uses a match block to modify these ssh settings for only 1 user | ||
| # Clean up any previous global ForceCommand we might have added | ||
| sed -i '/Match User $TARGET_USER/,/AcceptEnv SSH_CHECK_MODE FORCE_CONNECT/d' $SSHD_CONFIG | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Single quote wont expand variables, is this intended? |
||
|
|
||
| # copies the script from repo to script location | ||
| cp $SCRIPT_PATH $SERVER_SCRIPT | ||
|
|
||
| # Append the Match block to the end of the file | ||
| cat <<EOF >> $SSHD_CONFIG | ||
| Match User $TARGET_USER | ||
| ForceCommand $SERVER_SCRIPT | ||
| AcceptEnv SSH_CHECK_MODE FORCE_CONNECT | ||
| EOF | ||
|
|
||
| # Script has to be executable for it to be triggered on new connections | ||
| sudo chmod +x $SERVER_SCRIPT | ||
|
|
||
| echo "[4/5] Setting 1-hour shell timeout..." | ||
|
|
||
| USER_HOME=$(eval echo "~$TARGET_USER") | ||
| TIMEOUT_BLOCK='if [ -n "$SSH_TTY" ]; then export TMOUT='$TIMEOUT_SECONDS' && readonly TMOUT; fi' | ||
|
|
||
| if [ -d "$USER_HOME" ]; then | ||
| # Remove old TMOUT lines if they exist and append new one | ||
| sed -i '/TMOUT/d' "$USER_HOME/.bashrc" | ||
| echo $TIMEOUT_BLOCK >> "$USER_HOME/.bashrc" | ||
| chown $TARGET_USER:$TARGET_USER "$USER_HOME/.bashrc" | ||
| echo "Success: SSH timeout set for $TARGET_USER." | ||
| else | ||
| echo "ERROR: Home directory for $TARGET_USER not found. Timeout not set." | ||
| fi | ||
|
|
||
| echo "[5/5] Restarting SSH service to apply changes..." | ||
| systemctl restart ssh | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| #!/bin/bash | ||
|
|
||
| # Checks if Tailscale is installed, the daemon is up and running, and we are logged in (connected) to the VPN | ||
|
|
||
| # Check if tailscale is installed | ||
| if ! command -v tailscale &> /dev/null; then | ||
| echo "Tailscale not found. Installing..." | ||
| curl -fsSL https://tailscale.com/install.sh | sh | ||
| fi | ||
|
|
||
| # Check if the Tailscale daemon (tailscaled) is even running | ||
| if ! systemctl is-active --quiet tailscaled; then | ||
| echo "tailscaled is not running. Starting service..." | ||
| sudo systemctl start tailscaled | ||
| fi | ||
|
|
||
| # Check if the node is authenticated and connected to the tailnet | ||
| # 'tailscale status' returns 0 if connected, non-zero otherwise | ||
| if ! tailscale status >/dev/null 2>&1; then | ||
| echo "Tailscale is down or unauthenticated. Running 'up'..." | ||
|
|
||
| read -p "Enter the auth key. Please contact a software lead if you don't have one: " AUTH_KEY | ||
|
|
||
| sudo tailscale up --auth-key=$AUTH_KEY | ||
| fi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| #!/bin/bash | ||
|
|
||
| # Script to trigger a dialog on the Mezz PC monitor that someone | ||
| # has just connected remotely | ||
|
|
||
| # find the username of the person logged in physically | ||
| # we look for someone attached to the main display (:0) | ||
| LOCAL_USER=$(who | grep -m 1 "(tty2)" | awk '{print $1}') | ||
|
|
||
| # if no one is logged in locally, just exit | ||
| if [ -z "$LOCAL_USER" ]; then | ||
| exit 0 | ||
| fi | ||
|
|
||
| # get the User ID of the local user | ||
| LOCAL_UID=$(id -u "$LOCAL_USER") | ||
|
|
||
| # Trigger the dialog | ||
| # these environment variables must be set to trigger the dialog | ||
| # from a background script (i.e our SSH server script) | ||
| # specifically on our wayland setup | ||
| sudo -u "$LOCAL_USER" DISPLAY=tty2 \ | ||
| DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$LOCAL_UID/bus \ | ||
| XDG_RUNTIME_DIR="/run/user/$LOCAL_UID" \ | ||
| WAYLAND_DISPLAY="wayland-0" \ | ||
| GDK_BACKEND="wayland" \ | ||
| zenity --warning --title="Remote Connection" \ | ||
| --text="A new has just connected remotely (via SSH)." --timeout=10 & |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| #!/bin/bash | ||
|
|
||
| # Gets the names of all currently connected SSH users | ||
| # Each user has their name attached to their SSH public key in authorized_keys | ||
| # So we find the key each current connection used to connect, and then the corresponding name | ||
|
|
||
| # Get keys and comments from authorized_keys | ||
| KEYS_FILE="/home/thunderbots/.ssh/authorized_keys" | ||
| declare -A key_to_names | ||
|
|
||
| while read -r line; do | ||
| # Skip the line if it doesn't start with "ssh" | ||
| # to skip over the extra flags attached to each key on separate lines | ||
| [[ ! $line =~ ^ssh ]] && continue | ||
|
|
||
| # the line consists of <ssh key> <username> | ||
|
|
||
| # Get fingerprint for the ssh key | ||
| fingerprint=$(echo "$line" | ssh-keygen -l -f - | awk '{print $2}') | ||
|
|
||
| # get the username part of the line | ||
| username=$(echo "$line" | awk '{print $NF}') | ||
|
|
||
| # map fingerprint to username | ||
| key_to_names["$fingerprint"]="$username" | ||
| done < "$KEYS_FILE" | ||
|
|
||
| # Find currently active SSH PIDs and match them to log entries | ||
| pgrep -u "root" sshd | tail -n +2 | while read -r pid; do | ||
| # Look for the 'Accepted publickey' log entry for this specific PID | ||
| # there should be a log entry with the text "sshd[<pid>] | ||
| # log entry contains the fingerprint within the text "SHA256:<fingerprint> | ||
| fingerprint_match=$(grep "sshd\[$pid\]" /var/log/auth.log | grep "Accepted publickey" | grep -oE "SHA256:[^ ]+") | ||
|
|
||
| if [ -n "$fingerprint_match" ]; then | ||
| username=${key_to_names[$fingerprint_match]} | ||
| echo "${username}" | ||
| fi | ||
| done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closing quote?