Add templating option for RDP files

This commit is contained in:
Bolke de Bruin 2023-05-08 10:45:32 +02:00
parent 769abae3ba
commit cdc497f365
10 changed files with 424 additions and 266 deletions

View file

@ -89,12 +89,11 @@ type SecurityConfig struct {
} }
type ClientConfig struct { type ClientConfig struct {
NetworkAutoDetect int `koanf:"networkautodetect"` Defaults string `koanf:"defaults"`
BandwidthAutoDetect int `koanf:"bandwidthautodetect"` // kept for backwards compatibility
ConnectionType int `koanf:"connectiontype"` UsernameTemplate string `koanf:"usernametemplate"`
UsernameTemplate string `koanf:"usernametemplate"` SplitUserDomain bool `koanf:"splituserdomain"`
SplitUserDomain bool `koanf:"splituserdomain"` DefaultDomain string `koanf:"defaultdomain"`
DefaultDomain string `koanf:"defaultdomain"`
} }
func ToCamel(s string) string { func ToCamel(s string) string {

View file

@ -0,0 +1,83 @@
package parsers
import (
"bufio"
"bytes"
"fmt"
"sort"
"strconv"
"strings"
)
type RDP struct{}
func Parser() *RDP {
return &RDP{}
}
func (p *RDP) Unmarshal(b []byte) (map[string]interface{}, error) {
r := bytes.NewReader(b)
scanner := bufio.NewScanner(r)
mp := make(map[string]interface{})
c := 0
for scanner.Scan() {
c++
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
fields := strings.SplitN(line, ":", 3)
if len(fields) != 3 {
return nil, fmt.Errorf("malformed line %d: %q", c, line)
}
key := strings.TrimSpace(fields[0])
t := strings.TrimSpace(fields[1])
val := strings.TrimSpace(fields[2])
switch t {
case "i":
intValue, err := strconv.Atoi(val)
if err != nil {
return nil, fmt.Errorf("cannot parse integer at line %d: %s", c, line)
}
mp[key] = intValue
case "s":
mp[key] = val
default:
return nil, fmt.Errorf("malformed line %d: %s", c, line)
}
}
return mp, nil
}
func (p *RDP) Marshal(o map[string]interface{}) ([]byte, error) {
var b bytes.Buffer
keys := make([]string, 0, len(o))
for k := range o {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
v := o[key]
switch v.(type) {
case bool:
if v == true {
fmt.Fprintf(&b, "%s:i:1", key)
} else {
fmt.Fprintf(&b, "%s:i:0", key)
}
case int:
fmt.Fprintf(&b, "%s:i:%d", key, v)
case string:
fmt.Fprintf(&b, "%s:s:%s", key, v)
default:
return nil, fmt.Errorf("error marshalling")
}
fmt.Fprint(&b, "\r\n")
}
return b.Bytes(), nil
}

View file

@ -0,0 +1,85 @@
package parsers
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestUnmarshalRDPFile(t *testing.T) {
rdp := Parser()
testCases := []struct {
name string
cfg []byte
expOutput map[string]interface{}
err error
}{
{
name: "empty",
expOutput: map[string]interface{}{},
},
{
name: "string",
cfg: []byte(`username:s:user1`),
expOutput: map[string]interface{}{
"username": "user1",
},
},
{
name: "integer",
cfg: []byte(`session bpp:i:32`),
expOutput: map[string]interface{}{
"session bpp": 32,
},
},
{
name: "multi",
cfg: []byte("compression:i:1\r\nusername:s:user2\r\n"),
expOutput: map[string]interface{}{
"compression": 1,
"username": "user2",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
outMap, err := rdp.Unmarshal(tc.cfg)
assert.Equal(t, tc.err, err)
assert.Equal(t, tc.expOutput, outMap)
})
}
}
func TestRDP_Marshal(t *testing.T) {
testCases := []struct {
name string
input map[string]interface{}
output []byte
err error
}{
{
name: "Empty RDP",
input: map[string]interface{}{},
output: []byte(nil),
},
{
name: "Valid RDP all types",
input: map[string]interface{}{
"compression": 1,
"session bpp": 32,
"username": "user1",
},
output: []byte("compression:i:1\r\nsession bpp:i:32\r\nusername:s:user1\r\n"),
},
}
rdp := Parser()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
out, err := rdp.Marshal(tc.input)
assert.Equal(t, tc.output, out)
assert.Equal(t, tc.err, err)
})
}
}

View file

@ -108,14 +108,12 @@ func main() {
Hosts: conf.Server.Hosts, Hosts: conf.Server.Hosts,
HostSelection: conf.Server.HostSelection, HostSelection: conf.Server.HostSelection,
RdpOpts: web.RdpOpts{ RdpOpts: web.RdpOpts{
UsernameTemplate: conf.Client.UsernameTemplate, UsernameTemplate: conf.Client.UsernameTemplate,
SplitUserDomain: conf.Client.SplitUserDomain, SplitUserDomain: conf.Client.SplitUserDomain,
DefaultDomain: conf.Client.DefaultDomain, DefaultDomain: conf.Client.DefaultDomain,
NetworkAutoDetect: conf.Client.NetworkAutoDetect,
BandwidthAutoDetect: conf.Client.BandwidthAutoDetect,
ConnectionType: conf.Client.ConnectionType,
}, },
GatewayAddress: url, GatewayAddress: url,
TemplateFile: conf.Client.Defaults,
} }
if conf.Caps.TokenAuth { if conf.Caps.TokenAuth {

204
cmd/rdpgw/rdp/rdp.go Normal file
View file

@ -0,0 +1,204 @@
package rdp
import (
"errors"
"fmt"
"github.com/fatih/structs"
"log"
"reflect"
"strconv"
"strings"
)
const (
CRLF = "\r\n"
)
const (
SourceNTLM int = iota
SourceSmartCard
SourceCurrent
SourceBasic
SourceUserSelect
SourceCookie
)
type RdpSettings struct {
GatewayHostname string `rdp:"gatewayhostname"`
FullAddress string `rdp:"full address"`
AlternateFullAddress string `rdp:"alternate full address"`
Username string `rdp:"username"`
Domain string `rdp:"domain"`
GatewayCredentialsSource int `rdp:"gatewaycredentialssource" default:"0"`
GatewayCredentialMethod int `rdp:"gatewayprofileusagemethod" default:"0"`
GatewayUsageMethod int `rdp:"gatewayusagemethod" default:"0"`
GatewayAccessToken string `rdp:"gatewayaccesstoken"`
PromptCredentialsOnce bool `rdp:"promptcredentialonce" default:"true"`
AuthenticationLevel int `rdp:"authentication level" default:"3"`
EnableCredSSPSupport bool `rdp:"enablecredsspsupport" default:"true"`
EnableRdsAasAuth bool `rdp:"enablerdsaadauth" default:"false"`
DisableConnectionSharing bool `rdp:"disableconnectionsharing" default:"false"`
AlternateShell string `rdp:"alternate shell"`
AutoReconnectionEnabled bool `rdp:"autoreconnectionenabled" default:"true"`
BandwidthAutodetect bool `rdp:"bandwidthautodetect" default:"true"`
NetworkAutodetect bool `rdp:"networkautodetect" default:"true"`
Compression bool `rdp:"compression" default:"true"`
VideoPlaybackMode bool `rdp:"videoplaybackmode" default:"true"`
ConnectionType int `rdp:"connection type" default:"2"`
AudioCaptureMode bool `rdp:"audiocapturemode" default:"false"`
EncodeRedirectedVideoCapture bool `rdp:"encode redirected video capture" default:"true"`
RedirectedVideoCaptureEncodingQuality int `rdp:"redirected video capture encoding quality" default:"0"`
AudioMode int `rdp:"audiomode" default:"0"`
CameraStoreRedirect string `rdp:"camerastoredirect" default:"false"`
DeviceStoreRedirect string `rdp:"devicestoredirect" default:"false"`
DriveStoreRedirect string `rdp:"drivestoredirect" default:"false"`
KeyboardHook int `rdp:"keyboardhook" default:"2"`
RedirectClipboard bool `rdp:"redirectclipboard" default:"true"`
RedirectComPorts bool `rdp:"redirectcomports" default:"false"`
RedirectLocation bool `rdp:"redirectlocation" default:"false"`
RedirectPrinters bool `rdp:"redirectprinters" default:"true"`
RedirectSmartcards bool `rdp:"redirectsmartcards" default:"true"`
RedirectWebAuthn bool `rdp:"redirectwebauthn" default:"true"`
UsbDeviceStoRedirect string `rdp:"usbdevicestoredirect"`
UseMultimon bool `rdp:"use multimon" default:"false"`
SelectedMonitors string `rdp:"selectedmonitors"`
MaximizeToCurrentDisplays bool `rdp:"maximizetocurrentdisplays" default:"false"`
SingleMonInWindowedMode bool `rdp:"singlemoninwindowedmode" default:"0"`
ScreenModeId int `rdp:"screen mode id" default:"2"`
SmartSizing bool `rdp:"smart sizing" default:"false"`
DynamicResolution bool `rdp:"dynamic resolution" default:"true"`
DesktopSizeId int `rdp:"desktop size id"`
DesktopHeight int `rdp:"desktopheight"`
DesktopWidth int `rdp:"desktopwidth"`
DesktopScaleFactor int `rdp:"desktopscalefactor"`
BitmapCacheSize int `rdp:"bitmapcachesize" default:"1500"`
RemoteApplicationCmdLine string `rdp:"remoteapplicationcmdline"`
RemoteAppExpandWorkingDir bool `rdp:"remoteapplicationexpandworkingdir" default:"true"`
RemoteApplicationFile string `rdp:"remoteapplicationfile" default:"true"`
RemoteApplicationIcon string `rdp:"remoteapplicationicon"`
RemoteApplicationMode bool `rdp:"remoteapplicationmode" default:"true"`
RemoteApplicationName string `rdp:"remoteapplicationname"`
RemoteApplicationProgram string `rdp:"remoteapplicationprogram"`
}
type RdpBuilder struct {
Settings RdpSettings
}
func NewRdp() *RdpBuilder {
c := RdpSettings{}
initStruct(&c)
return &RdpBuilder{
Settings: c,
}
}
func (rb *RdpBuilder) String() string {
var sb strings.Builder
addStructToString(rb.Settings, &sb)
return sb.String()
}
func addStructToString(st interface{}, sb *strings.Builder) {
s := structs.New(st)
for _, f := range s.Fields() {
if isZero(f) {
continue
}
sb.WriteString(f.Tag("rdp"))
sb.WriteString(":")
switch f.Kind() {
case reflect.String:
sb.WriteString("s:")
sb.WriteString(f.Value().(string))
case reflect.Int:
sb.WriteString("i:")
fmt.Fprintf(sb, "%d", f.Value())
case reflect.Bool:
sb.WriteString("i:")
if f.Value().(bool) {
sb.WriteString("1")
} else {
sb.WriteString("0")
}
}
sb.WriteString(CRLF)
}
}
func isZero(f *structs.Field) bool {
t := f.Tag("default")
if t == "" {
return f.IsZero()
}
switch f.Kind() {
case reflect.String:
if f.Value().(string) != t {
return false
}
return true
case reflect.Int:
i, err := strconv.Atoi(t)
if err != nil {
log.Fatalf("runtime error: default %s is not an integer", t)
}
if f.Value().(int) != i {
return false
}
return true
case reflect.Bool:
b := false
if t == "true" || t == "1" {
b = true
}
if f.Value().(bool) != b {
return false
}
return true
}
return f.IsZero()
}
func initStruct(st interface{}) {
s := structs.New(st)
for _, f := range s.Fields() {
t := f.Tag("default")
if t == "" {
continue
}
err := setVariable(f, t)
if err != nil {
log.Fatalf("cannot init rdp struct: %s", err)
}
}
}
func setVariable(f *structs.Field, v string) error {
switch f.Kind() {
case reflect.String:
return f.Set(v)
case reflect.Int:
i, err := strconv.Atoi(v)
if err != nil {
return err
}
return f.Set(i)
case reflect.Bool:
b := false
if v == "true" || v == "1" {
b = true
}
return f.Set(b)
default:
return errors.New("invalid field type")
}
}

View file

@ -1,4 +1,4 @@
package web package rdp
import ( import (
"log" "log"
@ -12,18 +12,18 @@ const (
func TestRdpBuilder(t *testing.T) { func TestRdpBuilder(t *testing.T) {
builder := NewRdp() builder := NewRdp()
builder.Connection.GatewayHostname = "my.yahoo.com" builder.Settings.GatewayHostname = "my.yahoo.com"
builder.Session.AutoReconnectionEnabled = true builder.Settings.AutoReconnectionEnabled = true
builder.Display.SmartSizing = true builder.Settings.SmartSizing = true
s := builder.String() s := builder.String()
if !strings.Contains(s, "gatewayhostname:s:"+GatewayHostName+crlf) { if !strings.Contains(s, "gatewayhostname:s:"+GatewayHostName+CRLF) {
t.Fatalf("%s does not contain `gatewayhostname:s:%s", s, GatewayHostName) t.Fatalf("%s does not contain `gatewayhostname:s:%s", s, GatewayHostName)
} }
if strings.Contains(s, "autoreconnectionenabled") { if strings.Contains(s, "autoreconnectionenabled") {
t.Fatalf("autoreconnectionenabled is in %s, but is default value", s) t.Fatalf("autoreconnectionenabled is in %s, but is default value", s)
} }
if !strings.Contains(s, "smart sizing:i:1"+crlf) { if !strings.Contains(s, "smart sizing:i:1"+CRLF) {
t.Fatalf("%s does not contain smart sizing:i:1", s) t.Fatalf("%s does not contain smart sizing:i:1", s)
} }
@ -31,7 +31,7 @@ func TestRdpBuilder(t *testing.T) {
} }
func TestInitStruct(t *testing.T) { func TestInitStruct(t *testing.T) {
conn := RdpConnection{} conn := RdpSettings{}
initStruct(&conn) initStruct(&conn)
if conn.PromptCredentialsOnce != true { if conn.PromptCredentialsOnce != true {

View file

@ -1,229 +0,0 @@
package web
import (
"fmt"
"github.com/fatih/structs"
"log"
"reflect"
"strconv"
"strings"
)
const (
crlf = "\r\n"
)
const (
SourceNTLM int = iota
SourceSmartCard
SourceCurrent
SourceBasic
SourceUserSelect
SourceCookie
)
type RdpConnection struct {
GatewayHostname string `rdp:"gatewayhostname"`
FullAddress string `rdp:"full address"`
AlternateFullAddress string `rdp:"alternate full address"`
Username string `rdp:"username"`
Domain string `rdp:"domain"`
GatewayCredentialsSource int `rdp:"gatewaycredentialssource" default:"0"`
GatewayCredentialMethod int `rdp:"gatewayprofileusagemethod" default:"0"`
GatewayUsageMethod int `rdp:"gatewayusagemethod" default:"0"`
GatewayAccessToken string `rdp:"gatewayaccesstoken"`
PromptCredentialsOnce bool `rdp:"promptcredentialonce" default:"true"`
AuthenticationLevel int `rdp:"authentication level" default:"3"`
EnableCredSSPSupport bool `rdp:"enablecredsspsupport" default:"true"`
EnableRdsAasAuth bool `rdp:"enablerdsaadauth" default:"false"`
DisableConnectionSharing bool `rdp:"disableconnectionsharing" default:"false"`
AlternateShell string `rdp:"alternate shell"`
}
type RdpSession struct {
AutoReconnectionEnabled bool `rdp:"autoreconnectionenabled" default:"true"`
BandwidthAutodetect bool `rdp:"bandwidthautodetect" default:"true"`
NetworkAutodetect bool `rdp:"networkautodetect" default:"true"`
Compression bool `rdp:"compression" default:"true"`
VideoPlaybackMode bool `rdp:"videoplaybackmode" default:"true"`
ConnectionType int `rdp:"connection type" default:"2"`
}
type RdpDeviceRedirect struct {
AudioCaptureMode bool `rdp:"audiocapturemode" default:"false"`
EncodeRedirectedVideoCapture bool `rdp:"encode redirected video capture" default:"true"`
RedirectedVideoCaptureEncodingQuality int `rdp:"redirected video capture encoding quality" default:"0"`
AudioMode int `rdp:"audiomode" default:"0"`
CameraStoreRedirect string `rdp:"camerastoredirect" default:"false"`
DeviceStoreRedirect string `rdp:"devicestoredirect" default:"false"`
DriveStoreRedirect string `rdp:"drivestoredirect" default:"false"`
KeyboardHook int `rdp:"keyboardhook" default:"2"`
RedirectClipboard bool `rdp:"redirectclipboard" default:"true"`
RedirectComPorts bool `rdp:"redirectcomports" default:"false"`
RedirectLocation bool `rdp:"redirectlocation" default:"false"`
RedirectPrinters bool `rdp:"redirectprinters" default:"true"`
RedirectSmartcards bool `rdp:"redirectsmartcards" default:"true"`
RedirectWebAuthn bool `rdp:"redirectwebauthn" default:"true"`
UsbDeviceStoRedirect string `rdp:"usbdevicestoredirect"`
}
type RdpDisplay struct {
UseMultimon bool `rdp:"use multimon" default:"false"`
SelectedMonitors string `rdp:"selectedmonitors"`
MaximizeToCurrentDisplays bool `rdp:"maximizetocurrentdisplays" default:"false"`
SingleMonInWindowedMode bool `rdp:"singlemoninwindowedmode" default:"0"`
ScreenModeId int `rdp:"screen mode id" default:"2"`
SmartSizing bool `rdp:"smart sizing" default:"false"`
DynamicResolution bool `rdp:"dynamic resolution" default:"true"`
DesktopSizeId int `rdp:"desktop size id"`
DesktopHeight int `rdp:"desktopheight"`
DesktopWidth int `rdp:"desktopwidth"`
DesktopScaleFactor int `rdp:"desktopscalefactor"`
BitmapCacheSize int `rdp:"bitmapcachesize" default:"1500"`
}
type RdpRemoteApp struct {
RemoteApplicationCmdLine string `rdp:"remoteapplicationcmdline"`
RemoteAppExpandWorkingDir bool `rdp:"remoteapplicationexpandworkingdir" default:"true"`
RemoteApplicationFile string `rdp:"remoteapplicationfile" default:"true"`
RemoteApplicationIcon string `rdp:"remoteapplicationicon"`
RemoteApplicationMode bool `rdp:"remoteapplicationmode" default:"true"`
RemoteApplicationName string `rdp:"remoteapplicationname"`
RemoteApplicationProgram string `rdp:"remoteapplicationprogram"`
}
type RdpBuilder struct {
Connection RdpConnection
Session RdpSession
DeviceRedirect RdpDeviceRedirect
Display RdpDisplay
RemoteApp RdpRemoteApp
}
func NewRdp() *RdpBuilder {
c := RdpConnection{}
s := RdpSession{}
dr := RdpDeviceRedirect{}
disp := RdpDisplay{}
ra := RdpRemoteApp{}
initStruct(&c)
initStruct(&s)
initStruct(&dr)
initStruct(&disp)
initStruct(&ra)
return &RdpBuilder{
Connection: c,
Session: s,
DeviceRedirect: dr,
Display: disp,
RemoteApp: ra,
}
}
func (rb *RdpBuilder) String() string {
var sb strings.Builder
addStructToString(rb.Connection, &sb)
addStructToString(rb.Session, &sb)
addStructToString(rb.DeviceRedirect, &sb)
addStructToString(rb.Display, &sb)
addStructToString(rb.RemoteApp, &sb)
return sb.String()
}
func addStructToString(st interface{}, sb *strings.Builder) {
s := structs.New(st)
for _, f := range s.Fields() {
if isZero(f) {
continue
}
sb.WriteString(f.Tag("rdp"))
sb.WriteString(":")
switch f.Kind() {
case reflect.String:
sb.WriteString("s:")
sb.WriteString(f.Value().(string))
case reflect.Int:
sb.WriteString("i:")
fmt.Fprintf(sb, "%d", f.Value())
case reflect.Bool:
sb.WriteString("i:")
if f.Value().(bool) {
sb.WriteString("1")
} else {
sb.WriteString("0")
}
}
sb.WriteString(crlf)
}
}
func isZero(f *structs.Field) bool {
t := f.Tag("default")
if t == "" {
return f.IsZero()
}
switch f.Kind() {
case reflect.String:
if f.Value().(string) != t {
return false
}
return true
case reflect.Int:
i, err := strconv.Atoi(t)
if err != nil {
log.Fatalf("runtime error: default %s is not an integer", t)
}
if f.Value().(int) != i {
return false
}
return true
case reflect.Bool:
b := false
if t == "true" || t == "1" {
b = true
}
if f.Value().(bool) != b {
return false
}
return true
}
return f.IsZero()
}
func initStruct(st interface{}) {
s := structs.New(st)
for _, f := range s.Fields() {
t := f.Tag("default")
if t == "" {
continue
}
switch f.Kind() {
case reflect.String:
f.Set(t)
case reflect.Int:
i, err := strconv.Atoi(t)
if err != nil {
log.Fatalf("runtime error: default %s is not an integer", t)
}
f.Set(i)
case reflect.Bool:
b := false
if t == "true" || t == "1" {
b = true
}
err := f.Set(b)
if err != nil {
log.Fatalf("Cannot set bool field")
}
}
}
}

View file

@ -5,7 +5,12 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/config/parsers"
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity"
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/rdp"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"hash/maphash"
"log" "log"
"math/rand" "math/rand"
"net/http" "net/http"
@ -28,6 +33,7 @@ type Config struct {
HostSelection string HostSelection string
GatewayAddress *url.URL GatewayAddress *url.URL
RdpOpts RdpOpts RdpOpts RdpOpts
TemplateFile string
} }
type RdpOpts struct { type RdpOpts struct {
@ -49,12 +55,14 @@ type Handler struct {
hosts []string hosts []string
hostSelection string hostSelection string
rdpOpts RdpOpts rdpOpts RdpOpts
rdpDefaults string
} }
func (c *Config) NewHandler() *Handler { func (c *Config) NewHandler() *Handler {
if len(c.Hosts) < 1 { if len(c.Hosts) < 1 {
log.Fatal("Not enough hosts to connect to specified") log.Fatal("Not enough hosts to connect to specified")
} }
return &Handler{ return &Handler{
paaTokenGenerator: c.PAATokenGenerator, paaTokenGenerator: c.PAATokenGenerator,
enableUserToken: c.EnableUserToken, enableUserToken: c.EnableUserToken,
@ -65,12 +73,13 @@ func (c *Config) NewHandler() *Handler {
hosts: c.Hosts, hosts: c.Hosts,
hostSelection: c.HostSelection, hostSelection: c.HostSelection,
rdpOpts: c.RdpOpts, rdpOpts: c.RdpOpts,
rdpDefaults: c.TemplateFile,
} }
} }
func (h *Handler) selectRandomHost() string { func (h *Handler) selectRandomHost() string {
rand.Seed(time.Now().Unix()) r := rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64())))
host := h.hosts[rand.Intn(len(h.hosts))] host := h.hosts[r.Intn(len(h.hosts))]
return host return host
} }
@ -188,20 +197,25 @@ func (h *Handler) HandleDownload(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Disposition", "attachment; filename="+fn) w.Header().Set("Content-Disposition", "attachment; filename="+fn)
w.Header().Set("Content-Type", "application/x-rdp") w.Header().Set("Content-Type", "application/x-rdp")
rdp := NewRdp() d := rdp.NewRdp()
rdp.Connection.Username = render
rdp.Connection.Domain = domain
rdp.Connection.FullAddress = host
rdp.Connection.GatewayHostname = h.gatewayAddress.Host
rdp.Connection.GatewayCredentialsSource = SourceCookie
rdp.Connection.GatewayAccessToken = token
rdp.Connection.GatewayCredentialMethod = 1
rdp.Connection.GatewayUsageMethod = 1
rdp.Session.NetworkAutodetect = opts.NetworkAutoDetect != 0
rdp.Session.BandwidthAutodetect = opts.BandwidthAutoDetect != 0
rdp.Session.ConnectionType = opts.ConnectionType
rdp.Display.SmartSizing = true
rdp.Display.BitmapCacheSize = 32000
http.ServeContent(w, r, fn, time.Now(), strings.NewReader(rdp.String())) if h.rdpDefaults != "" {
var k = koanf.New(".")
if err := k.Load(file.Provider(h.rdpDefaults), parsers.Parser()); err != nil {
log.Fatalf("cannot load rdp template file from %s", h.rdpDefaults)
}
tag := koanf.UnmarshalConf{Tag: "rdp"}
k.UnmarshalWithConf("", &d.Settings, tag)
}
d.Settings.Username = render
d.Settings.Domain = domain
d.Settings.FullAddress = host
d.Settings.GatewayHostname = h.gatewayAddress.Host
d.Settings.GatewayCredentialsSource = rdp.SourceCookie
d.Settings.GatewayAccessToken = token
d.Settings.GatewayCredentialMethod = 1
d.Settings.GatewayUsageMethod = 1
http.ServeContent(w, r, fn, time.Now(), strings.NewReader(d.String()))
} }

View file

@ -3,6 +3,7 @@ package web
import ( import (
"context" "context"
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity"
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/rdp"
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/security" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/security"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -149,7 +150,7 @@ func TestHandler_HandleDownload(t *testing.T) {
t.Errorf("content disposition is nil") t.Errorf("content disposition is nil")
} }
data := rdpToMap(strings.Split(rr.Body.String(), crlf)) data := rdpToMap(strings.Split(rr.Body.String(), rdp.CRLF))
if data["username"] != testuser { if data["username"] != testuser {
t.Errorf("username key in rdp does not match: got %v want %v", data["username"], testuser) t.Errorf("username key in rdp does not match: got %v want %v", data["username"], testuser)
} }

3
go.mod
View file

@ -21,6 +21,7 @@ require (
github.com/msteinert/pam v1.0.0 github.com/msteinert/pam v1.0.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/prometheus/client_golang v1.15.0 github.com/prometheus/client_golang v1.15.0
github.com/stretchr/testify v1.8.1
github.com/thought-machine/go-flags v1.6.2 github.com/thought-machine/go-flags v1.6.2
golang.org/x/crypto v0.8.0 golang.org/x/crypto v0.8.0
golang.org/x/oauth2 v0.7.0 golang.org/x/oauth2 v0.7.0
@ -31,6 +32,7 @@ require (
require ( require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect
@ -43,6 +45,7 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect