Skip to content

Instantly share code, notes, and snippets.

@emilf
Created March 31, 2025 23:23
Show Gist options
  • Save emilf/e3089d9da420010806cabace30e6b255 to your computer and use it in GitHub Desktop.
Save emilf/e3089d9da420010806cabace30e6b255 to your computer and use it in GitHub Desktop.
Updated install install-linux.sh for opkssh for use on Arch Linux servers
#!/bin/bash
set -e # Exit if any command fails
# Define variables
INSTALL_DIR="/usr/bin"
BINARY_NAME="opkssh"
GITHUB_REPO="openpubkey/opkssh"
# Define the default OpenID Providers
PROVIDER_GOOGLE="https://accounts.google.com 206584157355-7cbe4s640tvm7naoludob4ut1emii7sf.apps.googleusercontent.com 24h"
PROVIDER_MICROSOFT="https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0 096ce0a3-5e72-4da8-9c86-12924b294a01 24h"
PROVIDER_GITLAB="https://gitlab.com 8d8b7024572c7fd501f64374dec6bba37096783dfcd792b3988104be08cb6923 24h"
# AuthorizedKeysCommand user
AUTH_CMD_USER="opksshuser"
AUTH_CMD_GROUP="opksshuser"
SUDOERS_PATH="/etc/sudoers.d/opkssh"
if [ "$EUID" -ne 0 ]; then
echo "Error: This script must be run as root."
echo "sudo $0"
exit 1
fi
HOME_POLICY=true
RESTART_SSH=true
LOCAL_INSTALL_FILE=""
INSTALL_VERSION="latest"
for arg in "$@"; do
if [[ "$arg" == "--no-home-policy" ]]; then
HOME_POLICY=false
elif [ "$arg" == "--no-sshd-restart" ]; then
RESTART_SSH=false
elif [[ "$arg" == --install-from=* ]]; then
LOCAL_INSTALL_FILE="${arg#*=}"
elif [[ "$arg" == --install-version=* ]]; then
INSTALL_VERSION="${arg#*=}"
fi
done
# Display help message
if [[ "$1" == "--help" ]]; then
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --no-home-policy Disables configuration that allows opkssh see policy files in user's home directory (/home/<username>/auth_id). Greatly simplifies install, try this if you are having install failures."
echo " --no-sshd-restart Do not restart SSH after installation"
echo " --install-from=FILEPATH Install using a local file"
echo " --install-version=VER Install a specific version from GitHub"
echo " --help Display this help message"
exit 0
fi
# Checks if the group and user used by the AuthorizedKeysCommand exists if not creates it
if ! getent group "$AUTH_CMD_GROUP" >/dev/null; then
groupadd --system "$AUTH_CMD_GROUP"
echo "Created group: $AUTH_CMD_GROUP"
fi
# If the AuthorizedKeysCommand user does not exist, create it and add it to the group
if ! getent passwd "$AUTH_CMD_USER" >/dev/null; then
useradd -r -M -s /sbin/nologin -g "$AUTH_CMD_GROUP" "$AUTH_CMD_USER"
echo "Created user: $AUTH_CMD_USER with group: $AUTH_CMD_GROUP"
else
# If the AuthorizedKeysCommand user exist, ensure it is added to the group
usermod -aG "$AUTH_CMD_GROUP" "$AUTH_CMD_USER"
echo "Added $AUTH_CMD_USER to group: $AUTH_CMD_GROUP"
fi
chown root:${AUTH_CMD_GROUP} "$INSTALL_DIR/$BINARY_NAME"
# Checks if SELinux is enabled and if so, ensures the context is set correctly
if command -v getenforce >/dev/null 2>&1; then
if [ "$(getenforce)" != "Disabled" ]; then
echo "SELinux detected. Configuring SELinux for opkssh"
echo " Restoring context for $INSTALL_DIR/$BINARY_NAME..."
restorecon "$INSTALL_DIR/$BINARY_NAME"
# Create temporary files for the compiled module and package
TE_TMP="/tmp/opkssh.te"
MOD_TMP="/tmp/opkssh.mod" # SELinux requires that modules have the same file name as the module name
PP_TMP="/tmp/opkssh.pp"
if [ "$HOME_POLICY" = true ]; then
echo " Using SELinux module that permits home policy"
# Pipe the TE directives into checkmodule via /dev/stdin
cat << 'EOF' > "$TE_TMP"
module opkssh 1.0;
require {
type sshd_t;
type var_log_t;
type ssh_exec_t;
type http_port_t;
type sudo_exec_t;
class file { append execute execute_no_trans open read map };
class tcp_socket name_connect;
}
# We need to allow the AuthorizedKeysCommand opkssh process launched by sshd to:
# 1. Make TCP connections to ports labeled http_port_t. This is so opkssh can download the public keys of the OpenID providers.
allow sshd_t http_port_t:tcp_socket name_connect;
# 2. Needed to allow opkssh to call `ssh -V` to determine if the version is supported by opkssh
allow sshd_t ssh_exec_t:file { execute execute_no_trans open read map };
# 3. Needed to allow opkssh to call `sudo opkssh readhome` to read the policy file in the user's home directory
allow sshd_t sudo_exec_t:file { execute execute_no_trans open read map };
# 4. Needed to allow opkssh to write to its log file
allow sshd_t var_log_t:file { open append };
EOF
else
echo " Using SELinux module does not permits home policy (--no-home-policy option supplied)"
# Redefine the tmp file names since SELinux modules must have the same name as the file
TE_TMP="/tmp/opkssh-no-home.te"
MOD_TMP="/tmp/opkssh-no-home.mod" # SELinux requires that modules have the same file name as the module name
PP_TMP="/tmp/opkssh-no-home.pp"
# Pipe the TE directives into checkmodule via /dev/stdin
cat << 'EOF' > "$TE_TMP"
module opkssh-no-home 1.0;
require {
type sshd_t;
type var_log_t;
type ssh_exec_t;
type http_port_t;
class file { append execute execute_no_trans open read map };
class tcp_socket name_connect;
}
# We need to allow the AuthorizedKeysCommand opkssh process launched by sshd to:
# 1. Make TCP connections to ports labeled http_port_t. This is so opkssh can download the public keys of the OpenID providers.
allow sshd_t http_port_t:tcp_socket name_connect;
# 2. Needed to allow opkssh to call `ssh -V` to determine if the version is supported by opkssh
allow sshd_t ssh_exec_t:file { execute execute_no_trans open read map };
# 3. Needed to allow opkssh to write to its log file
allow sshd_t var_log_t:file { open append };
EOF
fi
echo " Compiling SELinux module..."
checkmodule -M -m -o "$MOD_TMP" "$TE_TMP"
echo " Packaging module..."
semodule_package -o "$PP_TMP" -m "$MOD_TMP"
echo " Installing module..."
semodule -i "$PP_TMP"
rm -f "$TE_TMP" "$MOD_TMP" "$PP_TMP"
echo "SELinux module installed successfully!"
fi
fi
# Verify installation
if command -v $INSTALL_DIR/$BINARY_NAME &> /dev/null; then
# Setup configuration
echo "Configuring opkssh:"
mkdir -p /etc/opk
touch /etc/opk/auth_id
chown root:${AUTH_CMD_GROUP} /etc/opk/auth_id
chmod 640 /etc/opk/auth_id
touch /etc/opk/providers
chown root:${AUTH_CMD_GROUP} /etc/opk/providers
chmod 640 /etc/opk/providers
if [ -s /etc/opk/providers ]; then
echo " The providers policy file (/etc/opk/providers) is not empty. Keeping existing values"
else
echo "$PROVIDER_GOOGLE" >> /etc/opk/providers
echo "$PROVIDER_MICROSOFT" >> /etc/opk/providers
echo "$PROVIDER_GITLAB" >> /etc/opk/providers
fi
# Change from upstream, to override systemd querying userdb
touch /etc/ssh/sshd_config.d/19-opkssh-authkeyscommand.conf
chown root /etc/ssh/sshd_config.d/19-opkssh-authkeyscommand.conf
chmod 644 /etc/ssh/sshd_config.d/19-opkssh-authkeyscommand.conf
echo "AuthorizedKeysCommand /usr/bin/opkssh verify %u %k %t" >> /etc/ssh/sshd_config.d/19-opkssh-authkeyscommand.conf
echo "AuthorizedKeysCommandUser ${AUTH_CMD_USER}" >> /etc/ssh/sshd_config.d/19-opkssh-authkeyscommand.conf
if [ "$RESTART_SSH" = true ]; then
systemctl restart sshd
else
echo " --no-sshd-restart option supplied, skipping SSH restart."
fi
if [ "$HOME_POLICY" = true ]; then
if [ ! -f "$SUDOERS_PATH" ]; then
echo " Creating sudoers file at $SUDOERS_PATH..."
touch "$SUDOERS_PATH"
chmod 440 "$SUDOERS_PATH"
fi
SUDOERS_RULE_READ_HOME="$AUTH_CMD_USER ALL=(ALL) NOPASSWD: /usr/bin/opkssh readhome *"
if ! grep -qxF "$SUDOERS_RULE_READ_HOME" "$SUDOERS_PATH"; then
echo " Adding sudoers rule for $AUTH_CMD_USER..."
echo "# This allows opkssh to call opkssh readhome <username> to read the user's policy file in /home/<username>/auth_id" >> "$SUDOERS_PATH"
echo "$SUDOERS_RULE_READ_HOME" >> "$SUDOERS_PATH"
fi
else
echo " Skipping sudoers configuration as it is only needed for home policy (--no-home-policy option supplied)"
fi
touch /var/log/opkssh.log
chown root:${AUTH_CMD_GROUP} /var/log/opkssh.log
chmod 660 /var/log/opkssh.log
VERSION_INSTALLED=$($INSTALL_DIR/$BINARY_NAME --version)
INSTALLED_ON=$(date)
# Log the installation details to /var/log/opkssh.log to help with debugging
echo "Successfully installed opkssh (INSTALLED_ON: $INSTALLED_ON, VERSION_INSTALLED: $VERSION_INSTALLED, INSTALL_VERSION: $INSTALL_VERSION, LOCAL_INSTALL_FILE: $LOCAL_INSTALL_FILE, HOME_POLICY: $HOME_POLICY, RESTART_SSH: $RESTART_SSH)" >> /var/log/opkssh.log
echo "Installation successful! Run '$BINARY_NAME' to use it."
else
echo "Installation failed."
exit 1
fi

This is a guide to install opkssh on Arch Linux and as valid as of 31. march 2025

Installing

I use yay to install AUR packages, but installing packages using the AUR is beyond the scope of this guide.

You need to install one of the following AUR packages:

  • opkssh
  • opkssh-bin
  • opkssh-git

Next run the script in this gist as root: sudo ./install-linux.sh

The script is modified from the one in the opkssh repo to bypass distro checks, remove the parts that download and install the opkssh binary from github and install it and changes the method used to reconfigure OpenSSHd to work around a file that overrides the configuration the upstream scripts adds.
I've left the SELINUX part of the script untouched, as I don't use SELINUX. I cannot help if that doesn't work. It's unchanged from upstream.

When you've run the script, run sudo opkssh add <USER> <EMAIL> <PROVIDER> on the server to add a user who is allowed to log in.

  • <USER> is the local user
  • <EMAIL> is the email address they use at the authentication provider
  • <PROVIDER> is google, microsoft or gitlab

On the client, you need to do opkssh login to log in to your authentication providers.

Troubleshooting

If you have issues with this, I recommend looking at the opkssh README or their manual installation instructions for servers

If you still have problems, make sure the effective sshd configuration has the correct authorizedkeyscommand and authorizedkeyscommanduser values by running sudo /usr/bin/sshd -G | grep authorizedkeyscommand. This should return two lines that both reference opkssh

As a last resort, after looking at all logs and stuff, you can run the ssh server with sudo /usr/bin/sshd -ddd.
This will start a single process of the server, wait for a single connection and allow you to read through the output via stdout.
To do this you need to either shut down sshd.service or run this debug instance of sshd on another port.

Don't shut down sshd on a server you can only access via ssh!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment