Skip to main content
Sentinel AI uses SSH to connect to remote servers for executing commands, monitoring services, and performing automated remediation tasks.

Overview

The SSH client is implemented in src/tools/ssh.py using the Paramiko library. It supports both password and key-based authentication.
Default SSH Settings:
  • Host: localhost
  • Port: 2222
  • User: sentinel
  • Connection timeout: 10 seconds

Authentication Methods

Password Authentication

The simplest method, suitable for development and testing environments.
SSH_HOST=192.168.1.100
SSH_PORT=22
SSH_USER=admin
SSH_PASS=your_secure_password
Password authentication is less secure than key-based authentication. For production environments, always use SSH keys.
More secure method using SSH key pairs.
1

Generate SSH key pair

Generate a new SSH key pair for Sentinel AI:
ssh-keygen -t ed25519 -C "sentinel-ai" -f ~/.ssh/sentinel_key
This creates:
  • Private key: ~/.ssh/sentinel_key
  • Public key: ~/.ssh/sentinel_key.pub
2

Copy public key to server

Add the public key to the target server’s authorized keys:
ssh-copy-id -i ~/.ssh/sentinel_key.pub user@server
Or manually:
cat ~/.ssh/sentinel_key.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
3

Configure Sentinel AI

Set the key file path in your configuration:
.env
SSH_HOST=prod-server.example.com
SSH_PORT=22
SSH_USER=sentinel
# SSH_PASS is not needed with key-based auth
Modify the SSH client initialization to use the key file:
from src.core.config import config
from src.tools.ssh import SSHClient

client = SSHClient(
    hostname=config.SSH_HOST,
    username=config.SSH_USER,
    key_filename="~/.ssh/sentinel_key",
    port=config.SSH_PORT
)
4

Test connection

Verify the SSH connection works:
ssh -i ~/.ssh/sentinel_key [email protected]

SSH Client Implementation

The SSHClient class handles connection management and command execution:
class SSHClient:
    def connect(self):
        try:
            self.client = paramiko.SSHClient()
            self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self.client.connect(
                hostname=self.hostname,
                port=self.port,
                username=self.username,
                password=self.password,
                key_filename=self.key_filename,
                timeout=10
            )
            print(f"[SSH] Conexion establecida con {self.username}@{self.hostname}:{self.port}")
        except Exception as e:
            print(f"[SSH] Error de conexion: {e}")
            raise e

Key Features

Auto Host Key Policy

Automatically accepts unknown host keys using AutoAddPolicy()

Sudo Support

Execute commands with sudo privileges using password injection

Connection Timeout

10-second timeout prevents hanging on unreachable hosts

Stop Signal Handling

Gracefully handles interruption signals during command execution

User Permissions

The SSH user needs appropriate permissions to monitor and manage services.
1

Create dedicated user

Create a dedicated user for Sentinel AI:
sudo adduser sentinel
sudo usermod -aG sudo sentinel
2

Configure sudoers

Allow specific commands without password prompt:
sudo visudo
Add these lines:
/etc/sudoers
# Sentinel AI permissions
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/service
sentinel ALL=(ALL) NOPASSWD: /bin/systemctl
sentinel ALL=(ALL) NOPASSWD: /usr/bin/docker
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/nginx
3

Test permissions

Verify the user can execute service commands:
sudo -u sentinel service nginx status
sudo -u sentinel systemctl status postgresql
Grant only the minimum permissions required. Avoid giving full sudo access without password requirements.

Service-Specific Permissions

Depending on which services you’re monitoring:
/etc/sudoers.d/sentinel-nginx
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/service nginx status
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/service nginx start
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/service nginx stop
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/service nginx restart
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/nginx -t
/etc/sudoers.d/sentinel-postgresql
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/service postgresql status
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/service postgresql start
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/service postgresql stop
sentinel ALL=(ALL) NOPASSWD: /usr/sbin/service postgresql restart
sentinel ALL=(ALL) NOPASSWD: /usr/bin/psql
# Add sentinel to docker group
sudo usermod -aG docker sentinel

# Or use sudoers
sentinel ALL=(ALL) NOPASSWD: /usr/bin/docker ps
sentinel ALL=(ALL) NOPASSWD: /usr/bin/docker restart
sentinel ALL=(ALL) NOPASSWD: /usr/bin/docker logs

Security Best Practices

Use SSH Keys

Always use key-based authentication in production. Disable password authentication in SSH config.

Restrict SSH User

Create a dedicated user with limited permissions. Never use root account.

Enable SSH Logging

Monitor SSH access logs at /var/log/auth.log for unauthorized access attempts.

Use Firewall Rules

Restrict SSH access to specific IP addresses using iptables or cloud security groups.

Rotate Keys Regularly

Change SSH keys periodically, especially after team member departures.

Disable Root Login

Set PermitRootLogin no in /etc/ssh/sshd_config.

Connection Testing

Test your SSH configuration before running Sentinel AI:
from src.tools.ssh import SSHClient
from src.core.config import config

def test_ssh_connection():
    """Test SSH connection and basic commands."""
    try:
        # Initialize client
        client = SSHClient(
            hostname=config.SSH_HOST,
            username=config.SSH_USER,
            password=config.SSH_PASS,
            port=config.SSH_PORT
        )
        
        # Connect
        client.connect()
        print("✓ SSH connection established")
        
        # Test basic command
        exit_code, output, error = client.execute_command("whoami")
        print(f"✓ Basic command: {output}")
        
        # Test service status command
        exit_code, output, error = client.execute_command("service nginx status")
        print(f"✓ Service check: exit code {exit_code}")
        
        # Clean up
        client.close()
        print("✓ Connection closed successfully")
        
    except Exception as e:
        print(f"✗ Connection failed: {e}")
        return False
    
    return True

if __name__ == "__main__":
    test_ssh_connection()

Troubleshooting

Error: Connection timeout after 10 secondsSolutions:
  • Verify the SSH_HOST and SSH_PORT are correct
  • Check firewall rules allow connections on the SSH port
  • Ensure the target server’s SSH service is running
  • Test with direct SSH: ssh -p 2222 user@host
Error: Authentication failedSolutions:
  • Verify SSH_USER and SSH_PASS are correct
  • For key-based auth, ensure the key file exists and has correct permissions (600)
  • Check that the public key is in the server’s ~/.ssh/authorized_keys
  • Verify the user account is not locked on the server
Error: Permission denied when executing service commandsSolutions:
  • Configure sudoers as described in User Permissions section
  • Test with direct SSH: ssh user@host 'sudo service nginx status'
  • Ensure the user is in the sudo group
  • Check /var/log/auth.log for sudo errors
Error: Host key verification failedSolutions:
  • The SSH client uses AutoAddPolicy() to accept new keys automatically
  • If this still occurs, remove old key: ssh-keygen -R hostname
  • Or manually accept: ssh -o StrictHostKeyChecking=no user@host

Advanced Configuration

Multiple Servers

To manage multiple servers, extend the configuration:
SERVERS = {
    "production": {
        "host": "prod.example.com",
        "port": 22,
        "user": "sentinel",
        "key_file": "~/.ssh/sentinel_prod"
    },
    "staging": {
        "host": "staging.example.com",
        "port": 22,
        "user": "sentinel",
        "key_file": "~/.ssh/sentinel_staging"
    }
}

def get_ssh_client(server_name: str) -> SSHClient:
    """Get SSH client for a specific server."""
    server = SERVERS[server_name]
    return SSHClient(
        hostname=server["host"],
        username=server["user"],
        key_filename=server["key_file"],
        port=server["port"]
    )

SSH Bastion/Jump Host

For servers behind a bastion host:
import paramiko

def connect_via_bastion(bastion_host, bastion_user, target_host, target_user):
    """Connect to target server via bastion host."""
    # Connect to bastion
    bastion = paramiko.SSHClient()
    bastion.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    bastion.connect(bastion_host, username=bastion_user)
    
    # Create transport to target through bastion
    bastion_transport = bastion.get_transport()
    dest_addr = (target_host, 22)
    bastion_channel = bastion_transport.open_channel("direct-tcpip", dest_addr, ('', 0))
    
    # Connect to target
    target = paramiko.SSHClient()
    target.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    target.connect(target_host, username=target_user, sock=bastion_channel)
    
    return target

Next Steps

Environment Variables

Configure all environment variables

Services Configuration

Define services to monitor

Deployment

Deploy Sentinel AI with Docker

Build docs developers (and LLMs) love