Skip to content

Commit

Permalink
RDP execution
Browse files Browse the repository at this point in the history
  • Loading branch information
breathbath committed Apr 8, 2021
1 parent 972f14b commit 0e99c27
Show file tree
Hide file tree
Showing 12 changed files with 442 additions and 7 deletions.
44 changes: 44 additions & 0 deletions cmd/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"os/signal"
"syscall"

"github.com/cloudradar-monitoring/rportcli/internal/pkg/rdp"

"github.com/cloudradar-monitoring/rportcli/internal/pkg/cache"
"github.com/cloudradar-monitoring/rportcli/internal/pkg/client"

Expand Down Expand Up @@ -174,6 +176,39 @@ Any parameter passed are append to the ssh command. i.e. -b "-l root"`,
ShortName: "b",
Type: config.StringRequirementType,
},
{
Field: controllers.LaunchRDP,
Description: `Start the default RDP client after the tunnel is established.
Optionally pass the rdp-width and rdp-height params of the session.`,
ShortName: "d",
Type: config.BoolRequirementType,
Default: "0",
},
{
Field: controllers.RDPWidth,
Description: `RDP window width, 1024 is the default`,
ShortName: "w",
Type: config.StringRequirementType,
Default: "1024",
},
{
Field: controllers.RDPHeight,
Description: `RDP window height, 768 is the default`,
ShortName: "i",
Type: config.StringRequirementType,
Default: "768",
},
{
Field: controllers.RDPUser,
Description: `[required] username for a RDP session`,
ShortName: "u",
Type: config.StringRequirementType,
Validate: config.RequiredValidate,
Help: "Enter a RDP user name",
IsEnabled: func(providedParams *options.ParameterBag) bool {
return IsRDPUserRequired
},
},
}
}

Expand Down Expand Up @@ -220,12 +255,21 @@ func createTunnelController(params *options.ParameterBag) *controllers.TunnelCon
Cache: &cache.ClientsCache{},
}

rdpExecutor := &rdp.Executor{
CommandProvider: rdp.CommandProvider,
StdOut: os.Stdout,
Stdin: os.Stdin,
StdErr: os.Stderr,
}

return &controllers.TunnelController{
Rport: rportAPI,
TunnelRenderer: tr,
IPProvider: rportAPI,
ClientSearch: clientSearch,
SSHFunc: utils.RunSSH,
RDPWriter: rdp.WriteRdpFile,
RDPExecutor: rdpExecutor,
}
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/tunnelRDP.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package cmd

var IsRDPUserRequired = false
4 changes: 4 additions & 0 deletions cmd/tunnelRDP_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// +build linux
package cmd

var IsRDPUserRequired = true
62 changes: 55 additions & 7 deletions internal/pkg/controllers/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ import (
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"

io2 "github.com/breathbath/go_utils/v2/pkg/io"
"github.com/cloudradar-monitoring/rportcli/internal/pkg/rdp"

"github.com/cloudradar-monitoring/rportcli/internal/pkg/config"

"github.com/cloudradar-monitoring/rportcli/internal/pkg/output"
Expand All @@ -30,6 +37,10 @@ const (
ACL = "acl"
CheckPort = "checkp"
LaunchSSH = "launch-ssh"
LaunchRDP = "launch-rdp"
RDPWidth = "rdp-width"
RDPHeight = "rdp-height"
RDPUser = "rdp-user"
DefaultACL = "<<YOU CURRENT PUBLIC IP>>"
)

Expand All @@ -49,6 +60,8 @@ type TunnelController struct {
IPProvider IPProvider
ClientSearch ClientSearch
SSHFunc func(sshParams []string) error
RDPWriter func(fi rdp.FileInput, w io.Writer) error
RDPExecutor *rdp.Executor
}

func (tc *TunnelController) Tunnels(ctx context.Context) error {
Expand Down Expand Up @@ -159,12 +172,15 @@ func (tc *TunnelController) Create(ctx context.Context, params *options.Paramete
return err
}

shouldLaunchSSH := params.ReadString(LaunchSSH, "")
if shouldLaunchSSH == "" {
return nil
if params.ReadString(LaunchSSH, "") != "" {
return tc.startSSHFlow(ctx, tunnelCreated, params, clientID)
}

if params.ReadString(LaunchRDP, "") != "" {
return tc.startRDPFlow(tunnelCreated, params)
}

return tc.startSSHFlow(ctx, tunnelCreated, params, clientID)
return nil
}

func (tc *TunnelController) startSSHFlow(
Expand All @@ -175,7 +191,7 @@ func (tc *TunnelController) startSSHFlow(
) error {
sshParamsFlat := params.ReadString(LaunchSSH, "")
logrus.Debugf("ssh arguments are provided: '%s', will start an ssh session", sshParamsFlat)
port, host, err := tc.getSSHPortAndHost(tunnelCreated, params)
port, host, err := tc.extractPortAndHost(tunnelCreated, params)
if err != nil {
return fmt.Errorf("failed to parse rport URL '%s': %v", params.ReadString(config.ServerURL, ""), err)
}
Expand Down Expand Up @@ -212,7 +228,7 @@ func (tc *TunnelController) startSSHFlow(
}

func (tc *TunnelController) generateUsage(tunnelCreated *models.TunnelCreated, params *options.ParameterBag) string {
port, host, err := tc.getSSHPortAndHost(tunnelCreated, params)
port, host, err := tc.extractPortAndHost(tunnelCreated, params)
if err != nil {
logrus.Error(err)
return ""
Expand All @@ -229,7 +245,7 @@ func (tc *TunnelController) generateUsage(tunnelCreated *models.TunnelCreated, p
return fmt.Sprintf("ssh %s -l ${USER}", host)
}

func (tc *TunnelController) getSSHPortAndHost(
func (tc *TunnelController) extractPortAndHost(
tunnelCreated *models.TunnelCreated,
params *options.ParameterBag,
) (port, host string, err error) {
Expand Down Expand Up @@ -268,3 +284,35 @@ func (tc *TunnelController) findClientID(ctx context.Context, clientName string,
}
return clients[0].ID, nil
}

func (tc *TunnelController) startRDPFlow(
tunnelCreated *models.TunnelCreated,
params *options.ParameterBag,
) error {
port, host, err := tc.extractPortAndHost(tunnelCreated, params)
if err != nil {
return err
}

rdpFileInput := rdp.FileInput{
Address: fmt.Sprintf("%s:%s", host, port),
ScreenHeight: params.ReadInt(RDPHeight, 0),
ScreenWidth: params.ReadInt(RDPWidth, 0),
UserName: params.ReadString(RDPUser, ""),
}
file, err := ioutil.TempFile("", "rport-*.rdp")
if err != nil {
return err
}
defer io2.CloseResourceSecure("temp file", file)

logrus.Debugf("will write an rdp file %s", file.Name())
err = tc.RDPWriter(rdpFileInput, file)
if err != nil {
return err
}

rdpFileLocation := filepath.Join(os.TempDir(), file.Name())
logrus.Debugf("written rdp file to %s", rdpFileLocation)
return tc.RDPExecutor.StartRdp(rdpFileLocation)
}
78 changes: 78 additions & 0 deletions internal/pkg/controllers/tunnel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"net/http/httptest"
"testing"

"github.com/cloudradar-monitoring/rportcli/internal/pkg/rdp"

options "github.com/breathbath/go_utils/v2/pkg/config"

"github.com/cloudradar-monitoring/rportcli/internal/pkg/output"
Expand Down Expand Up @@ -654,3 +656,79 @@ func TestTunnelCreateWithSSHFailure(t *testing.T) {
err := tController.Create(context.Background(), params)
assert.EqualError(t, err, "ssh failure")
}

func TestTunnelCreateWithRDP(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
jsonEnc := json.NewEncoder(rw)
e := jsonEnc.Encode(api.TunnelCreatedResponse{Data: &models.TunnelCreated{
ID: "777",
Lhost: "lohost77",
ClientID: "1314",
Lport: "3344",
Scheme: "ssh",
}})
assert.NoError(t, e)
}))
defer srv.Close()

apiAuth := &utils.StorageBasicAuth{
AuthProvider: func() (login, pass string, err error) { return "dfasf", "34123", nil },
}

renderBuf := bytes.Buffer{}
cmdOutput := bytes.Buffer{}

cl := api.New(srv.URL, apiAuth)

isRDPCalled := false
tController := TunnelController{
Rport: cl,
TunnelRenderer: &TunnelRendererMock{Writer: &renderBuf},
IPProvider: IPProviderMock{
IP: "3.4.5.166",
},
ClientSearch: &ClientSearchMock{clientsToGive: []models.Client{}},
RDPWriter: func(fi rdp.FileInput, w io.Writer) error {
isRDPCalled = true
assert.Equal(
t,
rdp.FileInput{
Address: "rport-url123.com:3344",
ScreenHeight: 990,
ScreenWidth: 1090,
UserName: "Administrator",
},
fi,
)
return nil
},
RDPExecutor: &rdp.Executor{
CommandProvider: func(filePath string) (cmd string, args []string) {
assert.Contains(t, filePath, ".rdp")
return "echo", []string{"rdp executed"}
},
StdOut: &cmdOutput,
},
}

params := config.FromValues(map[string]string{
ClientID: "1315",
Local: "lohost88:3304",
Scheme: "rdp",
config.ServerURL: "http://rport-url123.com",
LaunchRDP: "1",
RDPUser: "Administrator",
RDPWidth: "1090",
RDPHeight: "990",
})
err := tController.Create(context.Background(), params)
assert.NoError(t, err)
assert.Equal(
t,
`rdp executed
`,
cmdOutput.String(),
)

assert.True(t, isRDPCalled)
}
33 changes: 33 additions & 0 deletions internal/pkg/rdp/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package rdp

import (
"io"
"os/exec"

"github.com/sirupsen/logrus"
)

type Executor struct {
CommandProvider func(filePath string) (cmd string, args []string)
StdOut io.Writer
Stdin io.Reader
StdErr io.Writer
}

func (re *Executor) StartRdp(filePath string) error {
rdpCmd, args := re.CommandProvider(filePath)
c := exec.Command(rdpCmd, args...)

c.Stdout = re.StdOut
c.Stdin = re.Stdin
c.Stderr = re.StdErr

err := c.Run()
logrus.Debugf("will run %s", c.String())
if err != nil {
return err
}
logrus.Debugf("finished run %s", c.String())

return nil
}
7 changes: 7 additions & 0 deletions internal/pkg/rdp/exec_nix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// +build linux

package rdp

func CommandProvider(filePath string) (cmd string, args []string) {
return "xfreerdp", []string{filePath}
}
7 changes: 7 additions & 0 deletions internal/pkg/rdp/exec_osx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// +build darwin

package rdp

func CommandProvider(filePath string) (cmd string, args []string) {
return "open", []string{filePath}
}
24 changes: 24 additions & 0 deletions internal/pkg/rdp/exec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package rdp

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
)

func TestExecutor(t *testing.T) {
stdOut := &bytes.Buffer{}
const filePath = "file123"
e := &Executor{
CommandProvider: func(fp string) (cmd string, args []string) {
assert.Equal(t, filePath, fp)
return "echo", []string{"123"}
},
StdOut: stdOut,
}

err := e.StartRdp(filePath)
assert.NoError(t, err)
assert.Equal(t, "123\n", stdOut.String())
}
7 changes: 7 additions & 0 deletions internal/pkg/rdp/exec_win.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// +build windows

package rdp

func CommandProvider(filePath string) (cmd string, args []string) {
return "start", []string{filePath}
}
Loading

0 comments on commit 0e99c27

Please sign in to comment.