bitbot/bot/sshclient.go

241 lines
5.0 KiB
Go

package bot
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"os"
"strings"
"github.com/charmbracelet/log"
"golang.org/x/crypto/ssh"
)
const (
privateKeyPath = "private_key.pem"
publicKeyPath = "public_key.pub"
bits = 2048
)
type SSHConnection struct {
client *ssh.Client
commands chan string
responses chan string
}
func NewSSHConnection(client *ssh.Client) *SSHConnection {
return &SSHConnection{
client: client,
commands: make(chan string),
responses: make(chan string),
}
}
func (conn *SSHConnection) startCommandExecution() {
for cmd := range conn.commands {
// Execute the command and send the response back
response, err := executeRemoteCommand(conn.client, cmd)
if err != nil {
log.Error(err)
conn.responses <- "Error executing command"
} else {
conn.responses <- response
}
}
}
func executeRemoteCommand(client *ssh.Client, command string) (string, error) {
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
output, err := session.CombinedOutput(command)
if err != nil {
return "", err
}
return string(output), nil
}
func GenerateAndSaveSSHKeyPairIfNotExist() error {
if KeyFilesExist(privateKeyPath, publicKeyPath) {
return nil
}
privateKey, publicKey, err := GenerateSSHKeyPair(bits)
if err != nil {
return err
}
err = SavePrivateKeyToFile(privateKeyPath, privateKey)
if err != nil {
return err
}
err = SavePublicKeyToFile(publicKeyPath, publicKey)
if err != nil {
return err
}
return nil
}
func GenerateAndSaveSSHKeyPair() error {
privateKey, publicKey, err := GenerateSSHKeyPair(bits)
if err != nil {
return err
}
err = SavePrivateKeyToFile(privateKeyPath, privateKey)
if err != nil {
return err
}
err = SavePublicKeyToFile(publicKeyPath, publicKey)
if err != nil {
return err
}
return nil
}
func KeyFilesExist(privateKeyPath, publicKeyPath string) bool {
_, privateKeyErr := os.Stat(privateKeyPath)
_, publicKeyErr := os.Stat(publicKeyPath)
return !os.IsNotExist(privateKeyErr) && !os.IsNotExist(publicKeyErr)
}
func GenerateSSHKeyPair(bits int) (*rsa.PrivateKey, ssh.PublicKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, nil, err
}
publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return nil, nil, err
}
return privateKey, publicKey, nil
}
func SavePrivateKeyToFile(filename string, privateKey *rsa.PrivateKey) error {
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyBytes,
}
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
err = pem.Encode(file, privateKeyPEM)
if err != nil {
return err
}
return nil
}
func SavePublicKeyToFile(filename string, publicKey ssh.PublicKey) error {
publicKeyBytes := ssh.MarshalAuthorizedKey(publicKey)
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(string(publicKeyBytes))
if err != nil {
return err
}
return nil
}
func SSHConnectToRemoteServer(connectionDetails string) (*SSHConnection, error) {
privateKey, err := LoadPrivateKey(privateKeyPath)
if err != nil {
return nil, err
}
config := &ssh.ClientConfig{
User: "username", // You will replace this with the actual username extracted from connectionDetails
Auth: []ssh.AuthMethod{
ssh.PublicKeys(privateKey),
},
// Other configuration options like HostKeyCallback, etc.
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Extract username, host, and port from connectionDetails
parts := strings.Split(connectionDetails, "@")
if len(parts) != 2 {
return nil, errors.New("invalid connection format")
}
username := parts[0]
hostPort := parts[1]
config.User = username // Set the actual username
// Connect to the remote server
client, err := ssh.Dial("tcp", hostPort, config)
if err != nil {
return nil, err
}
// Create an SSH connection instance
conn := NewSSHConnection(client)
// Start the goroutine for command execution
go conn.startCommandExecution()
// Now you have an active SSH connection with command execution capabilities
return conn, nil
}
func (conn *SSHConnection) ExecuteCommand(command string) (string, error) {
// Send the command to the goroutine for execution
conn.commands <- command
// Receive the response
response := <-conn.responses
return response, nil
}
func LoadPrivateKey(path string) (ssh.Signer, error) {
keyBytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
privateKeyBlock, _ := pem.Decode(keyBytes)
if privateKeyBlock == nil || privateKeyBlock.Type != "RSA PRIVATE KEY" {
return nil, errors.New("failed to decode valid private key")
}
privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
if err != nil {
return nil, err
}
return ssh.NewSignerFromKey(privateKey)
}
func GetPublicKey() (string, error) {
publicKeyBytes, err := os.ReadFile(publicKeyPath)
if err != nil {
return "", err
}
return string(publicKeyBytes), nil
}