How to Configure SSH authorized_keys for Multiple Command Restrictions


2 views

When using SSH key authentication, the command="..." option in ~/.ssh/authorized_keys provides a powerful way to restrict what a specific key can execute. However, this approach has a significant limitation: it only allows a single command to be specified per key entry.

The restrictive nature of the command option becomes apparent when you need to:

  • Allow access to multiple related commands
  • Create a whitelist of permitted operations
  • Maintain security while providing flexible access

1. Wrapper Script Approach

The most reliable method is to create a wrapper script that validates and dispatches commands:

#!/bin/bash
# /usr/local/bin/ssh_wrapper.sh

valid_commands=("backup" "status" "restart")

if [[ " ${valid_commands[*]} " =~ " $1 " ]]; then
    "$@"
else
    echo "Error: Invalid command. Allowed commands: ${valid_commands[*]}" >&2
    exit 1
fi

Then in authorized_keys:

command="/usr/local/bin/ssh_wrapper.sh $SSH_ORIGINAL_COMMAND" ssh-rsa AAAAB3NzaC1yc2E...

2. ForceCommand with Case Statement

An alternative is to use ForceCommand in sshd_config with pattern matching:

Match User restricted_user
    ForceCommand case "$SSH_ORIGINAL_COMMAND" in
        "backup"|"status"|"logs") eval "$SSH_ORIGINAL_COMMAND";;
        *) echo "Command not allowed"; exit 1;;
    esac

3. Environment Variable Validation

For more complex scenarios, combine environment variables with command restrictions:

command="if [[ \"$SSH_ALLOWED_CMD\" =~ ^(backup|status)$ ]]; then $SSH_ORIGINAL_COMMAND; else echo 'Denied'; fi" ssh-rsa AAAAB3NzaC1yc2E...

When implementing multiple command restrictions:

  • Always validate command arguments
  • Use absolute paths for both commands and scripts
  • Consider using SSH certificates for more granular control
  • Regularly audit allowed commands

Here's a more sophisticated Python-based solution:

#!/usr/bin/env python3
# /usr/local/bin/ssh_command_router.py

import sys
import subprocess

ALLOWED_COMMANDS = {
    'backup': {'args': 1, 'path': '/usr/bin/backup_script'},
    'status': {'args': 0, 'path': '/usr/bin/system_status'}
}

def main():
    if len(sys.argv) < 2:
        print("No command specified", file=sys.stderr)
        return 1
    
    cmd = sys.argv[1]
    if cmd not in ALLOWED_COMMANDS:
        print(f"Command {cmd} not allowed", file=sys.stderr)
        return 1
        
    if len(sys.argv[2:]) > ALLOWED_COMMANDS[cmd]['args']:
        print("Too many arguments", file=sys.stderr)
        return 1
        
    try:
        subprocess.run([ALLOWED_COMMANDS[cmd]['path']] + sys.argv[2:])
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        return 1

if __name__ == "__main__":
    sys.exit(main())

Configure in authorized_keys:

command="/usr/local/bin/ssh_command_router.py $SSH_ORIGINAL_COMMAND" ssh-rsa AAAAB3NzaC1yc2E...

The command="..." option in SSH's authorized_keys file is commonly used to force specific commands when a particular key is used for authentication. However, many developers don't realize this option is strictly limited to single command execution.

# Example of single command restriction
ssh-rsa AAAAB3Nza... command="/usr/bin/backup.sh" user@host

In real-world scenarios, system administrators often need to:

  • Allow different maintenance scripts through the same key
  • Implement granular access control for CI/CD pipelines
  • Create restricted interfaces for automated tools

While there's no direct regex support in authorized_keys, we can implement multi-command restrictions through these methods:

1. Wrapper Script Approach

Create a dispatcher script that checks SSH_ORIGINAL_COMMAND:

#!/bin/bash
# /usr/local/bin/ssh_wrapper.sh

case "$SSH_ORIGINAL_COMMAND" in
    "script1")
        /path/to/script1.sh
        ;;
    "script2")
        /path/to/script2.sh
        ;;
    *)
        echo "Invalid command"
        exit 1
        ;;
esac

Then reference it in authorized_keys:

ssh-rsa AAAAB3Nza... command="/usr/local/bin/ssh_wrapper.sh" user@host

2. ForceCommand with Match Block

Combine sshd_config settings with key restrictions:

# In /etc/ssh/sshd_config
Match User restricteduser
    ForceCommand /usr/local/bin/multi_command.sh

3. Environment Variable Validation

For more complex scenarios, validate through environment variables:

#!/bin/bash
# /usr/local/bin/env_validator.sh

if [[ "$ALLOWED_COMMANDS" == *"$SSH_ORIGINAL_COMMAND"* ]]; then
    eval "$SSH_ORIGINAL_COMMAND"
else
    echo "Command not permitted"
fi

When implementing multiple command restrictions:

  • Always validate and sanitize command inputs
  • Use absolute paths for all executables
  • Implement proper logging of command execution
  • Consider using SSH certificates instead of keys for more granular control

For enterprise environments, SSH certificates offer more flexibility:

# Creating a certificate with multiple force-command options
ssh-keygen -s ca_key -I "restricted_access" \
    -n "script1,script2" \
    -V +1d user_key.pub