Troubleshooting SSH %n Token Expansion: A Comprehensive Guide

The ssh_config file in OpenSSH provides powerful configuration options, including the use of percent_expand tokens for dynamic path generation. Among these tokens, %n is intended to represent the hostname as provided on the command line. However, users often encounter issues where the %n token fails to expand correctly, leading to errors like “percent_expand: unknown key %n”. This article provides a detailed exploration of the problem, its causes, and effective solutions. We aim to help you configure your SSH client properly, enabling seamless key management for multiple repositories, particularly in scenarios involving GitHub deploy tokens.

Understanding the SSH Configuration and %n Token

The ssh_config file (~/.ssh/config for user-specific settings, /etc/ssh/ssh_config for system-wide configurations) allows you to define settings for specific hosts or groups of hosts. This is particularly useful when managing multiple SSH connections with different configurations.

The percent_expand tokens enable dynamic substitution of values within configuration options. In the context of IdentityFile, the %n token should represent the hostname specified in the SSH command. For example, if you run ssh github-MyPackage, the %n token in IdentityFile %d/.ssh/github/%n should expand to github-MyPackage.

Common Use Case: GitHub Deploy Tokens

GitHub allows using SSH tokens as deploy tokens, granting specific repositories access without requiring full user credentials. A common challenge is managing multiple deploy tokens for different repositories. One might attempt to use globbing and the %n token to create a generic profile:

host github-*
    Hostname github.com
    User git
    IdentityFile %d/.ssh/github/%n
    IdentitiesOnly yes

The intention is that if you have a key pair named github-MyPackage in the ~/.ssh/github/ directory, the configuration will automatically use that key when you run git clone github-MyPackage:/myorganization/MyPackage.git or ssh github-MyPackage.

Diagnosing the “percent_expand: unknown key %n” Error

The “percent_expand: unknown key %n” error indicates that the SSH client is unable to resolve the %n token during configuration parsing. This can occur for several reasons:

1. SSH Client Version Compatibility

Older versions of OpenSSH may not fully support the %n token or may have bugs in its implementation. It is critical to ensure that you are running a reasonably up-to-date version of OpenSSH.

Solution:

  1. Check your OpenSSH version: Run ssh -V in your terminal.
  2. Upgrade OpenSSH: If your version is outdated, update it using your operating system’s package manager (e.g., apt update && apt upgrade openssh-client on Debian/Ubuntu, yum update openssh-clients on CentOS/RHEL, brew upgrade openssh on macOS with Homebrew).

2. Configuration File Syntax Errors

Even a small syntax error in your ssh_config file can prevent the entire file from being parsed correctly, leading to unexpected behavior.

Solution:

  1. Carefully review your ssh_config file: Look for typos, missing spaces, or incorrect indentation. The ssh_config file is sensitive to formatting.
  2. Use ssh -v for debugging: The -v flag (verbose mode) provides detailed output during SSH connection attempts, including information about configuration file parsing. This can help identify the line where the error occurs. For even more detail, use -vv or -vvv.

3. Incorrect Hostname Matching

The host directive in ssh_config specifies which hosts the following configuration options apply to. If the hostname you’re using in the SSH command does not match the host pattern, the configuration block will not be applied, and the %n token will not be expanded.

Solution:

  1. Verify the host pattern: Ensure that the host pattern in your ssh_config file matches the hostname you’re using. In the example host github-*, the hostname must start with github-.
  2. Use wildcards appropriately: The * wildcard matches zero or more characters. If you want to match any hostname, use host *. If you want to match a specific set of hostnames, list them separated by commas (e.g., host github-MyPackage, github-AnotherPackage).

4. Contextual Limitations of %n

The %n token’s behavior can vary depending on the context in which it’s used. While it generally represents the hostname provided on the command line, there might be specific situations where it is not correctly populated.

Solution:

  1. Test with a simple ssh command: Run ssh -v github-MyPackage to see how the %n token is expanded in a basic SSH connection. This can help isolate whether the issue is specific to git clone.
  2. Experiment with alternative tokens: If %n consistently fails, consider whether other tokens might be suitable. For example, if you can derive the desired filename from another piece of information available in the environment.

5. Interaction with Git and SSH Wrappers

Git often uses SSH internally for authentication and data transfer. However, Git might introduce its own layer of configuration or environment variables that interfere with SSH’s percent_expand mechanism.

Solution:

  1. Check Git’s sshCommand configuration: Git allows you to specify a custom SSH command using the sshCommand configuration option (git config --global core.sshCommand). If this is set, it might be overriding the standard SSH behavior. Ensure that sshCommand is either unset or correctly configured to pass the hostname to the underlying SSH client.
  2. Examine environment variables: Environment variables like GIT_SSH or GIT_SSH_COMMAND can also influence Git’s SSH behavior. Unset these variables to ensure that Git uses the default SSH client.
  3. Use ssh -Tvvv github-MyPackage for Git-related debugging: The -T option disables pseudo-terminal allocation, which can sometimes interfere with SSH connections used by Git. The verbose output (-vvv) will help pinpoint the source of the problem.

Advanced Configuration and Workarounds

If the above solutions do not resolve the issue, consider the following advanced configuration options and workarounds:

1. Explicit Host Entries

Instead of relying on globbing and the %n token, you can create explicit host entries for each repository. This is more verbose but can be more reliable.

host github-MyPackage
    Hostname github.com
    User git
    IdentityFile %d/.ssh/github/github-MyPackage
    IdentitiesOnly yes

host github-AnotherPackage
    Hostname github.com
    User git
    IdentityFile %d/.ssh/github/github-AnotherPackage
    IdentitiesOnly yes

2. Using a Script to Generate the IdentityFile Path

You can create a script that dynamically generates the IdentityFile path based on the hostname and use the ProxyCommand directive to execute the script.

  1. Create a script (e.g., ~/bin/ssh-key-selector.sh):
#!/bin/bash
HOST="$1"
KEY_PATH="$HOME/.ssh/github/$HOST"

if [ -f "$KEY_PATH" ]; then
  echo "IdentityFile $KEY_PATH"
else
  echo "IdentityFile $HOME/.ssh/id_rsa" # Default key if no match
fi
  1. Make the script executable: chmod +x ~/bin/ssh-key-selector.sh

  2. Update your ssh_config file:

host github-*
    Hostname github.com
    User git
    ProxyCommand ~/bin/ssh-key-selector.sh %n
    IdentitiesOnly yes
    IdentityFile /dev/null # Prevents ssh from trying other keys first

This setup uses ProxyCommand to execute the ssh-key-selector.sh script, passing the hostname (%n) as an argument. The script then checks if a key file exists for that hostname and outputs the corresponding IdentityFile directive. Setting IdentityFile /dev/null prevents SSH from trying other keys before the key specified by the script.

3. Leveraging SSH Certificates

SSH certificates provide a more robust and scalable solution for managing SSH keys. Instead of distributing individual key pairs, you can create a certificate authority (CA) and sign user keys with the CA. The SSH client can then be configured to trust the CA, allowing it to authenticate any key signed by the CA.

This approach eliminates the need to manage individual IdentityFile entries for each repository.

Specific Considerations for GitHub Deploy Tokens

When using GitHub deploy tokens, it’s essential to ensure that the key associated with the token has the correct permissions. The deploy token should be granted read-only or read-write access to the specific repository.

1. Key Permissions on GitHub

Verify that the public key associated with your deploy token is added to the correct repository with the necessary permissions. You can manage deploy tokens in the repository’s settings under “Deploy keys.”

2. Testing the Connection

Use the ssh -Tvvv github-MyPackage command to test the SSH connection. This will provide detailed output about the authentication process, including whether the correct key is being used and whether the GitHub server is accepting the key.

3. Addressing Potential GitHub Rate Limiting

In rare cases, GitHub might impose rate limits on SSH connections. If you encounter intermittent connection issues, consider implementing retry logic or increasing the connection timeout in your SSH configuration.

Conclusion

Troubleshooting SSH %n token expansion issues requires a systematic approach, starting with verifying SSH client version, syntax errors in ssh_config, and hostname matching. Advanced techniques such as explicit host entries, scripting, and SSH certificates offer alternative solutions for complex key management scenarios. By carefully examining the configuration, understanding the limitations of %n, and considering the specific context of Git and GitHub deploy tokens, you can resolve the “percent_expand: unknown key %n” error and achieve seamless SSH key management.