ssh backend (mostly)

This commit is contained in:
valerie 2024-12-01 23:14:33 -05:00
parent aa16549667
commit 79389d37b8
Signed by: valnyx
GPG key ID: CC10324DD962CB7E
3 changed files with 219 additions and 1 deletions

View file

@ -0,0 +1,213 @@
package main
import (
"encoding/json"
"fmt"
"net"
"os"
"strings"
"git.greysoh.dev/imterah/nextnet/backendutil"
"git.greysoh.dev/imterah/nextnet/commonbackend"
"github.com/charmbracelet/log"
"golang.org/x/crypto/ssh"
)
type SSHBackend struct {
data SSHBackendData
conn ssh.Client
clients []*commonbackend.ClientConnection
}
type SSHBackendData struct {
Ip string `json:"ip"`
Port uint16 `json:"port"`
Username string `json:"username"`
PrivateKey string `json:"privateKey"`
ListenOnIPs []string `json:"listenOnIPs"`
}
func (backend *SSHBackend) StartBackend(bytes []byte) (bool, error) {
var backendData SSHBackendData
err := json.Unmarshal(bytes, &backendData) // ?????
if err != nil {
return false, err
}
backend.data = backendData
if len(backend.data.ListenOnIPs) == 0 {
backend.data.ListenOnIPs = []string{"0.0.0.0"}
}
// create signer for privateKey
signer, err := ssh.ParsePrivateKey([]byte(backendData.PrivateKey))
if err != nil {
return false, err
}
auth := ssh.PublicKeys(signer)
config := &ssh.ClientConfig{
User: backendData.Username,
Auth: []ssh.AuthMethod{
auth,
},
}
conn, err := ssh.Dial("tcp", backendData.Ip+":"+string(backendData.Port), config)
if err != nil {
return false, err
}
backend.conn = conn
return true, nil
}
func (backend *SSHBackend) StopBackend() (bool, error) {
err := backend.conn.Close()
if err != nil {
return false, err
}
return true, nil
}
func (backend *SSHBackend) AddConnection(command *commonbackend.AddConnectionCommand) (bool, error) {
for _, ipListener := range backend.data.ListenOnIPs {
ip := net.TCPAddr{
IP: net.ParseIP(ipListener),
Port: int(command.DestPort),
}
listener, err := backend.conn.ListenTCP(&ip)
if err != nil {
return false, err
}
go func() {
for {
forwardedConn, err := listener.Accept()
if err != nil {
log.Warnf("failed to accept listener connection: %s", err.Error())
continue
}
sourceConn, err := net.Dial("tcp", command.SourceIP+":"+string(command.SourcePort))
if err != nil {
log.Warnf("failed to dial source connection: %s", err.Error())
continue
}
sourceBuffer := make([]byte, 65535)
forwardedBuffer := make([]byte, 65535)
go func() {
defer sourceConn.Close()
defer forwardedConn.Close()
for {
len, err := forwardedConn.Read(forwardedBuffer)
if err != nil {
log.Errorf("failed to read from forwarded connection: %s", err.Error())
return
}
_, err = sourceConn.Write(forwardedBuffer[:len])
if err != nil {
log.Errorf("failed to write to source connection: %s", err.Error())
return
}
}
}()
go func() {
defer sourceConn.Close()
defer forwardedConn.Close()
for {
len, err := sourceConn.Read(sourceBuffer)
if err != nil && err.Error() != "EOF" && strings.HasSuffix(err.Error(), "use of closed network connection") {
log.Errorf("failed to read from source connection: %s", err.Error())
return
}
_, err = forwardedConn.Write(sourceBuffer[:len])
if err != nil && err.Error() != "EOF" && strings.HasSuffix(err.Error(), "use of closed network connection") {
log.Errorf("failed to write to forwarded connection: %s", err.Error())
return
}
}
}()
}
}()
}
return true, nil
}
func (backend *SSHBackend) RemoveConnection(command *commonbackend.RemoveConnectionCommand) (bool, error) {
// FIXME: implement
return true, nil
}
func (backend *SSHBackend) GetAllConnections() []*commonbackend.ClientConnection {
// return []*commonbackend.ClientConnection{}
return backend.clients
}
func (backend *SSHBackend) CheckParametersForConnections(clientParameters *commonbackend.CheckClientParameters) *commonbackend.CheckParametersResponse {
if clientParameters.Protocol != "tcp" {
return &commonbackend.CheckParametersResponse{
IsValid: false,
Message: "Only TCP is supported",
}
}
return &commonbackend.CheckParametersResponse{
IsValid: true,
}
}
func (backend *SSHBackend) CheckParametersForBackend(arguments []byte) *commonbackend.CheckParametersResponse {
var backendData SSHBackendData
err := json.Unmarshal(arguments, &backendData) // ?????
if err != nil {
return &commonbackend.CheckParametersResponse{
IsValid: false,
Message: fmt.Sprintf("could not read json: %s", err.Error()),
}
}
return &commonbackend.CheckParametersResponse{
IsValid: true,
}
}
func main() {
// When using logging, you should use charmbracelet/log, because that's what everything else uses in this ecosystem of a project. - imterah
logLevel := os.Getenv("NEXTNET_LOG_LEVEL")
if logLevel != "" {
switch logLevel {
case "debug":
log.SetLevel(log.DebugLevel)
case "info":
log.SetLevel(log.InfoLevel)
case "warn":
log.SetLevel(log.WarnLevel)
case "error":
log.SetLevel(log.ErrorLevel)
case "fatal":
log.SetLevel(log.FatalLevel)
}
}
backend := &SSHBackend{}
application := backendutil.NewHelper(backend)
err := application.Start()
if err != nil {
log.Fatalf("failed execution in application: %s", err.Error())
}
}