Вопрос или проблема
Добрый день!
Я пытаюсь настроить 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.
Основные моменты
-
Настройка SSH-сервера: Ваша конфигурация SSH-сервера уже верна, однако имейте в виду, что пока вы используете анонимную аутентификацию (NoClientAuth: true), вы позволите любому клиенту подключаться. Рекомендуется использовать более строгую аутентификацию для безопасности.
-
Обработка запросов на перенаправление: Ваша функция
handleRequest
только создает слушателя на удаленном порту, но вы не устанавливаете соединение с целевым сервером. Это необходимо для корректной работы удаленного перенаправления. -
Установка соединения с целевым сервером: При получении запроса на перенаправление (например, от
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 {}
}
Замечания
-
Управление NAT: Если ваш сервер находится за NAT и не имеет перенаправленных входящих портов, вам нужно убедиться, что у вас есть способ подключения к вашему серверу извне (например, через туннель, VPN или другой обрабатывающий сервер).
-
Безопасность: Настоятельно рекомендуется внедрить аутентификацию клиентов на сервере (например, через публичные ключи) для защиты от несанкционированного доступа.
-
Логирование: Добавьте логирование для отслеживания ошибок и поведения вашего сервера в продуктивной среде.
Это решение должно позволить вашему серверу корректно обрабатывать запросы на удаленное перенаправление портов для входящих соединений.