Skip to content
Snippets Groups Projects
Commit 6b326314 authored by Bolke de Bruin's avatar Bolke de Bruin
Browse files

Finalize rdp templating

parent cdc497f3
No related branches found
Tags v2.0
No related merge requests found
......@@ -50,7 +50,7 @@ install: build
.PHONY: mod
mod:
go mod tidy -compat=1.19
go mod tidy -compat=1.20
# ------------------------------------------------------------------------------
# test
......
# 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`.
......@@ -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 {
......
......@@ -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,
......
package parsers
package rdp
import (
"bufio"
......
package parsers
package rdp
import (
"github.com/stretchr/testify/assert"
......
......@@ -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"
......@@ -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)
......
......@@ -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
......
......@@ -2,9 +2,9 @@ package transport
import (
"bufio"
"crypto/rand"
"errors"
"io"
"math/rand"
"net"
"net/http"
"net/http/httputil"
......@@ -12,14 +12,14 @@ import (
)
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) {
......@@ -27,9 +27,9 @@ func NewLegacy(w http.ResponseWriter) (*LegacyPKT, error) {
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
}
......@@ -37,7 +37,7 @@ func NewLegacy(w http.ResponseWriter) (*LegacyPKT, error) {
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)
......
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"
)
......@@ -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)
......
......@@ -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"
......@@ -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 {
......@@ -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
}
......@@ -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]
......@@ -178,6 +172,7 @@ 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 {
......@@ -185,31 +180,40 @@ func (h *Handler) HandleDownload(w http.ResponseWriter, r *http.Request) {
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
......
......@@ -8,6 +8,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
)
......@@ -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
}
......
......@@ -14,9 +14,6 @@ OpenId:
ClientSecret: 01cd304c-6f43-4480-9479-618eb6fd578f
Client:
UsernameTemplate: "{{ username }}"
NetworkAutoDetect: 0
BandwidthAutoDetect: 1
ConnectionType: 6
Security:
PAATokenSigningKey: prettypleasereplacemeinproductio
Caps:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment