shirk

SSH-based application control library for Crystal

Shirk

Shirk is a Crystal shard that enables applications to be controlled via SSH. It allows users to send messages, files, and interact with UIs through standard SSH protocols.

Installation

Prerequisites

  • Crystal 1.0.0 or higher
  • libssh development libraries

Install libssh on different systems:

Ubuntu/Debian:

sudo apt-get install libssh-dev

Fedora/CentOS/RHEL:

sudo dnf install libssh-devel
# or on older systems
sudo yum install libssh-devel

Arch Linux:

sudo pacman -S libssh

macOS (with Homebrew):

brew install libssh

Add to your project

Add this to your application's shard.yml:

dependencies:
  shirk:
    github: your-username/shirk

Then run:

shards install

Usage

Basic SSH Server

Create a basic SSH server that can receive messages:

require "shirk"

# Create SSH server on port 2222
server = Shirk::SSH::Server.new(2222)

# Set up message handler
server.on_message do |message|
  puts "Received: #{message}"

  case message.strip
  when "hello"
    "Hello! SSH server is working."
  when "time"
    "Current time: #{Time.local}"
  else
    "Echo: #{message}"
  end
end

# Handle Ctrl+C gracefully
Signal::INT.trap do
  puts "\nShutting down..."
  server.stop
  exit
end

puts "Starting SSH server on port 2222..."
server.start

Custom Authentication

Shirk supports customizable authentication handlers. You can control which users and public keys are allowed to connect:

require "shirk"

# Create a public key whitelist auth handler
auth_handler = Shirk::SSH::PublicKeyWhitelistAuth.new

# Add allowed public keys for users
auth_handler.add_key("alice", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC3 alice@example.com")
auth_handler.add_key("bob", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGb bob@workstation")

# Load keys from authorized_keys file
auth_handler.load_authorized_keys("alice", "/home/alice/.ssh/authorized_keys")

# Create server with custom authentication
server = Shirk::SSH::Server.new(2222, "0.0.0.0", auth_handler)

# Or set auth handler after creation
server.auth_handler = auth_handler

server.on_message do |message|
  "Hello authenticated user! You sent: #{message}"
end

server.start

Built-in Authentication Handlers

  • AcceptAllAuth - Accepts all authentication requests (demo/testing only)
  • RejectAllAuth - Rejects all authentication requests
  • PublicKeyWhitelistAuth - Allows only users with whitelisted public keys

Custom Authentication Handler

class MyAuthHandler < Shirk::SSH::AuthHandler
  def authenticate(request : Shirk::SSH::AuthRequest) : Shirk::SSH::AuthResult
    # Your custom authentication logic here
    if request.username == "admin" && request.pubkey?
      Shirk::SSH::AuthResult::Success
    else
      Shirk::SSH::AuthResult::Failure
    end
  end
end

server = Shirk::SSH::Server.new(2222)
server.auth_handler = MyAuthHandler.new

Connection Management and Validation

Shirk provides comprehensive connection management features including connection validation, rate limiting, event callbacks, and connection metadata tracking.

require "shirk"

server = Shirk::SSH::Server.new(2222)

# Set maximum concurrent connections
server.max_connections = 100

# Add IP whitelist validator
ip_validator = Shirk::SSH::IPWhitelistValidator.from_cidr(["127.0.0.1", "10.0.0.0/8"])
server.add_connection_validator(ip_validator)

# Add event callback for logging
logging_callback = Shirk::SSH::LoggingCallback.new
server.add_event_callback(logging_callback)

# Custom event callback
class MyEventCallback < Shirk::SSH::EventCallback
  def call(event : Shirk::SSH::Event) : Nil
    case event.type
    when .connection_start?
      puts "New connection from #{event.connection.remote_ip}"
    when .auth_success?
      puts "User authenticated: #{event.data}"
    when .message_received?
      puts "Message: #{event.data}"
    end
  end
end

server.add_event_callback(MyEventCallback.new)

server.on_message do |message|
  "Connection received: #{message}"
end

server.start

Built-in Connection Validators

  • IPWhitelistValidator - Allows connections only from whitelisted IP addresses or CIDR ranges
  • RateLimitValidator - Limits number of concurrent connections per IP address

Custom Connection Validator

class MyValidator < Shirk::SSH::ConnectionValidator
  def validate(connection : Shirk::SSH::ConnectionInfo) : Shirk::SSH::ConnectionResult
    # Your validation logic here
    if connection.remote_ip.starts_with?("192.168.")
      Shirk::SSH::ConnectionResult::Accept
    else
      Shirk::SSH::ConnectionResult::Reject
    end
  end
end

server.add_connection_validator(MyValidator.new)

Connection Information and Events

The system tracks comprehensive connection metadata:

# Get active connections
active_connections = server.active_connections
puts "Active connections: #{server.active_connections_count}"

# Each connection contains:
# - remote_ip : String
# - remote_port : Int32
# - connect_time : Time
# - username : String?
# - auth_method : String?
# - key_fingerprint : String?
# - duration : Time::Span
# - authenticated? : Bool

Event Types

  • ConnectionStart - New connection established
  • ConnectionEnd - Connection closed
  • AuthStart - Authentication attempt started
  • AuthSuccess - Authentication successful
  • AuthFailure - Authentication failed
  • MessageReceived - Message received from client
  • Error - Error occurred

Running the server

Save the code above to a file (e.g., server.cr) and run:

crystal build server.cr --link-flags "-lssh"
./server

Testing the server

You can test the SSH server using standard SSH clients:

# Send a message via SSH
echo "hello" | ssh localhost -p 2222

# Or use netcat/telnet for basic testing
echo "hello" | nc localhost 2222

Message Handlers

Shirk provides built-in message handlers and allows custom handlers:

Built-in Echo Handler

require "shirk"
require "shirk/handlers/message_handler"

# Use built-in echo handler
handler = Shirk::Handlers::MessageHandler.echo("Server")
server = Shirk::SSH::Server.new(2222)
server.on_message(&handler.handle)
server.start

Custom Handler with Block

server = Shirk::SSH::Server.new(2222)

server.on_message do |message|
  # Custom message processing
  if message.starts_with?("CMD:")
    command = message[4..-1].strip
    `#{command}`
  else
    "Unknown command: #{message}"
  end
end

server.start

Custom Handler Class

class MyHandler
  def handle(message : String) : String
    # Your custom logic here
    "Processed: #{message.reverse}"
  end
end

server = Shirk::SSH::Server.new(2222)
server.on_message(&MyHandler.new.handle)
server.start

API Reference

Shirk::SSH::Server

The main server class for handling SSH connections.

Constructors

  • new(port : Int32 = 2222, host : String = "0.0.0.0") - Create a new SSH server

Methods

  • on_message(&handler : String -> String) - Register a message handler
  • start - Start the SSH server
  • stop - Stop the SSH server

Shirk::Handlers

MessageHandler

  • new(prefix : String = "Echo") - Create a new message handler with prefix
  • echo(prefix : String = "Echo") - Factory method for echo handler
  • handle(message : String) : String - Process a message

CustomMessageHandler

  • new(&handler : String -> String) - Create custom handler with block

Examples

See the examples/ directory for complete examples:

  • message_server.cr - Basic message handling server
  • auth_demo.cr - Demonstration of custom authentication handlers
  • connection_demo.cr - NEW! Comprehensive connection management demo with validation, rate limiting, and event callbacks
  • simple_server.cr - Simple TCP server without SSH
  • More examples coming soon...

Connection Management Demo

The connection_demo.cr example showcases all the new connection management features:

# Build and run the connection management demo
crystal build examples/connection_demo.cr --link-flags "-lssh"
./connection_demo

# In another terminal, test the server
ssh localhost -p 3333
# Then try: stats, connections, help, hello

Features demonstrated:

  • Connection validation with IP whitelisting
  • Rate limiting and connection limits
  • Event callbacks for logging and monitoring
  • Connection metadata tracking
  • Authentication event tracking
  • Real-time statistics

Development

Building the project

# Build example
crystal build examples/message_server.cr --link-flags "-lssh"

# Run tests
crystal spec

# Build all examples
crystal build examples/*.cr --link-flags "-lssh"

Running the linter

ameba

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a Pull Request

Security Considerations

This is a basic SSH server implementation. For production use:

  1. Authentication: The current implementation accepts all authentication requests for demo purposes. Implement proper authentication in production.
  2. Key Management: Set up proper SSH host keys
  3. Input Validation: Validate and sanitize all incoming messages
  4. Rate Limiting: Implement rate limiting to prevent abuse
  5. Logging: Add proper logging for security auditing

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Built with Crystal
  • Uses libssh for SSH functionality
  • Inspired by the need for simple SSH-based application control
Repository

shirk

Owner
Statistic
  • 0
  • 0
  • 0
  • 1
  • 0
  • about 8 hours ago
  • November 26, 2025
License

MIT License

Links
Synced at

Thu, 27 Nov 2025 00:34:02 GMT

Languages