Finalize rdp templating
This commit is contained in:
parent
cdc497f365
commit
6b32631434
2
Makefile
2
Makefile
@ -50,7 +50,7 @@ install: build
|
||||
|
||||
.PHONY: mod
|
||||
mod:
|
||||
go mod tidy -compat=1.19
|
||||
go mod tidy -compat=1.20
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# test
|
||||
|
||||
15
UPGRADING.md
Normal file
15
UPGRADING.md
Normal file
@ -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`.
|
||||
@ -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,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package parsers
|
||||
package rdp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -1,4 +1,4 @@
|
||||
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)
|
||||
|
||||
@ -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"
|
||||
)
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user