Skip to content

Commit

Permalink
Finalize rdp templating
Browse files Browse the repository at this point in the history
  • Loading branch information
bolkedebruin committed May 15, 2023
1 parent cdc497f commit 6b32631
Show file tree
Hide file tree
Showing 13 changed files with 128 additions and 44 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ install: build

.PHONY: mod
mod:
go mod tidy -compat=1.19
go mod tidy -compat=1.20

# ------------------------------------------------------------------------------
# test
Expand Down
15 changes: 15 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Upgrading from 1.X to 2.0

In 2.0 the options for configuring client side RDP settings have been removed in favor of template file.
The template file is a RDP file that is used as a template for the connection. The template file is parsed
and a few settings are replaced to ensure the client can connect to the server and the correct domain is used.

The format of the template file is as follows:

```
# <setting>:<type i or s>:<value>
domain:s:testdomain
connection type:i:2
```

The filename is set under `client > defaults`.
1 change: 0 additions & 1 deletion cmd/rdpgw/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ type ClientConfig struct {
// kept for backwards compatibility
UsernameTemplate string `koanf:"usernametemplate"`
SplitUserDomain bool `koanf:"splituserdomain"`
DefaultDomain string `koanf:"defaultdomain"`
}

func ToCamel(s string) string {
Expand Down
1 change: 0 additions & 1 deletion cmd/rdpgw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ func main() {
RdpOpts: web.RdpOpts{
UsernameTemplate: conf.Client.UsernameTemplate,
SplitUserDomain: conf.Client.SplitUserDomain,
DefaultDomain: conf.Client.DefaultDomain,
},
GatewayAddress: url,
TemplateFile: conf.Client.Defaults,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package parsers
package rdp

import (
"bufio"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package parsers
package rdp

import (
"github.com/stretchr/testify/assert"
Expand Down
28 changes: 24 additions & 4 deletions cmd/rdpgw/rdp/rdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package rdp
import (
"errors"
"fmt"
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/rdp/koanf/parsers/rdp"
"github.com/fatih/structs"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"log"
"reflect"
"strconv"
Expand Down Expand Up @@ -81,21 +84,38 @@ type RdpSettings struct {
RemoteApplicationProgram string `rdp:"remoteapplicationprogram"`
}

type RdpBuilder struct {
type Builder struct {
Settings RdpSettings
}

func NewRdp() *RdpBuilder {
func NewBuilder() *Builder {
c := RdpSettings{}

initStruct(&c)

return &RdpBuilder{
return &Builder{
Settings: c,
}
}

func (rb *RdpBuilder) String() string {
func NewBuilderFromFile(filename string) (*Builder, error) {
c := RdpSettings{}
initStruct(&c)

var k = koanf.New(".")
if err := k.Load(file.Provider(filename), rdp.Parser()); err != nil {
return nil, err
}
t := koanf.UnmarshalConf{Tag: "rdp"}
if err := k.UnmarshalWithConf("", &c, t); err != nil {
return nil, err
}
return &Builder{
Settings: c,
}, nil
}

func (rb *Builder) String() string {
var sb strings.Builder

addStructToString(rb.Settings, &sb)
Expand Down
2 changes: 1 addition & 1 deletion cmd/rdpgw/rdp/rdp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const (
)

func TestRdpBuilder(t *testing.T) {
builder := NewRdp()
builder := NewBuilder()
builder.Settings.GatewayHostname = "my.yahoo.com"
builder.Settings.AutoReconnectionEnabled = true
builder.Settings.SmartSizing = true
Expand Down
14 changes: 7 additions & 7 deletions cmd/rdpgw/transport/legacy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,42 @@ package transport

import (
"bufio"
"crypto/rand"
"errors"
"io"
"math/rand"
"net"
"net/http"
"net/http/httputil"
"time"
)

const (
crlf = "\r\n"
crlf = "\r\n"
HttpOK = "HTTP/1.1 200 OK\r\n"
)

type LegacyPKT struct {
Conn net.Conn
Conn net.Conn
ChunkedReader io.Reader
Writer *bufio.Writer
Writer *bufio.Writer
}

func NewLegacy(w http.ResponseWriter) (*LegacyPKT, error) {
hj, ok := w.(http.Hijacker)
if ok {
conn, rw, err := hj.Hijack()
l := &LegacyPKT{
Conn: conn,
Conn: conn,
ChunkedReader: httputil.NewChunkedReader(rw.Reader),
Writer: rw.Writer,
Writer: rw.Writer,
}
return l, err
}

return nil, errors.New("cannot hijack connection")
}

func (t *LegacyPKT) ReadPacket() (n int, p []byte, err error){
func (t *LegacyPKT) ReadPacket() (n int, p []byte, err error) {
buf := make([]byte, 4096) // bufio.defaultBufSize
n, err = t.ChunkedReader.Read(buf)
p = make([]byte, n)
Expand Down
8 changes: 6 additions & 2 deletions cmd/rdpgw/web/oidc.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package web

import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/patrickmn/go-cache"
"golang.org/x/oauth2"
"math/rand"
"net/http"
"time"
)
Expand Down Expand Up @@ -116,7 +116,11 @@ func (h *OIDC) Authenticated(next http.Handler) http.Handler {

if !id.Authenticated() {
seed := make([]byte, 16)
rand.Read(seed)
_, err := rand.Read(seed)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
state := hex.EncodeToString(seed)
h.stateStore.Set(state, r.RequestURI, cache.DefaultExpiration)
http.Redirect(w, r, h.oAuth2Config.AuthCodeURL(state), http.StatusFound)
Expand Down
48 changes: 26 additions & 22 deletions cmd/rdpgw/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ package web

import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/config/parsers"
"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"
"math/rand"
rnd "math/rand"
"net/http"
"net/url"
"strings"
Expand All @@ -37,12 +35,8 @@ type Config struct {
}

type RdpOpts struct {
UsernameTemplate string
SplitUserDomain bool
DefaultDomain string
NetworkAutoDetect int
BandwidthAutoDetect int
ConnectionType int
UsernameTemplate string
SplitUserDomain bool
}

type Handler struct {
Expand Down Expand Up @@ -78,7 +72,7 @@ func (c *Config) NewHandler() *Handler {
}

func (h *Handler) selectRandomHost() string {
r := rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64())))
r := rnd.New(rnd.NewSource(int64(new(maphash.Hash).Sum64())))
host := h.hosts[r.Intn(len(h.hosts))]
return host
}
Expand Down Expand Up @@ -154,7 +148,7 @@ func (h *Handler) HandleDownload(w http.ResponseWriter, r *http.Request) {

// split the username into user and domain
var user = id.UserName()
var domain = opts.DefaultDomain
var domain = ""
if opts.SplitUserDomain {
creds := strings.SplitN(id.UserName(), "@", 2)
user = creds[0]
Expand All @@ -178,38 +172,48 @@ func (h *Handler) HandleDownload(w http.ResponseWriter, r *http.Request) {
if err != nil {
log.Printf("Cannot generate PAA token for user %s due to %s", user, err)
http.Error(w, errors.New("unable to generate gateway credentials").Error(), http.StatusInternalServerError)
return
}

if h.enableUserToken {
userToken, err := h.userTokenGenerator(ctx, user)
if err != nil {
log.Printf("Cannot generate token for user %s due to %s", user, err)
http.Error(w, errors.New("unable to generate gateway credentials").Error(), http.StatusInternalServerError)
return
}
render = strings.Replace(render, "{{ token }}", userToken, 1)
}

// authenticated
seed := make([]byte, 16)
rand.Read(seed)
_, err = rand.Read(seed)
if err != nil {
log.Printf("Cannot generate random seed due to %s", err)
http.Error(w, errors.New("unable to generate random sequence").Error(), http.StatusInternalServerError)
return
}
fn := hex.EncodeToString(seed) + ".rdp"

w.Header().Set("Content-Disposition", "attachment; filename="+fn)
w.Header().Set("Content-Type", "application/x-rdp")

d := rdp.NewRdp()

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)
var d *rdp.Builder
if h.rdpDefaults == "" {
d = rdp.NewBuilder()
} else {
d, err = rdp.NewBuilderFromFile(h.rdpDefaults)
if err != nil {
log.Printf("Cannot load RDP template file %s due to %s", h.rdpDefaults, err)
http.Error(w, errors.New("unable to load RDP template").Error(), http.StatusInternalServerError)
return
}
tag := koanf.UnmarshalConf{Tag: "rdp"}
k.UnmarshalWithConf("", &d.Settings, tag)
}

d.Settings.Username = render
d.Settings.Domain = domain
if domain != "" {
d.Settings.Domain = domain
}
d.Settings.FullAddress = host
d.Settings.GatewayHostname = h.gatewayAddress.Host
d.Settings.GatewayCredentialsSource = rdp.SourceCookie
Expand Down
46 changes: 46 additions & 0 deletions cmd/rdpgw/web/web_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
)
Expand Down Expand Up @@ -171,6 +172,51 @@ func TestHandler_HandleDownload(t *testing.T) {

}

func TestHandler_HandleDownloadWithRdpTemplate(t *testing.T) {
f, err := os.CreateTemp("", "rdp")
if err != nil {
t.Fatal(err)
}
defer os.Remove(f.Name())

err = os.WriteFile(f.Name(), []byte("domain:s:testdomain\r\n"), 0644)
if err != nil {
t.Fatal(err)
}

req, err := http.NewRequest("GET", "/connect", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
id := identity.NewUser()

id.SetUserName(testuser)
id.SetAuthenticated(true)

req = identity.AddToRequestCtx(id, req)

u, _ := url.Parse(gateway)
c := Config{
HostSelection: "roundrobin",
Hosts: hosts,
PAATokenGenerator: paaTokenMock,
GatewayAddress: u,
RdpOpts: RdpOpts{SplitUserDomain: true},
TemplateFile: f.Name(),
}
h := c.NewHandler()

hh := http.HandlerFunc(h.HandleDownload)
hh.ServeHTTP(rr, req)

data := rdpToMap(strings.Split(rr.Body.String(), rdp.CRLF))
if data["domain"] != "testdomain" {
t.Errorf("domain key in rdp does not match: got %v want %v", data["domain"], "testdomain")
}
}

func paaTokenMock(ctx context.Context, username string, host string) (string, error) {
return username + "_" + host, nil
}
Expand Down
3 changes: 0 additions & 3 deletions dev/docker/rdpgw.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ OpenId:
ClientSecret: 01cd304c-6f43-4480-9479-618eb6fd578f
Client:
UsernameTemplate: "{{ username }}"
NetworkAutoDetect: 0
BandwidthAutoDetect: 1
ConnectionType: 6
Security:
PAATokenSigningKey: prettypleasereplacemeinproductio
Caps:
Expand Down

0 comments on commit 6b32631

Please sign in to comment.