Удалённая переадресация SSH на Golang

Вопрос или проблема

Добрый день!

Я пытаюсь настроить SSH-сервер на Go, который принимает запросы от клиентов и начинает портовое перенаправление.

Я понимаю, что в OpenSSH мы можем установить туннель через частную сеть, и нам не требуется публичный IP-адрес, и он использует GatewayPorts.

Я провел некоторые исследования и смог создать программу, которая помогает в портовом перенаправлении в локальной сети, используя только функцию netDial (логика для передачи адреса в netDial пока что не закончена).

Однако, как это будет работать, когда мы начинаем туннель из частной сети за маршрутизатором, у которого нет входящего NAT (Destination NAT)?

Вот код:

package main

import (
    "net"
    "fmt"
    "os"
    "io/ioutil"
    "log"
    "io"
    "golang.org/x/crypto/ssh"
    "sync"
)

var config *ssh.ServerConfig // Объявить конфигурацию на уровне пакета

func startRemoteListener(port int) {
    listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
    if err != nil {
        fmt.Fprintf(os.Stderr, "Ошибка запуска слушателя на порту %d: %v\n", port, err)
        return
    }
    defer listener.Close()
    fmt.Printf("\nОжидание на порту %d...\n", port)

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Fprintf(os.Stderr, "Ошибка приема соединения на порту %d: %v\n", port, err)
            continue
        }
        go handleRemoteListenerConnection(conn)
    }
}

func startListener(port int) {
    listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
    if err != nil {
        fmt.Fprintf(os.Stderr, "Ошибка запуска слушателя на порту %d: %v\n", port, err)
        return
    }
    defer listener.Close()
    fmt.Printf("\nОжидание на порту %d...\n", port)

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Fprintf(os.Stderr, "Ошибка приема соединения на порту %d: %v\n", port, err)
            continue
        }
        go handleConnection(conn, port) // Обработка каждого соединения в новой горутине
    }
}

func handleConnection(conn net.Conn, port int) {

    fmt.Println("Это новое соединение на порту:", port)
    fmt.Println("Это соединение:", conn)

    // Увеличение этого соединения до SSH

    sshConn, chans, reqs, err := ssh.NewServerConn(conn, config)

    if err != nil {
        log.Printf("Не удалось выполнить обмен: %v", err)
        return
    }

    fmt.Println(sshConn, "\n",  chans ,  "\n", reqs, "\n", err)

    go handleRequest(reqs, conn, sshConn)

}

func handleRequest(reqs <-chan *ssh.Request, conn net.Conn, sshConn *ssh.ServerConn ) {

for req := range reqs {
        var payload struct {
            Address string
            Port    uint32
        }
        if err := ssh.Unmarshal(req.Payload, &payload); err != nil {
            req.Reply(false, nil)
            continue
        }

        // Мы успешно начали слушать на новом порту
        go startRemoteListener(int(payload.Port))

}
}

func handleRemoteListenerConnection(conn net.Conn) {
    // Подключение к переадресованному хосту
    targetConn, err := net.Dial("tcp", ":")
    if err != nil {
        log.Printf("Не удалось подключиться к цели: %v", err)
        conn.Close()
        return
    }

    // Использование WaitGroup для обеспечения правильного закрытия
    var wg sync.WaitGroup
    wg.Add(2)

    // Перенаправление данных от целевого соединения к входящему соединению
    go func() {
        defer wg.Done()
        if _, err := io.Copy(targetConn, conn); err != nil {
            log.Printf("Ошибка перенаправления к цели: %v", err)
        }
    }()

    // Перенаправление данных от входящего соединения к целевому соединению
    go func() {
        defer wg.Done()
        if _, err := io.Copy(conn, targetConn); err != nil {
            log.Printf("Ошибка перенаправления от цели: %v", err)
        }
    }()

    // Ожидание завершения обеих операций перенаправления
    wg.Wait()
    targetConn.Close()
    conn.Close()
}

func main() {
    privateKeyPath := "/home/ubuntu/.ssh/id_rsa" // Путь к вашему файлу закрытого ключа
    privateKey, err := ioutil.ReadFile(privateKeyPath)
    if err != nil {
        log.Fatalf("Не удалось прочитать закрытый ключ: %v", err)
    }

    signer, err := ssh.ParsePrivateKey(privateKey)
    if err != nil {
        log.Fatalf("Не удалось разобрать закрытый ключ: %v", err)
    }

    // Инициализация конфигурации сервера
    config = &ssh.ServerConfig{
        NoClientAuth: true, // Измените это в зависимости от ваших потребностей
    }
    config.AddHostKey(signer)

    // Начать ожидание входящих SSH-соединений
    go startListener(22)

    // Поддерживать основную горутину активной
    select {}
}

Пожалуйста, предложите, спасибо.

Я пытаюсь запустить следующую команду из любой частной сети:

ssh -R 8888:localhost:80 SSH_SERVER

Тогда этот трафик на SSH_SERVER:8888 должен перенаправляться на localhost

Ответ или решение

Для настройки SSH-сервера в Golang с поддержкой удаленного портового перенаправления (SSH Remote Forwarding), необходимо понимать, как правильно обрабатывать ssh.Request и перенаправление сетевых соединений. Обратим внимание на ключевые моменты вашего кода и как его доработать, чтобы он корректно работал в условиях, когда сервер находится за NAT без входящего NAT.

Основные моменты

  1. Настройка SSH-сервера: Ваша конфигурация SSH-сервера уже верна, однако имейте в виду, что пока вы используете анонимную аутентификацию (NoClientAuth: true), вы позволите любому клиенту подключаться. Рекомендуется использовать более строгую аутентификацию для безопасности.

  2. Обработка запросов на перенаправление: Ваша функция handleRequest только создает слушателя на удаленном порту, но вы не устанавливаете соединение с целевым сервером. Это необходимо для корректной работы удаленного перенаправления.

  3. Установка соединения с целевым сервером: При получении запроса на перенаправление (например, от ssh -R 8888:localhost:80 SSH_SERVER), вам нужно правильно настроить net.Dial для подключения к локальному серверу.

Исправленный код

Вот как можно доработать ваш код для реализации удаленного перенаправления:

package main

import (
    "fmt"
    "io/ioutil"
    "io"
    "log"
    "net"
    "sync"
    "golang.org/x/crypto/ssh"
)

var config *ssh.ServerConfig // Объявление config на уровне пакета

func startListener(port int) {
    listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
    if err != nil {
        log.Fatalf("Error starting listener on port %d: %v\n", port, err)
    }
    defer listener.Close()
    log.Printf("Listening on port %d...\n", port)

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Printf("Error accepting connection: %v\n", err)
            continue
        }
        go handleConnection(conn) // Обработка каждого соединения в новой горутине
    }
}

func handleConnection(conn net.Conn) {
    defer conn.Close()

    sshConn, chans, reqs, err := ssh.NewServerConn(conn, config)
    if err != nil {
        log.Printf("Failed to handshake: %v", err)
        return
    }

    log.Printf("New SSH connection from %s", sshConn.RemoteAddr())

    go ssh.DiscardRequests(reqs) // Игнорируем запросы, если они не нужны

    for newChannel := range chans {
        if newChannel.ChannelType() == "auth-port-forwarding" {
            go handleRemoteForwarding(newChannel)
        } else {
            newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
        }
    }
}

func handleRemoteForwarding(newChannel ssh.NewChannel) {
    channel, reqs, err := newChannel.Accept()
    if err != nil {
        log.Printf("Could not accept channel: %v", err)
        return
    }
    defer channel.Close()

    // Чтение данных с канала и перенаправление их на целевую точку
    go func() {
        var wg sync.WaitGroup
        targetAddr := "localhost:80" // Целевой адрес, на который происходит перенаправление
        targetConn, err := net.Dial("tcp", targetAddr)
        if err != nil {
            log.Printf("Failed to connect to target: %v", err)
            return
        }
        defer targetConn.Close()

        wg.Add(2)
        go func() {
            io.Copy(targetConn, channel)
            wg.Done()
        }()
        go func() {
            io.Copy(channel, targetConn)
            wg.Done()
        }()

        wg.Wait()
    }()

    ssh.DiscardRequests(reqs) // Игнорируем дополнительные запросы
}

func main() {
    privateKeyPath := "/home/ubuntu/.ssh/id_rsa" // Путь к приватному ключу
    privateKey, err := ioutil.ReadFile(privateKeyPath)
    if err != nil {
        log.Fatalf("Unable to read private key: %v", err)
    }

    signer, err := ssh.ParsePrivateKey(privateKey)
    if err != nil {
        log.Fatalf("Unable to parse private key: %v", err)
    }

    config = &ssh.ServerConfig{
        NoClientAuth: true,
    }
    config.AddHostKey(signer)

    go startListener(22) // Запускаем слушателя на порту 22

    // Держим главную горутину активной
    select {}
}

Замечания

  1. Управление NAT: Если ваш сервер находится за NAT и не имеет перенаправленных входящих портов, вам нужно убедиться, что у вас есть способ подключения к вашему серверу извне (например, через туннель, VPN или другой обрабатывающий сервер).

  2. Безопасность: Настоятельно рекомендуется внедрить аутентификацию клиентов на сервере (например, через публичные ключи) для защиты от несанкционированного доступа.

  3. Логирование: Добавьте логирование для отслеживания ошибок и поведения вашего сервера в продуктивной среде.

Это решение должно позволить вашему серверу корректно обрабатывать запросы на удаленное перенаправление портов для входящих соединений.

Оцените материал
Добавить комментарий

Капча загружается...