Skip to content

YubiKey OpenPGP Remote sudo with SSH Agent Forwarding

This note records a pattern I like for remote Linux administration:

  • normal SSH login stays on the usual SSH key or password mechanism;
  • sudo authentication uses a hardware-backed OpenPGP authentication key on a local YubiKey;
  • the remote host validates the forwarded SSH agent instead of storing the private key;
  • password-based sudo remains available as a fallback.

It is not a replacement for all local sudo prompts on your workstation. It is a targeted remote-maintenance pattern: after you already logged in to a trusted server, touching the local YubiKey can approve sudo.

Why separate SSH login and sudo

SSH login and sudo answer different questions.

SSH login asks:

Is this user allowed to open a session on the server?

sudo asks:

Is this already logged-in user allowed to elevate privileges now?

Keeping those two layers separate has a useful operational property. You can keep your existing SSH login workflow unchanged while adding a stronger second check for administrative actions.

For example:

  • SSH login can continue using an existing SSH agent, password manager, certificate, or normal key.
  • sudo can require a YubiKey-backed signing operation through a forwarded gpg-agent.
  • if the YubiKey is unavailable, the original sudo password fallback still works.

Architecture

The key idea is to expose a YubiKey OpenPGP authentication subkey through gpg-agent's SSH support.

local workstation
  YubiKey OpenPGP auth subkey
  gpg-agent SSH socket
        |
        | SSH agent forwarding, enabled only for this session
        v
remote Linux server
  PAM sudo rule
  validates whether forwarded agent can sign for an allowed public key
        |
        v
  if success: sudo authentication passes
  if failure: fall back to normal password auth

The private key stays on the YubiKey. The remote server only stores an allowed public key, or a reference to it.

Local setup

Install GnuPG and configure gpg-agent with SSH support.

On macOS with Homebrew:

brew install gnupg pinentry-mac yubikey-manager

Configure ~/.gnupg/gpg-agent.conf:

enable-ssh-support
pinentry-program /opt/homebrew/bin/pinentry-mac

Restart the agent:

gpgconf --kill gpg-agent
gpgconf --launch gpg-agent

Find the SSH socket:

gpgconf --list-dirs agent-ssh-socket

With the socket active, check what SSH keys the agent exposes:

SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" ssh-add -l -E sha256

Export the public key that will be allowed for sudo:

SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" ssh-add -L > yubikey-sudo-authorized_keys

In a real setup, keep only the exact public key you want to authorize for sudo. Do not blindly authorize every key an agent happens to expose.

Remote route 1: pam_ssh_agent_auth

Some distributions package pam_ssh_agent_auth. When it is available, it is the cleanest path.

Install it:

sudo dnf install pam_ssh_agent_auth

Place the allowed public key on the server:

sudo install -o root -g root -m 0600 yubikey-sudo-authorized_keys \
  /etc/security/authorized_keys_sudo_yubikey

Allow sudo to keep the forwarded agent socket:

sudo tee /etc/sudoers.d/10-yubikey-agent-auth >/dev/null <<'EOF'
Defaults env_keep += "SSH_AUTH_SOCK"
EOF
sudo chmod 0440 /etc/sudoers.d/10-yubikey-agent-auth
sudo visudo -cf /etc/sudoers.d/10-yubikey-agent-auth

Then add a sufficient PAM line before the regular password-based auth rule in /etc/pam.d/sudo:

auth       sufficient  pam_ssh_agent_auth.so file=/etc/security/authorized_keys_sudo_yubikey
auth       include     system-auth

Using sufficient is important. If the YubiKey path succeeds, sudo authentication passes. If it fails, PAM continues to the existing password rule.

Remote route 2: pam_exec and ssh-add -T

On some newer or smaller systems, pam_ssh_agent_auth might not be packaged. A practical fallback is to use pam_exec and ssh-add -T.

ssh-add -T <public-key-file> asks the SSH agent to sign test data and verifies the signature against the public keys in the file. If the forwarded agent can use the YubiKey-backed key, the test succeeds.

Create a small root-owned checker:

sudo tee /usr/local/sbin/yubikey-sudo-check >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

key_file="/etc/security/authorized_keys_sudo_yubikey"

if [[ -z "${SSH_AUTH_SOCK:-}" || ! -S "${SSH_AUTH_SOCK}" ]]; then
  exit 1
fi

if [[ ! -r "$key_file" ]]; then
  exit 1
fi

exec /usr/bin/ssh-add -T "$key_file"
EOF
sudo chown root:root /usr/local/sbin/yubikey-sudo-check
sudo chmod 0755 /usr/local/sbin/yubikey-sudo-check

Install the public key and sudoers environment rule just as in the first route:

sudo install -o root -g root -m 0600 yubikey-sudo-authorized_keys \
  /etc/security/authorized_keys_sudo_yubikey

sudo tee /etc/sudoers.d/10-yubikey-agent-auth >/dev/null <<'EOF'
Defaults env_keep += "SSH_AUTH_SOCK"
EOF
sudo chmod 0440 /etc/sudoers.d/10-yubikey-agent-auth
sudo visudo -cf /etc/sudoers.d/10-yubikey-agent-auth

Add this line before the regular password-based sudo rule:

auth       sufficient  pam_exec.so quiet /usr/local/sbin/yubikey-sudo-check
auth       include     system-auth

This is not as purpose-built as pam_ssh_agent_auth, but it has the same operational shape: use the forwarded agent if it works, otherwise continue to password authentication.

SSH command pattern

Do not enable global agent forwarding for every host.

Use the YubiKey agent socket only for the trusted remote sudo session that needs it:

ssh -tt \
  -o ForwardAgent="$(gpgconf --list-dirs agent-ssh-socket)" \
  admin@example-host \
  'sudo -k -v'

For ordinary SSH login, keep your normal SSH configuration. For example, if you already use a password manager or another SSH agent for login, leave it alone.

The two agents have different purposes:

  • login agent: opens the SSH session;
  • YubiKey gpg-agent: approves remote sudo.

Validation

Validate the hardware-backed sudo path:

ssh -tt \
  -o ForwardAgent="$(gpgconf --list-dirs agent-ssh-socket)" \
  admin@example-host \
  'sudo -k -v && echo YUBIKEY_SUDO_OK || echo YUBIKEY_SUDO_FAILED'

Validate password fallback:

ssh -tt \
  -o ForwardAgent=no \
  admin@example-host \
  'sudo -k -v && echo PASSWORD_FALLBACK_OK || echo PASSWORD_FALLBACK_FAILED'

The second test should ask for the normal sudo password. If it does not, stop and check whether you accidentally created passwordless sudo.

Security notes

Agent forwarding is powerful. A trusted remote root process can ask the forwarded agent to perform signing operations while the session is open. The private key is still not copied to the server, but the forwarded socket is a capability.

My practical rules are:

  • only forward the YubiKey agent to hosts you trust;
  • forward it per command or per host, not globally;
  • keep password fallback until you have a tested break-glass path;
  • use a dedicated allowed public key file for sudo;
  • keep the PAM line sufficient during the first rollout;
  • keep an open session while editing PAM or sudoers;
  • validate both hardware authentication and password fallback before closing the maintenance window.

Rollback

If something behaves unexpectedly:

  1. restore the previous /etc/pam.d/sudo backup;
  2. remove or disable the sudoers environment fragment;
  3. keep the normal password sudo path working;
  4. open a new SSH session and test before closing the old one.

PAM mistakes can lock you out of privilege escalation. Treat this like any other production authentication change: small steps, one host at a time, and a boring rollback path.

References