Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions docker-compose.yaml.example
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
version: "3.0"

networks:
guacd:
lion:
driver: bridge

services:
guacd:
image: jumpserver/guacd:1.4.0
container_name: guacd
lion:
image: jumpserver/lion:dev
build:
context: .
dockerfile: Dockerfile
args:
VERSION: dev
container_name: jms_lion
ports:
- "8081:8081"
- "4822:4822"
environment:
CORE_HOST: ${CORE_HOST:-http://10.0.0.5:8080}
BOOTSTRAP_TOKEN: ${BOOTSTRAP_TOKEN:-ICAgICAgICBUWCBl}
BIND_HOST: 0.0.0.0
HTTPD_PORT: 8081
LOG_LEVEL: DEBUG
GUA_HOST: 127.0.0.1
GUA_PORT: 4822
SHARE_ROOM_TYPE: local
GUACD_LOG_LEVEL: debug
networks:
- guacd
- lion
restart: always
volumes:
- ./data/:/opt/lion/data/:rw # /opt/lion/-> 本地项目路径, 修改 ./data 目录权限为777
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,5 @@ require (
golang.org/x/time v0.12.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

replace github.com/jumpserver-dev/sdk-go => ../sdk-go
21 changes: 15 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,19 @@ func main() {
config.Setup(configPath)
logger.SetupLogger(config.GlobalConfig)
jmsService := MustJMService()
pandaClient := NewPandaClient(*config.GlobalConfig)
pandaClientFactory := NewPandaClientFactory(*config.GlobalConfig)
pandaClient := pandaClientFactory(config.GlobalConfig.PandaHost)
bootstrap(jmsService)
tunnelService := tunnel.GuacamoleTunnelServer{
Cache: &tunnel.GuaTunnelCacheManager{
GuaTunnelCache: NewGuaTunnelCache(),
},
JmsService: jmsService,
SessionService: &session.Server{JmsService: jmsService,
PandaClient: pandaClient},
SessionService: &session.Server{
JmsService: jmsService,
PandaClient: pandaClient,
PandaClientFactory: pandaClientFactory,
},
}
eng := registerRouter(jmsService, &tunnelService)
go runHeartTask(jmsService, tunnelService.Cache)
Expand Down Expand Up @@ -631,11 +635,16 @@ func MustValidKey(key model.AccessKey) model.AccessKey {
}

func NewPandaClient(cfg config.Config) *panda.Client {
pandaHost := cfg.PandaHost
return NewPandaClientFactory(cfg)(cfg.PandaHost)
}

func NewPandaClientFactory(cfg config.Config) func(string) *panda.Client {
var key model.AccessKey
if err := key.LoadFromFile(cfg.AccessKeyFilePath); err != nil {
logger.Errorf("Create panda client failed: loading access key err %s", err)
return nil
return func(string) *panda.Client { return nil }
}
return func(pandaHost string) *panda.Client {
return panda.NewClient(pandaHost, key, cfg.IgnoreVerifyCerts)
}
return panda.NewClient(pandaHost, key, cfg.IgnoreVerifyCerts)
}
77 changes: 70 additions & 7 deletions pkg/gateway/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ type DomainGateway struct {
DstAddr string // 10.0.0.1:3389

sshClient *gossh.Client
jumpClient *gossh.Client
SelectedGateway *model.Gateway
Destination *model.Gateway

ln net.Listener

Expand All @@ -40,10 +42,10 @@ func (d *DomainGateway) run() {
if err != nil {
break
}
logger.Infof("Accept new conn by gateway %s ", d.SelectedGateway.Name)
logger.Infof("Accept new conn by SSH forwarder %s ", d.Name())
go d.handlerConn(con)
}
logger.Infof("Stop proxy by gateway %s", d.SelectedGateway.Name)
logger.Infof("Stop proxy by SSH forwarder %s", d.Name())
}

func (d *DomainGateway) handlerConn(srcCon net.Conn) {
Expand Down Expand Up @@ -82,6 +84,16 @@ func (d *DomainGateway) GetListenAddr() *net.TCPAddr {
}

func (d *DomainGateway) getAvailableGateway() bool {
if d.Destination != nil {
sshClient, jumpClient, err := d.createDestinationSSHClient()
if err != nil {
logger.Errorf("Dial SSH destination %s err: %s", d.Destination.Name, err)
return false
}
d.sshClient = sshClient
d.jumpClient = jumpClient
return true
}
if d.SelectedGateway != nil {
sshClient, err := d.createGatewaySSHClient(d.SelectedGateway)
if err != nil {
Expand All @@ -94,7 +106,39 @@ func (d *DomainGateway) getAvailableGateway() bool {
}
return false
}

func (d *DomainGateway) createDestinationSSHClient() (*gossh.Client, *gossh.Client, error) {
if d.SelectedGateway == nil {
client, err := d.createGatewaySSHClient(d.Destination)
return client, nil, err
}
jumpClient, err := d.createGatewaySSHClient(d.SelectedGateway)
if err != nil {
return nil, nil, err
}
addr := gatewaySSHAddress(d.Destination)
conn, err := jumpClient.Dial("tcp", addr)
if err != nil {
_ = jumpClient.Close()
return nil, nil, err
}
_ = conn.SetDeadline(time.Now().Add(miniTimeout))
clientConn, chans, reqs, err := gossh.NewClientConn(
conn, addr, gatewaySSHConfig(d.Destination),
)
if err != nil {
_ = conn.Close()
_ = jumpClient.Close()
return nil, nil, err
}
_ = conn.SetDeadline(time.Time{})
return gossh.NewClient(clientConn, chans, reqs), jumpClient, nil
}
func (d *DomainGateway) createGatewaySSHClient(gateway *model.Gateway) (*gossh.Client, error) {
return gossh.Dial("tcp", gatewaySSHAddress(gateway), gatewaySSHConfig(gateway))
}

func gatewaySSHConfig(gateway *model.Gateway) *gossh.ClientConfig {
auths := make([]gossh.AuthMethod, 0, 3)
loginAccount := gateway.Account
if loginAccount.IsSSHKey() {
Expand All @@ -110,26 +154,45 @@ func (d *DomainGateway) createGatewaySSHClient(gateway *model.Gateway) (*gossh.C
return []string{loginAccount.Secret}, nil
}))
}
sshConfig := gossh.ClientConfig{
return &gossh.ClientConfig{
User: loginAccount.Username,
Auth: auths,
HostKeyCallback: NewTrustHostKeyCallback(),
Config: createSSHConfig(),
Timeout: miniTimeout,
HostKeyAlgorithms: allHostKeyAlgorithms(),
}
}

func gatewaySSHAddress(gateway *model.Gateway) string {
port := gateway.Protocols.GetProtocolPort("ssh")
addr := net.JoinHostPort(gateway.Address, strconv.Itoa(port))
return gossh.Dial("tcp", addr, &sshConfig)
return net.JoinHostPort(gateway.Address, strconv.Itoa(port))
}

func (d *DomainGateway) Name() string {
if d.Destination != nil {
return d.Destination.Name
}
if d.SelectedGateway != nil {
return d.SelectedGateway.Name
}
return "unknown"
}
func (d *DomainGateway) Stop() {
d.closeOnce()
}

func (d *DomainGateway) closeOnce() {
d.once.Do(func() {
_ = d.ln.Close()
_ = d.sshClient.Close()
if d.ln != nil {
_ = d.ln.Close()
}
if d.sshClient != nil {
_ = d.sshClient.Close()
}
if d.jumpClient != nil {
_ = d.jumpClient.Close()
}
})
}

Expand Down
61 changes: 61 additions & 0 deletions pkg/gateway/domain_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package gateway

import (
"net"
"os"
"strconv"
"testing"
"time"

"github.com/jumpserver-dev/sdk-go/model"
)

func TestDomainGatewaySSHForwardIntegration(t *testing.T) {
host := os.Getenv("LION_SSH_TEST_HOST")
keyFile := os.Getenv("LION_SSH_TEST_KEY_FILE")
destination := os.Getenv("LION_SSH_TEST_DESTINATION")
if host == "" || keyFile == "" || destination == "" {
t.Skip("set LION_SSH_TEST_HOST, LION_SSH_TEST_KEY_FILE and LION_SSH_TEST_DESTINATION")
}
port, err := strconv.Atoi(os.Getenv("LION_SSH_TEST_PORT"))
if err != nil || port == 0 {
port = 22
}
secret, err := os.ReadFile(keyFile)
if err != nil {
t.Fatal(err)
}
sshTarget := func(name, address string, sshPort int) *model.Gateway {
return &model.Gateway{
Name: name,
Address: address,
Protocols: model.Protocols{{Name: "ssh", Port: sshPort}},
Account: model.Account{BaseAccount: model.BaseAccount{
Username: "root",
Secret: string(secret),
SecretType: model.LabelValue{Value: "ssh_key"},
}},
}
}
forwarder := DomainGateway{
DstAddr: destination,
Destination: sshTarget("integration-provider", host, port),
}
if jumpHost := os.Getenv("LION_SSH_TEST_JUMP_HOST"); jumpHost != "" {
jumpPort, err := strconv.Atoi(os.Getenv("LION_SSH_TEST_JUMP_PORT"))
if err != nil || jumpPort == 0 {
jumpPort = 22
}
forwarder.SelectedGateway = sshTarget("integration-gateway", jumpHost, jumpPort)
}
if err := forwarder.Start(); err != nil {
t.Fatal(err)
}
defer forwarder.Stop()

conn, err := net.DialTimeout("tcp", forwarder.GetListenAddr().String(), 5*time.Second)
if err != nil {
t.Fatalf("dial forwarded destination: %v", err)
}
_ = conn.Close()
}
Loading
Loading