mirror of
https://github.com/bolkedebruin/rdpgw.git
synced 2026-03-27 22:46:37 +00:00
Add kdcproxy to support spnego
This commit is contained in:
@@ -21,13 +21,15 @@ const (
|
||||
SessionStoreCookie = "cookie"
|
||||
SessionStoreFile = "file"
|
||||
|
||||
AuthenticationOpenId = "openid"
|
||||
AuthenticationBasic = "local"
|
||||
AuthenticationOpenId = "openid"
|
||||
AuthenticationBasic = "local"
|
||||
AuthenticationKerberos = "kerberos"
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
Server ServerConfig `koanf:"server"`
|
||||
OpenId OpenIDConfig `koanf:"openid"`
|
||||
Kerberos KerberosConfig `koanf:"kerberos"`
|
||||
Caps RDGCapsConfig `koanf:"caps"`
|
||||
Security SecurityConfig `koanf:"security"`
|
||||
Client ClientConfig `koanf:"client"`
|
||||
@@ -50,6 +52,11 @@ type ServerConfig struct {
|
||||
AuthSocket string `koanf:"authsocket"`
|
||||
}
|
||||
|
||||
type KerberosConfig struct {
|
||||
Keytab string `koanf:"keytab"`
|
||||
Krb5Conf string `koanf:"krb5conf"`
|
||||
}
|
||||
|
||||
type OpenIDConfig struct {
|
||||
ProviderUrl string `koanf:"providerurl"`
|
||||
ClientId string `koanf:"clientid"`
|
||||
@@ -161,6 +168,7 @@ func Load(configFile string) Configuration {
|
||||
k.UnmarshalWithConf("Caps", &Conf.Caps, koanfTag)
|
||||
k.UnmarshalWithConf("Security", &Conf.Security, koanfTag)
|
||||
k.UnmarshalWithConf("Client", &Conf.Client, koanfTag)
|
||||
k.UnmarshalWithConf("Kerberos", &Conf.Kerberos, koanfTag)
|
||||
|
||||
if len(Conf.Security.PAATokenEncryptionKey) != 32 {
|
||||
Conf.Security.PAATokenEncryptionKey, _ = security.GenerateRandomString(32)
|
||||
@@ -206,6 +214,10 @@ func Load(configFile string) Configuration {
|
||||
log.Fatalf("openid is configured but tokenauth disabled")
|
||||
}
|
||||
|
||||
if Conf.Server.Authentication == AuthenticationKerberos && Conf.Kerberos.Keytab == "" {
|
||||
log.Fatalf("kerberos is configured but no keytab was specified")
|
||||
}
|
||||
|
||||
// prepend '//' if required for URL parsing
|
||||
if !strings.Contains(Conf.Server.GatewayAddress, "//") {
|
||||
Conf.Server.GatewayAddress = "//" + Conf.Server.GatewayAddress
|
||||
|
||||
157
cmd/rdpgw/kdcproxy/proxy.go
Normal file
157
cmd/rdpgw/kdcproxy/proxy.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package kdcproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
krbconfig "github.com/bolkedebruin/gokrb5/v8/config"
|
||||
"github.com/jcmturner/gofork/encoding/asn1"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLength = 128 * 1024
|
||||
systemConfigPath = "/etc/krb5.conf"
|
||||
timeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type KdcProxyMsg struct {
|
||||
Message []byte `asn1:"tag:0,explicit"`
|
||||
Realm string `asn1:"tag:1,optional"`
|
||||
Flags int `asn1:"tag:2,optional"`
|
||||
}
|
||||
|
||||
type KerberosProxy struct {
|
||||
krb5Config *krbconfig.Config
|
||||
}
|
||||
|
||||
func InitKdcProxy(krb5Conf string) KerberosProxy {
|
||||
path := systemConfigPath
|
||||
if krb5Conf != "" {
|
||||
path = krb5Conf
|
||||
}
|
||||
cfg, err := krbconfig.Load(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot load krb5 config %s due to %s", path, err)
|
||||
}
|
||||
|
||||
return KerberosProxy{
|
||||
krb5Config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (k KerberosProxy) Handler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
length := r.ContentLength
|
||||
if length == -1 {
|
||||
http.Error(w, "Content length required", http.StatusLengthRequired)
|
||||
return
|
||||
}
|
||||
|
||||
if length > maxLength {
|
||||
http.Error(w, "Request entity too large", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
data := make([]byte, length)
|
||||
_, err := io.ReadFull(r.Body, data)
|
||||
if err != nil {
|
||||
log.Printf("Error reading from stream: %s", err)
|
||||
http.Error(w, "Error reading from stream", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := decode(data)
|
||||
if err != nil {
|
||||
log.Printf("Cannot unmarshal: %s", err)
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
krb5resp, err := k.forward(msg.Realm, msg.Message)
|
||||
if err != nil {
|
||||
log.Printf("cannot forward to kdc due to %s", err)
|
||||
http.Error(w, "Service unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
reply, err := encode(krb5resp)
|
||||
if err != nil {
|
||||
log.Printf("unable to encode krb5 message due to %s", err)
|
||||
http.Error(w, "encoding error", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/kerberos")
|
||||
w.Write(reply)
|
||||
}
|
||||
|
||||
func (k *KerberosProxy) forward(realm string, data []byte) (resp []byte, err error) {
|
||||
if realm == "" {
|
||||
realm = k.krb5Config.LibDefaults.DefaultRealm
|
||||
}
|
||||
|
||||
// load udp first as is the default for kerberos
|
||||
c, kdcs, err := k.krb5Config.GetKDCs(realm, false)
|
||||
if err != nil || c < 1 {
|
||||
return nil, fmt.Errorf("cannot get kdc for realm %s due to %s", realm, err)
|
||||
}
|
||||
|
||||
for i := range kdcs {
|
||||
conn, err := net.Dial("tcp", kdcs[i])
|
||||
if err != nil {
|
||||
log.Printf("error connecting to %s due to %s, trying next if available", kdcs[i], err)
|
||||
continue
|
||||
}
|
||||
conn.SetDeadline(time.Now().Add(timeout))
|
||||
|
||||
_, err = conn.Write(data)
|
||||
if err != nil {
|
||||
log.Printf("cannot write packet data to %s due to %s, trying next if available", kdcs[i], err)
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
// todo check header
|
||||
resp, err = io.ReadAll(conn)
|
||||
if err != nil {
|
||||
log.Printf("error reading from kdc %s due to %s, trying next if available", kdcs[i], err)
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no kdcs found for realm %s", realm)
|
||||
}
|
||||
|
||||
func decode(data []byte) (msg *KdcProxyMsg, err error) {
|
||||
var m KdcProxyMsg
|
||||
rest, err := asn1.Unmarshal(data, &m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(rest) > 0 {
|
||||
return nil, fmt.Errorf("trailing data in request")
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func encode(krb5data []byte) (r []byte, err error) {
|
||||
m := KdcProxyMsg{Message: krb5data}
|
||||
enc, err := asn1.Marshal(m)
|
||||
if err != nil {
|
||||
log.Printf("cannot marshal due to %s", err)
|
||||
return nil, err
|
||||
}
|
||||
return enc, nil
|
||||
}
|
||||
@@ -4,8 +4,12 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/bolkedebruin/gokrb5/v8/keytab"
|
||||
"github.com/bolkedebruin/gokrb5/v8/service"
|
||||
"github.com/bolkedebruin/gokrb5/v8/spnego"
|
||||
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/common"
|
||||
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/config"
|
||||
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/kdcproxy"
|
||||
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/protocol"
|
||||
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/security"
|
||||
"github.com/bolkedebruin/rdpgw/cmd/rdpgw/web"
|
||||
@@ -204,6 +208,19 @@ func main() {
|
||||
if conf.Server.Authentication == config.AuthenticationBasic {
|
||||
h := web.BasicAuthHandler{SocketAddress: conf.Server.AuthSocket}
|
||||
http.Handle("/remoteDesktopGateway/", common.EnrichContext(h.BasicAuth(gw.HandleGatewayProtocol)))
|
||||
} else if conf.Server.Authentication == config.AuthenticationKerberos {
|
||||
keytab, err := keytab.Load(conf.Kerberos.Keytab)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot load keytab: %s", err)
|
||||
}
|
||||
http.Handle("/remoteDesktopGateway/", common.EnrichContext(
|
||||
spnego.SPNEGOKRB5Authenticate(
|
||||
http.HandlerFunc(gw.HandleGatewayProtocol),
|
||||
keytab,
|
||||
service.Logger(log.Default()))),
|
||||
)
|
||||
k := kdcproxy.InitKdcProxy(conf.Kerberos.Krb5Conf)
|
||||
http.HandleFunc("/KdcProxy", k.Handler)
|
||||
} else {
|
||||
// openid
|
||||
oidc := initOIDC(url, store)
|
||||
|
||||
@@ -68,9 +68,9 @@ func (g *Gateway) HandleGatewayProtocol(w http.ResponseWriter, r *http.Request)
|
||||
t = &Tunnel{
|
||||
RDGId: connId,
|
||||
RemoteAddr: ctx.Value(common.ClientIPCtx).(string),
|
||||
UserName: ctx.Value(common.UsernameCtx).(string),
|
||||
}
|
||||
// username can be nil with openid as it's only available later
|
||||
// username can be nil with openid & kerberos as it's only available later
|
||||
// todo grab kerberos principal now?
|
||||
username := ctx.Value(common.UsernameCtx)
|
||||
if username != nil {
|
||||
t.UserName = username.(string)
|
||||
|
||||
Reference in New Issue
Block a user