ssh-auth-cmd is a Rust application designed to chain multiple SSH AuthorizedKeysCommand configurations, since OpenSSH only supports configuring a single one. The new version supports individual configuration files, user switching, comprehensive OpenSSH placeholder support, and automatic installation.
- Build the application:
cargo build --release- Install the binary:
sudo cp target/release/ssh-auth-cmd /usr/local/bin/
sudo chmod +x /usr/local/bin/ssh-auth-cmd- Install using the built-in installer (recommended):
sudo /usr/local/bin/ssh-auth-cmd installOr with a specific user:
sudo /usr/local/bin/ssh-auth-cmd install --user nobodyCommands are now configured in individual TOML files in /etc/ssh/auth_cmd.d/:
/etc/ssh/auth_cmd.d/
├── 01-local-keys.toml
├── 02-ldap-keys.toml
├── 03-database-keys.toml
└── 99-emergency-keys.toml
Files are processed in alphabetical order, so you can use prefixes to control execution order.
Each configuration file contains a single command definition:
name = "local_keys"
command = "cat"
args = ["/home/%u/.ssh/authorized_keys"]
enabled = true
timeout = 30
user = "nobody"
readonly = falsename = "ldap_keys"
command = "/usr/local/bin/ldap-ssh-keys"
args = ["--user", "%u", "--hostname", "%h", "--connection", "%C"]
enabled = true
timeout = 60
user = "ldap-user"
readonly = falsename = "audit_logger"
command = "/usr/local/bin/ssh-audit-log"
args = ["--user", "%u", "--key-type", "%t", "--fingerprint", "%f"]
enabled = true
timeout = 10
user = "audit"
readonly = true # Don't include output in authorized keysname: A descriptive name for the commandcommand: The command/script to execute
args: Array of arguments to pass to the commandenabled: Whether this command is enabled (default: true)timeout: Timeout in seconds for command execution (default: 30)user: UNIX user to run the command as (only when ssh-auth-cmd runs as root)readonly: If true, discard stdout output (useful for logging/auditing)
The following OpenSSH placeholders are supported in the args array:
%C: Connection specification (user, client IP, client port, server IP, server port)%D: Routing domain%f: Key fingerprint%h: Hostname- %k`: Key being offered for authentication
%t: Key type%U: Original username (before any transformations)%u: Username being authenticated%%: Literal%character
name = "comprehensive_auth"
command = "/usr/local/bin/auth-checker"
args = [
"--user", "%u",
"--original-user", "%U",
"--hostname", "%h",
"--connection", "%C",
"--key-type", "%t",
"--fingerprint", "%f",
"--key", "%k",
"--domain", "%D",
"--literal-percent", "%%"
]When using the install command, ssh-auth-cmd configures OpenSSH as:
AuthorizedKeysCommand /usr/local/bin/ssh-auth-cmd key-cmd -c %C -D %D -f %f -h %h -k %k -t %t -U %U -u %u
AuthorizedKeysCommandUser root
This exhaustively uses all OpenSSH substitution variables currently defined.
When ssh-auth-cmd runs as root (recommended), you can specify different users for each command:
- If
useris specified in a command config, that command runs as the specified user - If
useris not specified, the command runs as the same user asssh-auth-cmd - If
ssh-auth-cmdis not running as root, theuserfield is ignored
This allows for principle of least privilege - each command can run with only the permissions it needs.
ssh-auth-cmd key-cmd -c %C -D %D -f %f -h %h -k %k -t %t -U %U -u %uThis is the mode OpenSSH calls. It processes all enabled configurations and outputs authorized keys.
sudo ssh-auth-cmd config-checkValidates:
- Configuration directory and file permissions
- Configuration file syntax
- Placeholder substitution validity
- Command binary permissions
- That all command binaries are only writable by root
# Install with default settings (AuthorizedKeysCommandUser root)
sudo ssh-auth-cmd install
# Install with specific user
sudo ssh-auth-cmd install --user nobody
# Install with custom sshd_config location
sudo ssh-auth-cmd install --config /etc/ssh/sshd_config.customThe install command:
- Migrates existing
AuthorizedKeysCommandto a config file in/etc/ssh/auth_cmd.d/ - Comments out the old configuration
- Adds the new ssh-auth-cmd configuration
- Preserves existing
AuthorizedKeysCommandUsersettings when migrating - Will not overwrite existing files in
/etc/ssh/auth_cmd.d/
All configuration files and the configuration directory must have secure permissions:
# Set proper permissions
sudo chown -R root:root /etc/ssh/auth_cmd.d/
sudo chmod 755 /etc/ssh/auth_cmd.d/
sudo chmod 600 /etc/ssh/auth_cmd.d/*.tomlThe config-check command verifies these permissions automatically.
- All configured command binaries must be owned by root and not writable by others
- Commands run with the privileges of the specified
user(or the user running ssh-auth-cmd) - Use the
readonlyflag for commands that should only log/audit without providing keys
Different commands can run as different users for security isolation:
- LDAP queries might run as a dedicated
ldap-authuser - Database queries might run as a
db-authuser - Local file access might run as
nobody - Audit logging might run as an
audituser
/etc/ssh/auth_cmd.d/01-local.toml:
name = "local_keys"
command = "cat"
args = ["/home/%u/.ssh/authorized_keys"]
enabled = true
timeout = 30
user = "nobody"/etc/ssh/auth_cmd.d/02-ldap.toml:
name = "ldap_lookup"
command = "/usr/local/bin/ldap-ssh-keys"
args = ["--user", "%u", "--hostname", "%h"]
enabled = true
timeout = 60
user = "ldap-auth"/etc/ssh/auth_cmd.d/01-audit.toml:
name = "connection_audit"
command = "/usr/local/bin/log-ssh-attempt"
args = [
"--user", "%u",
"--original-user", "%U",
"--connection", "%C",
"--hostname", "%h",
"--key-fingerprint", "%f",
"--key-type", "%t"
]
enabled = true
timeout = 10
user = "audit"
readonly = true # Just for logging, don't provide keys/etc/ssh/auth_cmd.d/02-corporate-keys.toml:
name = "corporate_ldap"
command = "/usr/local/bin/corporate-auth"
args = ["--user", "%u", "--domain", "corp.example.com"]
enabled = true
timeout = 60
user = "corp-auth"/etc/ssh/auth_cmd.d/03-emergency.toml:
name = "emergency_access"
command = "/usr/local/bin/emergency-keys"
args = ["--user", "%u", "--validate-emergency"]
enabled = true
timeout = 30
user = "emergency-auth"/etc/ssh/auth_cmd.d/01-local.toml:
name = "local_keys"
command = "cat"
args = ["/home/%u/.ssh/authorized_keys"]
enabled = true
timeout = 30/etc/ssh/auth_cmd.d/02-shared-dev.toml:
name = "shared_dev_keys"
command = "cat"
args = ["/etc/ssh/shared_dev_keys"]
enabled = true
timeout = 10If you're migrating from the original single-file configuration:
- Run the install command to automatically migrate:
sudo ssh-auth-cmd install-
The installer will:
- Move your existing
AuthorizedKeysCommandto a config file - Comment out the old configuration
- Install the new ssh-auth-cmd configuration
- Move your existing
-
Verify the migration:
sudo ssh-auth-cmd config-check# Check all configurations
sudo ssh-auth-cmd config-check
# Test manually for a specific user
sudo ssh-auth-cmd key-cmd -u testuser
# Test with full OpenSSH context
sudo ssh-auth-cmd key-cmd -c "testuser,192.168.1.100,22,10.0.0.1,22" -u testuser -h hostname.example.com# Fix directory permissions
sudo chown -R root:root /etc/ssh/auth_cmd.d/
sudo chmod 755 /etc/ssh/auth_cmd.d/
sudo chmod 600 /etc/ssh/auth_cmd.d/*.toml
# Check command permissions
ls -la /usr/local/bin/your-auth-commandEnable SSH debug logging to see what's happening:
# In /etc/ssh/sshd_config
LogLevel DEBUG
# Then check logs
sudo tail -f /var/log/auth.log-
"Configuration directory is writable by group or others"
- Fix:
sudo chmod 755 /etc/ssh/auth_cmd.d/
- Fix:
-
"Configuration file is writable by group or others"
- Fix:
sudo chmod 600 /etc/ssh/auth_cmd.d/*.toml
- Fix:
-
"Command is not owned by root"
- Fix:
sudo chown root:root /path/to/command
- Fix:
-
"Invalid placeholder"
- Check that you're only using supported placeholders: %C, %D, %f, %h, %k, %t, %U, %u, %%
-
"User not found"
- Ensure the user specified in the
userfield exists on the system
- Ensure the user specified in the
- Commands run sequentially in alphabetical order by filename
- Set appropriate timeouts to prevent hanging connections
- Use
readonly = truefor audit/logging commands that don't provide keys - Consider disabling unnecessary commands in production environments
- Naming Convention: Use numbered prefixes (01-, 02-, etc.) to control execution order
- User Separation: Run different commands as different users for security isolation
- Timeouts: Set conservative timeouts to prevent hanging SSH connections
- Auditing: Use readonly commands for comprehensive audit logging
- Testing: Always test configurations with
config-checkbefore deployment - Monitoring: Monitor command execution times and failure rates
- Backup: Keep backups of working configurations before making changes
name = "vault_ssh_ca"
command = "/usr/local/bin/vault-ssh-helper"
args = ["-mode", "ca", "-username", "%u"]
enabled = true
timeout = 45
user = "vault-ssh"name = "ipa_keys"
command = "/usr/bin/sss_ssh_authorizedkeys"
args = ["%u"]
enabled = true
timeout = 30
user = "sssd"name = "db_keys"
command = "/usr/local/bin/db-ssh-keys"
args = [
"--user", "%u",
"--client-ip", "%C",
"--hostname", "%h"
]
enabled = true
timeout = 60
user = "db-auth"This comprehensive setup provides a secure, flexible, and maintainable way to chain multiple SSH authentication sources while maintaining full OpenSSH compatibility and security best practices.