Compare commits

..

13 Commits

18 changed files with 541 additions and 245 deletions
+8 -1
View File
@@ -99,4 +99,11 @@ cmd/tls.key:
cmd/rsa.key: cmd/rsa.key:
openssl genrsa -traditional -out cmd/rsa.key 2048 openssl genrsa -traditional -out cmd/rsa.key 2048
.PHONY: all clean test mtls-client-cert-for-test:
## you can use this recipe to generate certificate/key files for mtls testing against
## the built-in certificate used as trusted ca.
openssl genrsa -out mtls-client.key 2048
openssl req -new -key mtls-client.key -out mtls-client.csr -subj "/CN=mtls-client"
openssl x509 -req -in mtls-client.csr -CA cmd/tls.crt -CAkey cmd/tls.key -CAcreateserial -out mtls-client.crt -days 365
.PHONY: all clean test mtls-client-cert-for-test
-16
View File
@@ -1179,22 +1179,6 @@ func (ctl *client_ctl_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
// handle authentication using the first message. // handle authentication using the first message.
// end this task if authentication fails. // end this task if authentication fails.
if !ctl.noauth && c.ctl_auth != nil { if !ctl.noauth && c.ctl_auth != nil {
/*
var req *http.Request
req = ws.Request()
if req.Header.Get("Authorization") == "" {
var access_token string
access_token = req.FormValue("access-token") // this is an authorization token
if access_token != "" {
// websocket doesn't actual have extra headers except a few fixed
// ones. add "Authorization" header from the query paramerer and
// compose a fake header to reuse the same Authentication() function
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", access_token))
}
}
*/
status_code, _ = c.ctl_auth.Authenticate(ws.Request(), "access-token") status_code, _ = c.ctl_auth.Authenticate(ws.Request(), "access-token")
if status_code != http.StatusOK { goto done } if status_code != http.StatusOK { goto done }
} }
+4 -5
View File
@@ -56,11 +56,10 @@ func (pty *client_pty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
// end this task if authentication fails. // end this task if authentication fails.
if c.ctl_auth != nil { if c.ctl_auth != nil {
var status_code int var status_code int
var msg string status_code, _ = c.ctl_auth.Authenticate(req, "access-token")
status_code, msg = c.ctl_auth.Authenticate(req, "access-token")
if status_code != http.StatusOK { if status_code != http.StatusOK {
ws.Close() ws.Close()
return status_code, fmt.Errorf("failed to authenticate - %s", msg) return status_code, fmt.Errorf("failed to authenticate")
} }
} }
@@ -277,7 +276,7 @@ done:
// ------------------------------------------------------ // ------------------------------------------------------
func (pty *client_pty_xterm_file) Authenticate(req *http.Request) (int, string) { func (pty *client_pty_xterm_file) Authenticate(req *http.Request) (int, string) {
if pty.file == "xterm.html" { if pty.c.ctl_auth != nil && pty.file == "xterm.html" {
// this is not a real api call. but at least for xterm.html, // this is not a real api call. but at least for xterm.html,
// i don't bypass authentication and and in addition, // i don't bypass authentication and and in addition,
// i check the value of the "access-token" parameter for // i check the value of the "access-token" parameter for
@@ -345,7 +344,7 @@ func (pty *client_pty_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Req
default: default:
if strings.HasPrefix(pty.file, "_redir:") { if strings.HasPrefix(pty.file, "_redir:") {
status_code = http.StatusMovedPermanently status_code = http.StatusMovedPermanently
w.Header().Set("Location", pty.file[7:]) w.Header().Set("Location", append_raw_query(pty.file[7:], req))
w.WriteHeader(status_code) w.WriteHeader(status_code)
} else { } else {
status_code = WriteEmptyRespHeader(w, http.StatusNotFound) status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
+2 -19
View File
@@ -1902,28 +1902,11 @@ func NewClient(ctx context.Context, name string, logger Logger, cfg *ClientConfi
c.ctl_mux.Handle("/_pty/ws", c.ctl_mux.Handle("/_pty/ws",
c.SafeWrapWebsocketHandler(c.WrapWebsocketHandler(&client_pty_ws{C: &c, Id: HS_ID_CTL}))) c.SafeWrapWebsocketHandler(c.WrapWebsocketHandler(&client_pty_ws{C: &c, Id: HS_ID_CTL})))
c.ctl_mux.Handle("/_pty/xterm.js",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "xterm.js"}))
c.ctl_mux.Handle("/_pty/xterm.js/",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_forbidden"}))
c.ctl_mux.Handle("/_pty/xterm-addon-fit.js",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "xterm-addon-fit.js"}))
c.ctl_mux.Handle("/_pty/xterm-addon-fit.js/",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_forbidden"}))
c.ctl_mux.Handle("/_pty/xterm-addon-unicode11.js",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "xterm-addon-unicode11.js"}))
c.ctl_mux.Handle("/_pty/xterm-addon-unicode11.js/",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_forbidden"}))
c.ctl_mux.Handle("/_pty/xterm.css",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "xterm.css"}))
c.ctl_mux.Handle("/_pty/xterm.css/",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_forbidden"}))
c.ctl_mux.Handle("/_pty/xterm.html", c.ctl_mux.Handle("/_pty/xterm.html",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "xterm.html"})) c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "xterm.html"}))
c.ctl_mux.Handle("/_pty/xterm.html/", // without this forbidden, /_pty/xterm.js/ access resulted in xterm.html. c.ctl_mux.Handle("/_pty/xterm.html/",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_forbidden"})) c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_forbidden"}))
c.ctl_mux.Handle("/_pty/", c.ctl_mux.Handle("/_pty/{$}",
c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_redir:xterm.html"})) c.WrapHttpHandler(&client_pty_xterm_file{client_ctl: client_ctl{c: &c, id: HS_ID_CTL}, file: "_redir:xterm.html"}))
c.ctl_addr = make([]string, len(cfg.CtlAddrs)) c.ctl_addr = make([]string, len(cfg.CtlAddrs))
+121 -9
View File
@@ -9,6 +9,7 @@ import "fmt"
import "hodu" import "hodu"
import "net/netip" import "net/netip"
import "os" import "os"
import "regexp"
import "strings" import "strings"
import "time" import "time"
@@ -21,9 +22,18 @@ type ServerRptyConfig struct {
Protection string `yaml:"protection"` Protection string `yaml:"protection"`
TokenRsaKeyText string `yaml:"token-rsa-key-text"` TokenRsaKeyText string `yaml:"token-rsa-key-text"`
TokenRsaKeyFile string `yaml:"token-rsa-key-file"` TokenRsaKeyFile string `yaml:"token-rsa-key-file"`
TokenTtl string `yaml:"token-ttl"`
} `yaml:"client-token"` } `yaml:"client-token"`
} }
type ServerTLSSNIConfig struct {
Name string `yaml:"name-regex"`
CertFile string `yaml:"cert-file"`
KeyFile string `yaml:"key-file"`
CertText string `yaml:"cert-text"`
KeyText string `yaml:"key-text"`
}
type ServerTLSConfig struct { type ServerTLSConfig struct {
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled"`
CertFile string `yaml:"cert-file"` CertFile string `yaml:"cert-file"`
@@ -40,9 +50,13 @@ type ServerTLSConfig struct {
//MaxVersion TLSVersion `yaml:"max-version"` //MaxVersion TLSVersion `yaml:"max-version"`
//PreferServerCipherSuites bool `yaml:"prefer-server-cipher-suites"` //PreferServerCipherSuites bool `yaml:"prefer-server-cipher-suites"`
//ClientAllowedSans []string `yaml:"client-allowed-sans"` //ClientAllowedSans []string `yaml:"client-allowed-sans"`
SNIs []ServerTLSSNIConfig `yaml:"snis"`
} }
type ClientTLSConfig struct { type ClientTLSConfig struct {
// The Enabled field doesnt indicate using or not using tls.
// It expresses the rest of items are applied or not in tls configuration.
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled"`
CertFile string `yaml:"cert-file"` CertFile string `yaml:"cert-file"`
KeyFile string `yaml:"key-file"` KeyFile string `yaml:"key-file"`
@@ -77,25 +91,34 @@ type CTLServiceConfig struct {
Auth HttpAuthConfig `yaml:"auth"` Auth HttpAuthConfig `yaml:"auth"`
} }
type ECTServiceConfig struct {
Addrs []string `yaml:"addresses"`
Auth HttpAuthConfig `yaml:"auth"`
}
type RPXServiceConfig struct { type RPXServiceConfig struct {
Addrs []string `yaml:"addresses"` Addrs []string `yaml:"addresses"`
} }
type RPXClientTokenConfig struct { type RPXClientTokenConfig struct {
AttrName string `yaml:"attr-name"` AttrName string `yaml:"attr-name"`
Regex string `yaml:"regex"`
SubmatchIndex int `yaml:"submatch-index"`
Protection string `yaml:"protection"` Protection string `yaml:"protection"`
TokenRsaKeyText string `yaml:"token-rsa-key-text"` TokenRsaKeyText string `yaml:"token-rsa-key-text"`
TokenRsaKeyFile string `yaml:"token-rsa-key-file"` TokenRsaKeyFile string `yaml:"token-rsa-key-file"`
Regex string `yaml:"regex"` TokenTtl string `yaml:"token-ttl"`
SubmatchIndex int `yaml:"submatch-index"`
} }
type PXYServiceConfig struct { type PXYServiceConfig struct {
Addrs []string `yaml:"addresses"` Addrs []string `yaml:"addresses"`
Auth HttpAuthConfig `yaml:"auth"`
} }
type WPXServiceConfig struct { type WPXServiceConfig struct {
Addrs []string `yaml:"addresses"` Addrs []string `yaml:"addresses"`
Auth HttpAuthConfig `yaml:"auth"`
} }
type RPCServiceConfig struct { // rpc server-side configuration type RPCServiceConfig struct { // rpc server-side configuration
@@ -161,6 +184,11 @@ type ServerConfig struct {
Rpty ServerRptyConfig `yaml:"rpty"` Rpty ServerRptyConfig `yaml:"rpty"`
} `yaml:"ctl"` } `yaml:"ctl"`
ECT struct {
Service ECTServiceConfig `yaml:"service"`
TLS ServerTLSConfig `yaml:"tls"`
} `yaml:"ect"`
RPX struct { RPX struct {
Service RPXServiceConfig `yaml:"service"` Service RPXServiceConfig `yaml:"service"`
TLS ServerTLSConfig `yaml:"tls"` TLS ServerTLSConfig `yaml:"tls"`
@@ -171,7 +199,10 @@ type ServerConfig struct {
Service PXYServiceConfig `yaml:"service"` Service PXYServiceConfig `yaml:"service"`
TLS ServerTLSConfig `yaml:"tls"` TLS ServerTLSConfig `yaml:"tls"`
Target struct { Target struct {
TLS ClientTLSConfig `yaml:"tls"` // TODO: This will have to be extended to be an array
// of configurations to cater for different targets
// It needs a name or a name pattern field to match the target.
TLS ClientTLSConfig `yaml:"tls"`
} `yaml:"target"` } `yaml:"target"`
} `yaml:"pxy"` } `yaml:"pxy"`
@@ -288,15 +319,75 @@ func log_strings_to_mask(str []string) hodu.LogMask {
// -------------------------------------------------------------------- // --------------------------------------------------------------------
type ServerSNICert struct {
name *regexp.Regexp
cert tls.Certificate
}
func hostname_wildcard_match(wildcard string, name string) bool {
var suffix_start int
var suffix_len int
var prefix_end int
var i int
// must start with "*."
if len(wildcard) < 3 || wildcard[0] != '*' || wildcard[1] != '.' { return false }
// suffix is everything after "*."
suffix_start = 1 // points to "."
suffix_len = len(wildcard) - suffix_start
// name must be longer than suffix (so there's at least one label)
if len(name) <= suffix_len { return false }
// check suffix match
if name[len(name)-suffix_len:] != wildcard[suffix_start:] { return false }
// check there is exactly one label before suffix
prefix_end = len(name) - suffix_len
if prefix_end == 0 { return false }
// ensure no '.' in the prefix (only one label)
for i = 0; i < prefix_end; i++ {
if name[i] == '.' { return false }
}
return true
}
func make_tls_server_config(cfg *ServerTLSConfig) (*tls.Config, error) { func make_tls_server_config(cfg *ServerTLSConfig) (*tls.Config, error) {
var tlscfg *tls.Config var tlscfg *tls.Config
if cfg.Enabled { if cfg.Enabled {
var cert tls.Certificate var cert tls.Certificate
var cert_pool *x509.CertPool var cert_pool *x509.CertPool
var sni_certs []ServerSNICert
var i int
var ok bool var ok bool
var err error var err error
for i = range cfg.SNIs {
var regex *regexp.Regexp
if cfg.SNIs[i].CertText != "" && cfg.SNIs[i].KeyText != "" {
cert, err = tls.X509KeyPair([]byte(cfg.SNIs[i].CertText), []byte(cfg.SNIs[i].KeyText))
} else if cfg.SNIs[i].CertFile != "" && cfg.SNIs[i].KeyFile != "" {
cert, err = tls.LoadX509KeyPair(cfg.SNIs[i].CertFile, cfg.SNIs[i].KeyFile)
} else {
continue;
}
if err != nil {
return nil, fmt.Errorf("failed to load sni key pair - %s", err.Error())
}
regex, err = regexp.Compile(cfg.SNIs[i].Name)
if err != nil {
return nil, fmt.Errorf("failed to compile sni name-regex - %s", err.Error())
}
sni_certs = append(sni_certs, ServerSNICert{name: regex, cert: cert});
}
if cfg.CertText != "" && cfg.KeyText != "" { if cfg.CertText != "" && cfg.KeyText != "" {
cert, err = tls.X509KeyPair([]byte(cfg.CertText), []byte(cfg.KeyText)) cert, err = tls.X509KeyPair([]byte(cfg.CertText), []byte(cfg.KeyText))
} else if cfg.CertFile != "" && cfg.KeyFile != "" { } else if cfg.CertFile != "" && cfg.KeyFile != "" {
@@ -313,7 +404,7 @@ func make_tls_server_config(cfg *ServerTLSConfig) (*tls.Config, error) {
if cfg.ClientCACertText != "" { if cfg.ClientCACertText != "" {
ok = cert_pool.AppendCertsFromPEM([]byte(cfg.ClientCACertText)) ok = cert_pool.AppendCertsFromPEM([]byte(cfg.ClientCACertText))
if !ok { if !ok {
return nil, fmt.Errorf("failed to append certificate to pool") return nil, fmt.Errorf("failed to append configured certificate text to pool")
} }
} else if cfg.ClientCACertFile != "" { } else if cfg.ClientCACertFile != "" {
var text []byte var text []byte
@@ -323,22 +414,40 @@ func make_tls_server_config(cfg *ServerTLSConfig) (*tls.Config, error) {
} }
ok = cert_pool.AppendCertsFromPEM(text) ok = cert_pool.AppendCertsFromPEM(text)
if !ok { if !ok {
return nil, fmt.Errorf("failed to append certificate to pool") return nil, fmt.Errorf("failed to append configured certificate file to pool")
} }
} else { } else {
// if the client ca cert is not specified, use the bundled tls cert
// as a trusted ca cert.
ok = cert_pool.AppendCertsFromPEM(hodu_tls_cert_text) ok = cert_pool.AppendCertsFromPEM(hodu_tls_cert_text)
if !ok { if !ok {
return nil, fmt.Errorf("failed to append certificate to pool") return nil, fmt.Errorf("failed to append builtin certificate to pool")
} }
} }
tlscfg = &tls.Config{ tlscfg = &tls.Config{
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
// If multiple certificates are configured, we may have to implement GetCertificate
// GetCertificate: func (chi *tls.ClientHelloInfo) (*Certificate, error) { return cert, nil }
ClientAuth: tls_string_to_client_auth_type(cfg.ClientAuthType), ClientAuth: tls_string_to_client_auth_type(cfg.ClientAuthType),
ClientCAs: cert_pool, // trusted CA certs for client certificate verification ClientCAs: cert_pool, // trusted CA certs for client certificate verification
} }
if (len(sni_certs) > 0) {
tlscfg.GetCertificate = func (chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
var server_name string
var x int
server_name = strings.TrimSuffix(strings.ToLower(chi.ServerName), ".")
if server_name == "" { return nil, nil }
for x = range sni_certs {
if sni_certs[x].name.MatchString(server_name) {
return &sni_certs[x].cert, nil
}
}
return nil, nil
}
}
} }
return tlscfg, nil return tlscfg, nil
@@ -367,7 +476,8 @@ func make_tls_client_config(cfg *ClientTLSConfig) (*tls.Config, error) {
return nil, fmt.Errorf("failed to load key pair - %s", err.Error()) return nil, fmt.Errorf("failed to load key pair - %s", err.Error())
} }
cert_pool = x509.NewCertPool() cert_pool, err = x509.SystemCertPool()
if err != nil { cert_pool = x509.NewCertPool() }
if cfg.ServerCACertText != "" { if cfg.ServerCACertText != "" {
ok = cert_pool.AppendCertsFromPEM([]byte(cfg.ServerCACertText)) ok = cert_pool.AppendCertsFromPEM([]byte(cfg.ServerCACertText))
if !ok { if !ok {
@@ -491,6 +601,8 @@ func make_http_auth_config(cfg *HttpAuthConfig) (*hodu.HttpAuthConfig, error) {
action = hodu.HTTP_ACCESS_REJECT action = hodu.HTTP_ACCESS_REJECT
case "auth-required": case "auth-required":
action = hodu.HTTP_ACCESS_AUTH_REQUIRED action = hodu.HTTP_ACCESS_AUTH_REQUIRED
case "cert-required":
action = hodu.HTTP_ACCESS_CERT_REQUIRED
default: default:
return nil, fmt.Errorf("invalid access rule action %s", rule.Action) return nil, fmt.Errorf("invalid access rule action %s", rule.Action)
} }
+40 -19
View File
@@ -99,7 +99,7 @@ func (sh *signal_handler) WriteLog(id string, level hodu.LogLevel, fmt string, a
// -------------------------------------------------------------------- // --------------------------------------------------------------------
func server_main(ctl_addrs []string, rpc_addrs []string, rpx_addrs[] string, pxy_addrs []string, wpx_addrs []string, cfg *ServerConfig) error { func server_main(ctl_addrs []string, ect_addrs []string, rpc_addrs []string, rpx_addrs[] string, pxy_addrs []string, wpx_addrs []string, cfg *ServerConfig) error {
var s *hodu.Server var s *hodu.Server
var config *hodu.ServerConfig var config *hodu.ServerConfig
var logger *AppLogger var logger *AppLogger
@@ -111,6 +111,7 @@ func server_main(ctl_addrs []string, rpc_addrs []string, rpx_addrs[] string, pxy
config = &hodu.ServerConfig{ config = &hodu.ServerConfig{
CtlAddrs: ctl_addrs, CtlAddrs: ctl_addrs,
EctAddrs: ect_addrs,
RpcAddrs: rpc_addrs, RpcAddrs: rpc_addrs,
RpxAddrs: rpx_addrs, RpxAddrs: rpx_addrs,
PxyAddrs: pxy_addrs, PxyAddrs: pxy_addrs,
@@ -120,6 +121,8 @@ func server_main(ctl_addrs []string, rpc_addrs []string, rpx_addrs[] string, pxy
// load configuration from cfg // load configuration from cfg
config.CtlTls, err = make_tls_server_config(&cfg.CTL.TLS) config.CtlTls, err = make_tls_server_config(&cfg.CTL.TLS)
if err != nil { return err } if err != nil { return err }
config.EctTls, err = make_tls_server_config(&cfg.ECT.TLS)
if err != nil { return err }
config.RpcTls, err = make_tls_server_config(&cfg.RPC.TLS) config.RpcTls, err = make_tls_server_config(&cfg.RPC.TLS)
if err != nil { return err } if err != nil { return err }
config.RpxTls, err = make_tls_server_config(&cfg.RPX.TLS) config.RpxTls, err = make_tls_server_config(&cfg.RPX.TLS)
@@ -128,36 +131,47 @@ func server_main(ctl_addrs []string, rpc_addrs []string, rpx_addrs[] string, pxy
if err != nil { return err } if err != nil { return err }
config.PxyTargetTls, err = make_tls_client_config(&cfg.PXY.Target.TLS) config.PxyTargetTls, err = make_tls_client_config(&cfg.PXY.Target.TLS)
if err != nil { return err } if err != nil { return err }
config.PxyAuth, err = make_http_auth_config(&cfg.PXY.Service.Auth)
if err != nil { return err }
config.WpxTls, err = make_tls_server_config(&cfg.WPX.TLS) config.WpxTls, err = make_tls_server_config(&cfg.WPX.TLS)
if err != nil { return err } if err != nil { return err }
config.WpxAuth, err = make_http_auth_config(&cfg.WPX.Service.Auth)
if err != nil { return err }
if len(config.CtlAddrs) <= 0 { config.CtlAddrs = cfg.CTL.Service.Addrs } if len(config.CtlAddrs) <= 0 { config.CtlAddrs = cfg.CTL.Service.Addrs }
if len(config.EctAddrs) <= 0 { config.EctAddrs = cfg.ECT.Service.Addrs }
if len(config.RpcAddrs) <= 0 { config.RpcAddrs = cfg.RPC.Service.Addrs } if len(config.RpcAddrs) <= 0 { config.RpcAddrs = cfg.RPC.Service.Addrs }
if len(config.RpxAddrs) <= 0 { config.RpxAddrs = cfg.RPX.Service.Addrs } if len(config.RpxAddrs) <= 0 { config.RpxAddrs = cfg.RPX.Service.Addrs }
if len(config.PxyAddrs) <= 0 { config.PxyAddrs = cfg.PXY.Service.Addrs } if len(config.PxyAddrs) <= 0 { config.PxyAddrs = cfg.PXY.Service.Addrs }
if len(config.WpxAddrs) <= 0 { config.WpxAddrs = cfg.WPX.Service.Addrs } if len(config.WpxAddrs) <= 0 { config.WpxAddrs = cfg.WPX.Service.Addrs }
config.RptyClientTokenProtection = cfg.CTL.Rpty.ClientToken.Protection config.RptyClientTokenProtection = cfg.CTL.Rpty.ClientToken.Protection
config.RptyClientTokenRsaKey, err = make_rsa_private_key_config(cfg.CTL.Rpty.ClientToken.TokenRsaKeyText, cfg.CTL.Rpty.ClientToken.TokenRsaKeyFile, nil, "rpx client token rsa key") config.RptyClientTokenTtl, err = hodu.ParseDurationString(cfg.CTL.Rpty.ClientToken.TokenTtl)
if err != nil { return err }
config.RptyClientTokenRsaKey, err = make_rsa_private_key_config(cfg.CTL.Rpty.ClientToken.TokenRsaKeyText, cfg.CTL.Rpty.ClientToken.TokenRsaKeyFile, hodu_rsa_key_text, "rpty client token rsa key")
if err != nil { return err }
config.RpxClientTokenAttrName = cfg.RPX.ClientToken.AttrName config.RpxClientTokenAttrName = cfg.RPX.ClientToken.AttrName
config.RpxClientTokenProtection = cfg.RPX.ClientToken.Protection config.RpxClientTokenSubmatchIndex = cfg.RPX.ClientToken.SubmatchIndex
config.RpxClientTokenRsaKey, err = make_rsa_private_key_config(cfg.RPX.ClientToken.TokenRsaKeyText, cfg.RPX.ClientToken.TokenRsaKeyFile, nil, "rpx client token rsa key")
if err != nil { return err }
if strings.EqualFold(config.RpxClientTokenProtection, "rsa-aes-256-gcm") && config.RpxClientTokenRsaKey == nil {
return fmt.Errorf("missing rpx client token rsa key for protection %s", config.RpxClientTokenProtection)
}
if cfg.RPX.ClientToken.Regex != "" { if cfg.RPX.ClientToken.Regex != "" {
config.RpxClientTokenRegex, err = regexp.Compile(cfg.RPX.ClientToken.Regex) config.RpxClientTokenRegex, err = regexp.Compile(cfg.RPX.ClientToken.Regex)
if err != nil { return err } if err != nil { return err }
} }
config.RpxClientTokenSubmatchIndex = cfg.RPX.ClientToken.SubmatchIndex
config.RpxClientTokenProtection = cfg.RPX.ClientToken.Protection
config.RpxClientTokenTtl, err = hodu.ParseDurationString(cfg.RPX.ClientToken.TokenTtl)
if err != nil { return err }
config.RpxClientTokenRsaKey, err = make_rsa_private_key_config(cfg.RPX.ClientToken.TokenRsaKeyText, cfg.RPX.ClientToken.TokenRsaKeyFile, hodu_rsa_key_text, "rpx client token rsa key")
if err != nil { return err }
config.CtlCors = cfg.CTL.Service.Cors config.CtlCors = cfg.CTL.Service.Cors
config.CtlAuth, err = make_http_auth_config(&cfg.CTL.Service.Auth) config.CtlAuth, err = make_http_auth_config(&cfg.CTL.Service.Auth)
if err != nil { return err } if err != nil { return err }
config.CtlPrefix = cfg.CTL.Service.Prefix config.CtlPrefix = cfg.CTL.Service.Prefix
config.EctAuth, err = make_http_auth_config(&cfg.ECT.Service.Auth)
if err != nil { return err }
config.RpcMaxConns = cfg.APP.MaxRpcConns config.RpcMaxConns = cfg.APP.MaxRpcConns
config.RpcMinPingIntvl = cfg.APP.MinRpcPingIntvl config.RpcMinPingIntvl = cfg.APP.MinRpcPingIntvl
config.MaxPeers = cfg.APP.MaxPeers config.MaxPeers = cfg.APP.MaxPeers
@@ -214,6 +228,7 @@ func server_main(ctl_addrs []string, rpc_addrs []string, rpx_addrs[] string, pxy
s.StartService(nil) s.StartService(nil)
s.StartCtlService() s.StartCtlService()
s.StartEctService()
s.StartRpxService() s.StartRpxService()
s.StartPxyService() s.StartPxyService()
s.StartWpxService() s.StartWpxService()
@@ -350,14 +365,14 @@ func client_main(ctl_addrs []string, rpc_addrs []string, route_configs []string,
config.HttpMaxHeaderBytes = cfg.APP.HttpMaxHeaderBytes config.HttpMaxHeaderBytes = cfg.APP.HttpMaxHeaderBytes
if cfg.APP.TokenText != "" { if cfg.APP.TokenText != "" {
config.Token = cfg.APP.TokenText config.Token = strings.TrimSpace(cfg.APP.TokenText)
} else if cfg.APP.TokenFile != "" { } else if cfg.APP.TokenFile != "" {
var bytes []byte var bytes []byte
bytes, err = os.ReadFile(cfg.APP.TokenFile) bytes, err = os.ReadFile(cfg.APP.TokenFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to read token file - %s", err.Error()) return fmt.Errorf("unable to read token file - %s", err.Error())
} }
config.Token = string(bytes) config.Token = strings.TrimSpace(string(bytes))
} }
// end of loading configuration from cfg // end of loading configuration from cfg
@@ -417,6 +432,7 @@ func main() {
if strings.EqualFold(os.Args[1], "server") { if strings.EqualFold(os.Args[1], "server") {
var rpc_addrs []string var rpc_addrs []string
var ctl_addrs []string var ctl_addrs []string
var ect_addrs []string
var rpx_addrs []string var rpx_addrs []string
var pxy_addrs []string var pxy_addrs []string
var wpx_addrs []string var wpx_addrs []string
@@ -427,6 +443,7 @@ func main() {
var cfg ServerConfig var cfg ServerConfig
ctl_addrs = make([]string, 0) ctl_addrs = make([]string, 0)
ect_addrs = make([]string, 0)
rpc_addrs = make([]string, 0) rpc_addrs = make([]string, 0)
rpx_addrs = make([]string, 0) rpx_addrs = make([]string, 0)
pxy_addrs = make([]string, 0) pxy_addrs = make([]string, 0)
@@ -437,6 +454,10 @@ func main() {
ctl_addrs = append(ctl_addrs, v) ctl_addrs = append(ctl_addrs, v)
return nil return nil
}) })
flgs.Func("ect-on", "specify a listening address for limited external control channel", func(v string) error {
ect_addrs = append(ect_addrs, v)
return nil
})
flgs.Func("rpc-on", "specify a rpc listening address", func(v string) error { flgs.Func("rpc-on", "specify a rpc listening address", func(v string) error {
rpc_addrs = append(rpc_addrs, v) rpc_addrs = append(rpc_addrs, v)
return nil return nil
@@ -511,7 +532,7 @@ func main() {
cfg.APP.PtyShell = pty_shell cfg.APP.PtyShell = pty_shell
} }
err = server_main(ctl_addrs, rpc_addrs, rpx_addrs, pxy_addrs, wpx_addrs, &cfg) err = server_main(ctl_addrs, ect_addrs, rpc_addrs, rpx_addrs, pxy_addrs, wpx_addrs, &cfg)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: server error - %s\n", err.Error()) fmt.Fprintf(os.Stderr, "ERROR: server error - %s\n", err.Error())
goto oops goto oops
@@ -557,10 +578,6 @@ func main() {
pty_shell = v pty_shell = v
return nil return nil
}) })
flgs.Func("rxc-profile-files", "specify a file pattern for rxc profiles", func(v string) error {
rxc_profile_files = append(rxc_profile_files, v)
return nil
})
flgs.Func("client-token", "specify a client token", func(v string) error { flgs.Func("client-token", "specify a client token", func(v string) error {
client_token = v client_token = v
return nil return nil
@@ -569,6 +586,10 @@ func main() {
rpx_target_addr = v rpx_target_addr = v
return nil return nil
}) })
flgs.Func("rxc-profile-files", "specify a file pattern for rxc profiles", func(v string) error {
rxc_profile_files = append(rxc_profile_files, v)
return nil
})
flgs.SetOutput(io.Discard) flgs.SetOutput(io.Discard)
err = flgs.Parse(os.Args[2:]) err = flgs.Parse(os.Args[2:])
if err != nil { if err != nil {
@@ -634,8 +655,8 @@ func main() {
os.Exit(0) os.Exit(0)
wrong_usage: wrong_usage:
fmt.Fprintf(os.Stderr, "USAGE: %s server --rpc-on=addr:port --ctl-on=addr:port --rpx-on=addr:port --pxy-on=addr:port --wpx-on=addr:port [--config-file=file] [--config-file-pattern=pattern] [--pty-shell=string]\n", os.Args[0]) fmt.Fprintf(os.Stderr, "USAGE: %s server --rpc-on=addr:port --ctl-on=addr:port --ect-on=addr:port --rpx-on=addr:port --pxy-on=addr:port --wpx-on=addr:port [--config-file=file] [--config-file-pattern=pattern] [--pty-shell=string]\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s client --rpc-to=addr:port --ctl-on=addr:port [--config-file=file] [--config-file-pattern=pattern] [--pty-shell=string] [--rxc-profile-files=pattern] [--client-token=string] [--rpx-target-addr=addr:port] [peer-addr:peer-port ...]\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s client --rpc-to=addr:port --ctl-on=addr:port [--config-file=file] [--config-file-pattern=pattern] [--pty-shell=string] [--rxc-profile-files=pattern] [--client-token=string] [--rpx-target-addr=addr:port] [local-target-addr:local-target-port,remote-binding-addr:remote-binding-port,type,description ...]\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s version\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s version\n", os.Args[0])
os.Exit(1) os.Exit(1)
+24 -12
View File
@@ -70,6 +70,7 @@ const (
HTTP_ACCESS_ACCEPT HttpAccessAction = iota HTTP_ACCESS_ACCEPT HttpAccessAction = iota
HTTP_ACCESS_REJECT HTTP_ACCESS_REJECT
HTTP_ACCESS_AUTH_REQUIRED HTTP_ACCESS_AUTH_REQUIRED
HTTP_ACCESS_CERT_REQUIRED
) )
type HttpAccessRule struct { type HttpAccessRule struct {
@@ -380,7 +381,7 @@ func (stats *json_out_go_stats) from_runtime_stats() {
// ------------------------------------ // ------------------------------------
func (auth *HttpAuthConfig) check_auth_token(jwt_token string) error { func (auth *HttpAuthConfig) check_access_token(jwt_token string) error {
var jwt *JWT[ServerTokenClaim] var jwt *JWT[ServerTokenClaim]
var claim ServerTokenClaim var claim ServerTokenClaim
var err error var err error
@@ -393,13 +394,13 @@ func (auth *HttpAuthConfig) check_auth_token(jwt_token string) error {
now = time.Now() now = time.Now()
// TODO: subject check. other claim check // TODO: subject check. other claim check
if !now.Before(time.Unix(claim.IssuedAt, 0)) && now.Before(time.Unix(claim.ExpiresAt, 0)) { return nil } // not expired if !now.Before(time.Unix(claim.IssuedAt, 0)) && now.Before(time.Unix(claim.ExpiresAt, 0)) { return nil } // not expired
return fmt.Errorf("auth token expired"); return fmt.Errorf("access token expired");
} else { } else {
return fmt.Errorf("failed to verify auth token"); return fmt.Errorf("failed to verify access token");
} }
} }
func (auth *HttpAuthConfig) Authenticate(req *http.Request, auth_token_param_name string) (int, string) { func (auth *HttpAuthConfig) Authenticate(req *http.Request, access_token_param_name string) (int, string) {
var rule HttpAccessRule var rule HttpAccessRule
var raddrport netip.AddrPort var raddrport netip.AddrPort
var raddr netip.Addr var raddr netip.Addr
@@ -434,6 +435,12 @@ func (auth *HttpAuthConfig) Authenticate(req *http.Request, auth_token_param_nam
return http.StatusOK, "" return http.StatusOK, ""
} else if rule.Action == HTTP_ACCESS_REJECT { } else if rule.Action == HTTP_ACCESS_REJECT {
return http.StatusForbidden, "" return http.StatusForbidden, ""
} else if rule.Action == HTTP_ACCESS_CERT_REQUIRED {
if req.TLS == nil || len(req.TLS.PeerCertificates) <= 0 {
return http.StatusForbidden, ""
} else {
return http.StatusOK, ""
}
} }
// HTTP_ACCESS_AUTH_REQUIRED. // HTTP_ACCESS_AUTH_REQUIRED.
@@ -455,18 +462,18 @@ func (auth *HttpAuthConfig) Authenticate(req *http.Request, auth_token_param_nam
var auth_parts []string var auth_parts []string
auth_parts = strings.Fields(auth_hdr) auth_parts = strings.Fields(auth_hdr)
if len(auth_parts) == 2 && strings.EqualFold(auth_parts[0], "Bearer") && auth.TokenRsaKey != nil { if len(auth_parts) == 2 && strings.EqualFold(auth_parts[0], "Bearer") && auth.TokenRsaKey != nil {
err = auth.check_auth_token(auth_parts[1]) err = auth.check_access_token(auth_parts[1])
if err != nil { return http.StatusUnauthorized, err.Error() } if err != nil { return http.StatusUnauthorized, "" } // not returning auth.Realm becuase it sents the Authorization header
return http.StatusOK, "" return http.StatusOK, ""
} }
} else if auth_token_param_name != "" { } else if access_token_param_name != "" {
// there is no Authorization header. // there is no Authorization header.
// but there may be a token parameter. // but there may be a token parameter.
var auth_token string var access_token string
auth_token = req.FormValue(auth_token_param_name) access_token = req.FormValue(access_token_param_name)
if auth_token != "" { if access_token != "" {
err = auth.check_auth_token(auth_token) err = auth.check_access_token(access_token)
if err != nil { return http.StatusUnauthorized, err.Error() } if err != nil { return http.StatusUnauthorized, "" } // not returning auth.Realm because it sent an access token
return http.StatusOK, "" return http.StatusOK, ""
} }
} }
@@ -590,6 +597,11 @@ func get_regex_submatch(re *regexp.Regexp, str string, n int) string {
return str[start:end] return str[start:end]
} }
func append_raw_query(location string, req *http.Request) string {
if req.URL.RawQuery == "" { return location }
return location + "?" + req.URL.RawQuery
}
func read_line_limited(r *bufio.Reader, max int) (string, error) { func read_line_limited(r *bufio.Reader, max int) (string, error) {
var b []byte var b []byte
var line []byte var line []byte
+3 -3
View File
@@ -182,7 +182,7 @@ func TestHttpAuthConfigAuthenticateWithEncodedHeaders(t *testing.T) {
req.Header.Set("X-Auth-Username", username) req.Header.Set("X-Auth-Username", username)
req.Header.Set("X-Auth-Password", password) req.Header.Set("X-Auth-Password", password)
status, realm = auth.Authenticate(req) status, realm = auth.Authenticate(req, "")
if status != http.StatusOK || realm != "" { if status != http.StatusOK || realm != "" {
t.Fatalf("unexpected auth result status=%d realm=%q", status, realm) t.Fatalf("unexpected auth result status=%d realm=%q", status, realm)
} }
@@ -203,7 +203,7 @@ func TestHttpAuthConfigAuthenticateRejectsInvalidBase64(t *testing.T) {
req.RemoteAddr = "127.0.0.1:12345" req.RemoteAddr = "127.0.0.1:12345"
req.Header.Set("X-Auth-Username", "%%%") req.Header.Set("X-Auth-Username", "%%%")
status, _ = auth.Authenticate(req) status, _ = auth.Authenticate(req, "")
if status != http.StatusBadRequest { if status != http.StatusBadRequest {
t.Fatalf("expected bad request for invalid header encoding, got %d", status) t.Fatalf("expected bad request for invalid header encoding, got %d", status)
} }
@@ -226,7 +226,7 @@ func TestHttpAuthConfigAccessRuleReject(t *testing.T) {
req = httptest.NewRequest(http.MethodGet, "http://example.com/blocked/path", nil) req = httptest.NewRequest(http.MethodGet, "http://example.com/blocked/path", nil)
req.RemoteAddr = "127.0.0.1:12345" req.RemoteAddr = "127.0.0.1:12345"
status, _ = auth.Authenticate(req) status, _ = auth.Authenticate(req, "")
if status != http.StatusForbidden { if status != http.StatusForbidden {
t.Fatalf("expected forbidden status, got %d", status) t.Fatalf("expected forbidden status, got %d", status)
} }
+1 -1
View File
@@ -3,7 +3,7 @@
b64() { openssl base64 -e -A | tr '+/' '-_' | tr -d '='; } b64() { openssl base64 -e -A | tr '+/' '-_' | tr -d '='; }
usage() { usage() {
echo "Usage: $0 generate key-file [ttl-seconds]" >&2 echo "Usage: $0 generate private-key-file [ttl-seconds]" >&2
exit 1 exit 1
} }
+2 -2
View File
@@ -10,8 +10,8 @@ function fail(string $msg): void {
function usage(): void { function usage(): void {
fail( fail(
"USAGE: rsa-aes-256-gcm.php encipher key-file token [ttl-seconds]\n" . "USAGE: rsa-aes-256-gcm.php encipher public-key-file text-to-encipher [ttl-seconds]\n" .
" rsa-aes-256-gcm.php decipher key-file [document]\n\n" . " rsa-aes-256-gcm.php decipher private-key-file [document]\n\n" .
"If document is omitted, stdin is used. ttl-seconds defaults to 30." "If document is omitted, stdin is used. ttl-seconds defaults to 30."
); );
} }
+2 -2
View File
@@ -20,8 +20,8 @@ def fail(msg: str) -> None:
def usage() -> None: def usage() -> None:
fail( fail(
"USAGE: rsa-aes-256-gcm.py encipher key-file token [ttl-seconds]\n" "USAGE: rsa-aes-256-gcm.py encipher public-key-file text-to-encipher [ttl-seconds]\n"
" rsa-aes-256-gcm.py decipher key-file [document]\n\n" " rsa-aes-256-gcm.py decipher private-key-file [document]\n\n"
"If document is omitted, stdin is used. ttl-seconds defaults to 30." "If document is omitted, stdin is used. ttl-seconds defaults to 30."
) )
+116 -36
View File
@@ -1,6 +1,7 @@
package hodu package hodu
import "container/list" import "container/list"
import "crypto/rsa"
import "encoding/json" import "encoding/json"
import "fmt" import "fmt"
import "net/http" import "net/http"
@@ -22,6 +23,10 @@ type json_out_auth_token struct {
RefreshToken string `json:"refresh-token,omitempty"` RefreshToken string `json:"refresh-token,omitempty"`
} }
type json_out_client_token struct {
ClientToken string `json:"client-token"`
}
type json_out_server_conn struct { type json_out_server_conn struct {
CId ConnId `json:"conn-id"` CId ConnId `json:"conn-id"`
ServerAddr string `json:"server-addr"` ServerAddr string `json:"server-addr"`
@@ -96,6 +101,10 @@ type ServerCtl struct {
NoAuth bool // override the auth configuration if true NoAuth bool // override the auth configuration if true
} }
type server_ctl_encipher struct {
ServerCtl
}
type server_ctl_token struct { type server_ctl_token struct {
ServerCtl ServerCtl
} }
@@ -114,6 +123,7 @@ type server_ctl_server_conns_id_routes struct {
type server_ctl_server_conns_id_routes_id struct { type server_ctl_server_conns_id_routes_id struct {
ServerCtl ServerCtl
HttpAuth *HttpAuthConfig
} }
type server_ctl_server_conns_id_routes_id_peers struct { type server_ctl_server_conns_id_routes_id_peers struct {
@@ -171,41 +181,115 @@ func (ctl *ServerCtl) Authenticate(req *http.Request) (int, string) {
func (ctl *server_ctl_token) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) { func (ctl *server_ctl_token) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
var s *Server var s *Server
var q url.Values
var status_code int var status_code int
var je *json.Encoder var je *json.Encoder
var _type string
var endpoint string
var source string
var key *rsa.PrivateKey
var ttl time.Duration
var err error var err error
s = ctl.S s = ctl.S
je = json.NewEncoder(w) je = json.NewEncoder(w)
// while different combinations will return success
// meaningful combinations are as follows:
// - _ctl/token - get access token
// - _ctl/token?type=client-token&endpoint=rpx&source=abcdefg - get enciphered client token
// - _ctl/token?type=client-token&endpoint=rpty&source=abcdefg - get enciphered client token
q = req.URL.Query()
_type = q.Get("type")
endpoint = q.Get("endpoint")
source = q.Get("source")
if s.Cfg.CtlAuth == nil || !s.Cfg.CtlAuth.Enabled {
// this check may look a bit weird if endpoint is rpty or rpx
// but this request itself is coming in from the ctl endpoint
// if the ctl authentication is properly configured, i don't
// want to enable this call.
status_code = WriteJsonRespHeader(w, http.StatusForbidden)
err = fmt.Errorf("auth not enabled")
je.Encode(JsonErrmsg{Text: err.Error()})
goto oops
}
if endpoint == "rpty" {
key = s.Cfg.RptyClientTokenRsaKey
ttl = s.Cfg.RptyClientTokenTtl
} else if endpoint == "rpx" {
key = s.Cfg.RpxClientTokenRsaKey
ttl = s.Cfg.RpxClientTokenTtl
} else {
key = s.Cfg.CtlAuth.TokenRsaKey
ttl = s.Cfg.CtlAuth.TokenTtl
}
_ = key
_ = ttl
switch req.Method { switch req.Method {
case http.MethodGet: case http.MethodGet:
var jwt *JWT[ServerTokenClaim] if key == nil {
var claim ServerTokenClaim
var tok string
var now time.Time
if s.Cfg.CtlAuth == nil || !s.Cfg.CtlAuth.Enabled || s.Cfg.CtlAuth.TokenRsaKey == nil {
status_code = WriteJsonRespHeader(w, http.StatusForbidden) status_code = WriteJsonRespHeader(w, http.StatusForbidden)
err = fmt.Errorf("auth not enabled or token rsa key not set") // 'enabled' may sound weird but use this word as it's given out tot the caller
err = fmt.Errorf("token rsa key not enabled")
je.Encode(JsonErrmsg{Text: err.Error()})
goto oops
}
if ttl <= 0 {
status_code = WriteJsonRespHeader(w, http.StatusForbidden)
// 'enabled' may sound weird but use this word as it's given out tot the caller
err = fmt.Errorf("token ttl not enabled")
je.Encode(JsonErrmsg{Text: err.Error()}) je.Encode(JsonErrmsg{Text: err.Error()})
goto oops goto oops
} }
now = time.Now() if _type == "client-token" {
claim.IssuedAt = now.Unix() var enc *RSAAES
claim.ExpiresAt = now.Add(s.Cfg.CtlAuth.TokenTtl).Unix() var tok string
jwt = NewJWT(s.Cfg.CtlAuth.TokenRsaKey, &claim) var now time.Time
tok, err = jwt.SignRS256()
if err != nil {
status_code = WriteJsonRespHeader(w, http.StatusInternalServerError)
je.Encode(JsonErrmsg{Text: err.Error()})
goto oops
}
status_code = WriteJsonRespHeader(w, http.StatusOK) if source == "" {
err = je.Encode(json_out_auth_token{ AccessToken: tok }) // TODO: refresh token status_code = WriteJsonRespHeader(w, http.StatusBadRequest)
if err != nil { goto oops } err = fmt.Errorf("source text for client token not provided")
je.Encode(JsonErrmsg{Text: err.Error()})
goto oops
}
enc = NewRSAAES(key)
now = time.Now()
tok, err = enc.EncipherToken(source, now, now.Add(ttl))
if err != nil {
status_code = WriteJsonRespHeader(w, http.StatusInternalServerError)
je.Encode(JsonErrmsg{Text: err.Error()})
goto oops
}
status_code = WriteJsonRespHeader(w, http.StatusOK)
err = je.Encode(json_out_client_token{ ClientToken: tok })
if err != nil { goto oops }
} else {
var jwt *JWT[ServerTokenClaim]
var claim ServerTokenClaim
var tok string
var now time.Time
now = time.Now()
claim.IssuedAt = now.Unix()
claim.ExpiresAt = now.Add(ttl).Unix()
jwt = NewJWT(key, &claim)
tok, err = jwt.SignRS256()
if err != nil {
status_code = WriteJsonRespHeader(w, http.StatusInternalServerError)
je.Encode(JsonErrmsg{Text: err.Error()})
goto oops
}
status_code = WriteJsonRespHeader(w, http.StatusOK)
err = je.Encode(json_out_auth_token{ AccessToken: tok }) // TODO: refresh token
if err != nil { goto oops }
}
default: default:
status_code = WriteEmptyRespHeader(w, http.StatusMethodNotAllowed) status_code = WriteEmptyRespHeader(w, http.StatusMethodNotAllowed)
@@ -447,6 +531,18 @@ oops:
// ------------------------------------ // ------------------------------------
func (ctl *server_ctl_server_conns_id_routes_id) Authenticate(req *http.Request) (int, string) {
if ctl.HttpAuth != nil {
// this is kind of hack to cater for the use of this object
// from the wpx context for session-info call
return ctl.HttpAuth.Authenticate(req, "access-token")
}
// this part must be the same as ServerCtl.Authenticate
if ctl.NoAuth || ctl.S.Cfg.CtlAuth == nil { return http.StatusOK, "" }
return ctl.S.Cfg.CtlAuth.Authenticate(req, "")
}
func (ctl *server_ctl_server_conns_id_routes_id) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) { func (ctl *server_ctl_server_conns_id_routes_id) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
var s *Server var s *Server
var status_code int var status_code int
@@ -956,22 +1052,6 @@ func (ctl *server_ctl_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
// handle authentication using the first message. // handle authentication using the first message.
// end this task if authentication fails. // end this task if authentication fails.
if !ctl.NoAuth && s.Cfg.CtlAuth != nil { if !ctl.NoAuth && s.Cfg.CtlAuth != nil {
/*
var req *http.Request
req = ws.Request()
if req.Header.Get("Authorization") == "" {
var token string
token = req.FormValue("access-token") // this is an authorization token
if token != "" {
// websocket doesn't actual have extra headers except a few fixed
// ones. add "Authorization" header from the query paramerer and
// compose a fake header to reuse the same Authentication() function
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
}
}
*/
status_code, _ = s.Cfg.CtlAuth.Authenticate(ws.Request(), "access-token") status_code, _ = s.Cfg.CtlAuth.Authenticate(ws.Request(), "access-token")
if status_code != http.StatusOK { goto done } if status_code != http.StatusOK { goto done }
} }
+26 -11
View File
@@ -21,12 +21,14 @@ import "golang.org/x/sys/unix"
type server_pty_ws struct { type server_pty_ws struct {
S *Server S *Server
Id string Id string
Auth *HttpAuthConfig
ws *websocket.Conn ws *websocket.Conn
} }
type server_rpty_ws struct { type server_rpty_ws struct {
S *Server S *Server
Id string Id string
Auth *HttpAuthConfig
ws *websocket.Conn ws *websocket.Conn
} }
@@ -34,6 +36,7 @@ type server_pty_xterm_file struct {
ServerCtl ServerCtl
file string file string
mode string mode string
auth *HttpAuthConfig
} }
// ------------------------------------------------------ // ------------------------------------------------------
@@ -45,6 +48,7 @@ func (pty *server_pty_ws) Identity() string {
func (pty *server_pty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) { func (pty *server_pty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
var s *Server var s *Server
var req *http.Request var req *http.Request
var auth *HttpAuthConfig
//var username string //var username string
//var password string //var password string
var in *os.File var in *os.File
@@ -61,13 +65,14 @@ func (pty *server_pty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
// handle authentication using the first message. // handle authentication using the first message.
// end this task if authentication fails. // end this task if authentication fails.
if s.Cfg.CtlAuth != nil { auth = pty.Auth
if auth == nil { auth = s.Cfg.CtlAuth }
if auth != nil {
var status_code int var status_code int
var msg string status_code, _ = auth.Authenticate(req, "access-token")
status_code, msg = s.Cfg.CtlAuth.Authenticate(req, "access-token")
if status_code != http.StatusOK { if status_code != http.StatusOK {
ws.Close() ws.Close()
return status_code, fmt.Errorf("failed to authenticate - %s", msg) return status_code, fmt.Errorf("failed to authenticate")
} }
} }
@@ -316,6 +321,7 @@ func (rpty *server_rpty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
var req *http.Request var req *http.Request
var token string var token string
var cts *ServerConn var cts *ServerConn
var auth *HttpAuthConfig
//var username string //var username string
//var password string //var password string
var rp *ServerRpty var rp *ServerRpty
@@ -327,13 +333,14 @@ func (rpty *server_rpty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
// handle authentication using the first message. // handle authentication using the first message.
// end this task if authentication fails. // end this task if authentication fails.
if s.Cfg.CtlAuth != nil { auth = rpty.Auth
if auth == nil { auth = s.Cfg.CtlAuth }
if auth != nil {
var status_code int var status_code int
var msg string status_code, _ = auth.Authenticate(req, "access-token")
status_code, msg = s.Cfg.CtlAuth.Authenticate(req, "access-token")
if status_code != http.StatusOK { if status_code != http.StatusOK {
ws.Close() ws.Close()
return status_code, fmt.Errorf("failed to authenticate - %s", msg) return status_code, fmt.Errorf("failed to authenticate")
} }
} }
@@ -347,6 +354,11 @@ func (rpty *server_rpty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
var rsa_aes *RSAAES var rsa_aes *RSAAES
var rsa_token *RSAAESToken var rsa_token *RSAAESToken
if s.Cfg.RptyClientTokenRsaKey == nil {
s.log.Write(rpty.Id, LOG_WARN, "[%s] Failed to decrypt protected client token [%s] - no rpty client token rsa key for %s", req.RemoteAddr, token, s.Cfg.RptyClientTokenProtection)
return http.StatusInternalServerError, fmt.Errorf("client token decryption failure - %s", token)
}
rsa_aes = NewRSAAES(s.Cfg.RptyClientTokenRsaKey) rsa_aes = NewRSAAES(s.Cfg.RptyClientTokenRsaKey)
rsa_token, err = rsa_aes.DecipherToken(token, time.Now()) rsa_token, err = rsa_aes.DecipherToken(token, time.Now())
if err != nil { if err != nil {
@@ -450,13 +462,16 @@ done:
func (pty *server_pty_xterm_file) Authenticate(req *http.Request) (int, string) { func (pty *server_pty_xterm_file) Authenticate(req *http.Request) (int, string) {
// The parent method ServerCtl.Authenticate() applies // The parent method ServerCtl.Authenticate() applies
// authentication to all resources. i want to exclude some files. // authentication to all resources. i want to exclude some files.
var auth *HttpAuthConfig
if pty.file == "xterm.html" { auth = pty.auth
if auth == nil { auth = pty.S.Cfg.CtlAuth }
if auth != nil && pty.file == "xterm.html" {
// this is not a real api call. but at least for xterm.html, // this is not a real api call. but at least for xterm.html,
// i don't bypass authentication and and in addition, // i don't bypass authentication and and in addition,
// i check the value of the "access-token" parameter for // i check the value of the "access-token" parameter for
// jwt authentication if it exists // jwt authentication if it exists
return pty.S.Cfg.CtlAuth.Authenticate(req, "access-token") return auth.Authenticate(req, "access-token")
} }
// you can download other files without authentication // you can download other files without authentication
@@ -518,7 +533,7 @@ func (pty *server_pty_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Req
default: default:
if strings.HasPrefix(pty.file, "_redir:") { if strings.HasPrefix(pty.file, "_redir:") {
status_code = http.StatusMovedPermanently status_code = http.StatusMovedPermanently
w.Header().Set("Location", pty.file[7:]) w.Header().Set("Location", append_raw_query(pty.file[7:], req))
w.WriteHeader(status_code) w.WriteHeader(status_code)
} else { } else {
status_code = WriteEmptyRespHeader(w, http.StatusNotFound) status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
+21 -2
View File
@@ -35,6 +35,7 @@ type server_pxy_http_main struct {
type server_pxy_xterm_file struct { type server_pxy_xterm_file struct {
server_pxy server_pxy
file string file string
HttpAuth* HttpAuthConfig
} }
type server_pxy_http_wpx struct { type server_pxy_http_wpx struct {
@@ -187,6 +188,7 @@ func (pxy *server_pxy) Cors(req *http.Request) bool {
} }
func (pxy *server_pxy) Authenticate(req *http.Request) (int, string) { func (pxy *server_pxy) Authenticate(req *http.Request) (int, string) {
// no authentication by default
return http.StatusOK, "" return http.StatusOK, ""
} }
@@ -464,6 +466,12 @@ func (pxy *server_pxy_http_wpx) ServeHTTP(w http.ResponseWriter, req *http.Reque
// return status_code, err // return status_code, err
} }
// ------------------------------------ // ------------------------------------
func (pxy *server_pxy_xterm_file) Authenticate(req *http.Request) (int, string) {
if pxy.HttpAuth != nil && pxy.file == "xterm.html" {
return pxy.HttpAuth.Authenticate(req, "access-token")
}
return http.StatusOK, ""
}
func (pxy *server_pxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) { func (pxy *server_pxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Request) (int, error) {
var s *Server var s *Server
@@ -546,7 +554,7 @@ func (pxy *server_pxy_xterm_file) ServeHTTP(w http.ResponseWriter, req *http.Req
default: default:
if strings.HasPrefix(pxy.file, "_redir:") { if strings.HasPrefix(pxy.file, "_redir:") {
status_code = http.StatusMovedPermanently status_code = http.StatusMovedPermanently
w.Header().Set("Location", pxy.file[7:]) w.Header().Set("Location", append_raw_query(pxy.file[7:], req))
w.WriteHeader(status_code) w.WriteHeader(status_code)
} else { } else {
status_code = WriteEmptyRespHeader(w, http.StatusNotFound) status_code = WriteEmptyRespHeader(w, http.StatusNotFound)
@@ -666,6 +674,7 @@ type server_pxy_ssh_ws struct {
S *Server S *Server
ws *websocket.Conn ws *websocket.Conn
Id string Id string
HttpAuth* HttpAuthConfig
} }
func (pxy *server_pxy_ssh_ws) Identity() string { func (pxy *server_pxy_ssh_ws) Identity() string {
@@ -841,8 +850,18 @@ func (pxy *server_pxy_ssh_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
s = pxy.S s = pxy.S
req = ws.Request() req = ws.Request()
ssh_state.init() ssh_state.init()
// server_pxy_ssh_ws is instantiated from two different contexts - pxy and wpx
// use the provided HttpAuth value for each instantiation rather than accessing
// the server configuration directly.
if pxy.HttpAuth != nil {
var status_code int
status_code, _ = pxy.HttpAuth.Authenticate(ws.Request(), "access-token")
if status_code != http.StatusOK { goto done }
}
port_id = req.PathValue("port_id") port_id = req.PathValue("port_id")
conn_id = req.PathValue("conn_id") conn_id = req.PathValue("conn_id")
route_id = req.PathValue("route_id") route_id = req.PathValue("route_id")
@@ -862,7 +881,7 @@ func (pxy *server_pxy_ssh_ws) ServeWebsocket(ws *websocket.Conn) (int, error) {
} }
// [SUPER-IMPORTANT!!] // [SUPER-IMPORTANT!!]
// create a fake server route. this is not a compleete structure. // create a fake server route. this is not a complete structure.
// some pointer fields are nil. extra care needs to be taken // some pointer fields are nil. extra care needs to be taken
// below to ensure it doesn't access undesired fields when exitending // below to ensure it doesn't access undesired fields when exitending
// code further // code further
+5
View File
@@ -45,6 +45,11 @@ func (rpx *server_rpx) get_client_token(req *http.Request) string {
} }
if strings.EqualFold(rpx.S.Cfg.RpxClientTokenProtection, "rsa-aes-256-gcm") { if strings.EqualFold(rpx.S.Cfg.RpxClientTokenProtection, "rsa-aes-256-gcm") {
if rpx.S.Cfg.RpxClientTokenRsaKey == nil {
rpx.S.log.Write(rpx.Id, LOG_WARN, "[%s] Failed to decrypt protected client token [%s] - no rpx client token rsa key for %s", req.RemoteAddr, val, rpx.S.Cfg.RpxClientTokenProtection)
return ""
}
rsa_aes = NewRSAAES(rpx.S.Cfg.RpxClientTokenRsaKey) rsa_aes = NewRSAAES(rpx.S.Cfg.RpxClientTokenRsaKey)
token, err = rsa_aes.DecipherToken(val, time.Now()) token, err = rsa_aes.DecipherToken(val, time.Now())
if err != nil { if err != nil {
+144 -86
View File
@@ -35,6 +35,7 @@ const CTS_LIMIT int = 16384
type PortId uint16 type PortId uint16
const PORT_ID_MARKER string = "_" const PORT_ID_MARKER string = "_"
const HS_ID_CTL string = "ctl" const HS_ID_CTL string = "ctl"
const HS_ID_ECT string = "ect"
const HS_ID_RPX string = "rpx" const HS_ID_RPX string = "rpx"
const HS_ID_PXY string = "pxy" const HS_ID_PXY string = "pxy"
const HS_ID_WPX string = "wpx" const HS_ID_WPX string = "wpx"
@@ -80,8 +81,13 @@ type ServerConfig struct {
CtlAuth *HttpAuthConfig CtlAuth *HttpAuthConfig
CtlCors bool CtlCors bool
EctAddrs []string
EctTls *tls.Config
EctAuth *HttpAuthConfig
RptyClientTokenProtection string RptyClientTokenProtection string
RptyClientTokenRsaKey *rsa.PrivateKey RptyClientTokenRsaKey *rsa.PrivateKey
RptyClientTokenTtl time.Duration
RpxAddrs []string RpxAddrs []string
RpxTls *tls.Config RpxTls *tls.Config
@@ -90,13 +96,16 @@ type ServerConfig struct {
RpxClientTokenRsaKey *rsa.PrivateKey RpxClientTokenRsaKey *rsa.PrivateKey
RpxClientTokenRegex *regexp.Regexp RpxClientTokenRegex *regexp.Regexp
RpxClientTokenSubmatchIndex int RpxClientTokenSubmatchIndex int
RpxClientTokenTtl time.Duration
PxyAddrs []string PxyAddrs []string
PxyTls *tls.Config PxyTls *tls.Config
PxyTargetTls *tls.Config PxyTargetTls *tls.Config
PxyAuth *HttpAuthConfig
WpxAddrs []string WpxAddrs []string
WpxTls *tls.Config WpxTls *tls.Config
WpxAuth *HttpAuthConfig
} }
type ServerEventKind int type ServerEventKind int
@@ -158,6 +167,11 @@ type Server struct {
ctl_addrs_mtx sync.Mutex ctl_addrs_mtx sync.Mutex
ctl_addrs *list.List // of net.Addr ctl_addrs *list.List // of net.Addr
ect_mux *http.ServeMux
ect []*http.Server // control server
ect_addrs_mtx sync.Mutex
ect_addrs *list.List // of net.Addr
rpc []*net.TCPListener // main listener for grpc rpc []*net.TCPListener // main listener for grpc
rpc_wg sync.WaitGroup rpc_wg sync.WaitGroup
rpc_svr *grpc.Server rpc_svr *grpc.Server
@@ -1387,7 +1401,7 @@ func (hlw *server_http_log_writer) Write(p []byte) (n int, err error) {
// the standard http.Server always requires *log.Logger // the standard http.Server always requires *log.Logger
// use this iowriter to create a logger to pass it to the http server. // use this iowriter to create a logger to pass it to the http server.
// since this is another log write wrapper, give adjustment value // since this is another log write wrapper, give adjustment value
hlw.svr.log.WriteWithCallDepth(hlw.id, LOG_INFO, hlw.depth, string(p)) hlw.svr.log.WriteWithCallDepth(hlw.id, LOG_INFO, hlw.depth, "%s", string(p))
return len(p), nil return len(p), nil
} }
@@ -1516,6 +1530,7 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi
var hs_max_header_bytes int var hs_max_header_bytes int
var hs_base_ctx func(net.Listener) context.Context var hs_base_ctx func(net.Listener) context.Context
var hs_log_ctl *log.Logger var hs_log_ctl *log.Logger
var hs_log_ect *log.Logger
var hs_log_rpx *log.Logger var hs_log_rpx *log.Logger
var hs_log_pxy *log.Logger var hs_log_pxy *log.Logger
var hs_log_wpx *log.Logger var hs_log_wpx *log.Logger
@@ -1564,6 +1579,7 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi
s.bulletin = NewBulletin[*ServerEvent](&s, 1024) s.bulletin = NewBulletin[*ServerEvent](&s, 1024)
s.ctl_addrs = list.New() s.ctl_addrs = list.New()
s.ect_addrs = list.New()
s.rpx_addrs = list.New() s.rpx_addrs = list.New()
s.pxy_addrs = list.New() s.pxy_addrs = list.New()
s.wpx_addrs = list.New() s.wpx_addrs = list.New()
@@ -1593,10 +1609,11 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi
// if s.Ctx is cancelled, i like all in-flight requests to be cancelled as well // if s.Ctx is cancelled, i like all in-flight requests to be cancelled as well
hs_base_ctx = func(_ net.Listener) context.Context { return s.Ctx } hs_base_ctx = func(_ net.Listener) context.Context { return s.Ctx }
hs_log_ctl = log.New(&server_http_log_writer{svr: &s, id: "ctl", depth: +2}, "", 0) hs_log_ctl = log.New(&server_http_log_writer{svr: &s, id: HS_ID_CTL, depth: +2}, "", 0)
hs_log_rpx = log.New(&server_http_log_writer{svr: &s, id: "rpx", depth: +2}, "", 0) hs_log_ect = log.New(&server_http_log_writer{svr: &s, id: HS_ID_ECT, depth: +2}, "", 0)
hs_log_pxy = log.New(&server_http_log_writer{svr: &s, id: "pxy", depth: +2}, "", 0) hs_log_rpx = log.New(&server_http_log_writer{svr: &s, id: HS_ID_RPX, depth: +2}, "", 0)
hs_log_wpx = log.New(&server_http_log_writer{svr: &s, id: "wpx", depth: +2}, "", 0) hs_log_pxy = log.New(&server_http_log_writer{svr: &s, id: HS_ID_PXY, depth: +2}, "", 0)
hs_log_wpx = log.New(&server_http_log_writer{svr: &s, id: HS_ID_WPX, depth: +2}, "", 0)
// --------------------------------------------------------- // ---------------------------------------------------------
@@ -1609,7 +1626,8 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi
s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/server-conns/{conn_id}/routes", s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/server-conns/{conn_id}/routes",
s.WrapHttpHandler(&server_ctl_server_conns_id_routes{ServerCtl{S: &s, Id: HS_ID_CTL}})) s.WrapHttpHandler(&server_ctl_server_conns_id_routes{ServerCtl{S: &s, Id: HS_ID_CTL}}))
s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/server-conns/{conn_id}/routes/{route_id}", s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/server-conns/{conn_id}/routes/{route_id}",
s.WrapHttpHandler(&server_ctl_server_conns_id_routes_id{ServerCtl{S: &s, Id: HS_ID_CTL}})) s.WrapHttpHandler(&server_ctl_server_conns_id_routes_id{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, HttpAuth: nil}))
s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/server-conns/{conn_id}/routes/{route_id}/peers", s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/server-conns/{conn_id}/routes/{route_id}/peers",
s.WrapHttpHandler(&server_ctl_server_conns_id_routes_id_peers{ServerCtl{S: &s, Id: HS_ID_CTL}})) s.WrapHttpHandler(&server_ctl_server_conns_id_routes_id_peers{ServerCtl{S: &s, Id: HS_ID_CTL}}))
s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/server-conns/{conn_id}/routes/{route_id}/peers/{peer_id}", s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/server-conns/{conn_id}/routes/{route_id}/peers/{peer_id}",
@@ -1636,61 +1654,29 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi
s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/metrics", s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/metrics",
promhttp.HandlerFor(s.promreg, promhttp.HandlerOpts{ EnableOpenMetrics: true })) promhttp.HandlerFor(s.promreg, promhttp.HandlerOpts{ EnableOpenMetrics: true }))
s.ctl_mux.Handle("/_ctl/events", s.ctl_mux.Handle(s.Cfg.CtlPrefix + "/_ctl/events",
s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_ctl_ws{ServerCtl{S: &s, Id: HS_ID_CTL}}))) s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_ctl_ws{ServerCtl{S: &s, Id: HS_ID_CTL}})))
// [NOTE]
// s.cfg.CtlPrefix applies to "/_ctl/" something only
// other endpoins below don't begin with /_ctl. so the prefix doesn't apply.
s.ctl_mux.Handle("/_pty/ws", s.ctl_mux.Handle("/_pty/ws",
s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_pty_ws{S: &s, Id: HS_ID_CTL}))) s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_pty_ws{S: &s, Id: HS_ID_CTL})))
/* Not needed any more as xterm.html bundles everything
s.ctl_mux.Handle("/_pty/xterm.js",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm.js"}))
s.ctl_mux.Handle("/_pty/xterm.js/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"}))
s.ctl_mux.Handle("/_pty/xterm-addon-fit.js",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm-addon-fit.js"}))
s.ctl_mux.Handle("/_pty/xterm-addon-fit.js/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"}))
s.ctl_mux.Handle("/_pty/xterm-addon-unicode11.js",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm-addon-unicode11.js"}))
s.ctl_mux.Handle("/_pty/xterm-addon-unicode11.js/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"}))
s.ctl_mux.Handle("/_pty/xterm.css",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm.css"}))
s.ctl_mux.Handle("/_pty/xterm.css/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"}))
*/
s.ctl_mux.Handle("/_pty/xterm.html", s.ctl_mux.Handle("/_pty/xterm.html",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm.html", mode: "pty"})) s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm.html", mode: "pty"}))
s.ctl_mux.Handle("/_pty/xterm.html/", s.ctl_mux.Handle("/_pty/xterm.html/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"})) s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"}))
s.ctl_mux.Handle("/_pty/", s.ctl_mux.Handle("/_pty/{$}",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_redir:xterm.html"})) s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_redir:xterm.html"}))
s.ctl_mux.Handle("/_rpty/ws", s.ctl_mux.Handle("/_rpty/ws",
s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_rpty_ws{S: &s, Id: HS_ID_CTL}))) s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_rpty_ws{S: &s, Id: HS_ID_CTL})))
/* Not needed any more as xterm.html bundles everything
s.ctl_mux.Handle("/_rpty/xterm.js",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm.js"}))
s.ctl_mux.Handle("/_rpty/xterm.js/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"}))
s.ctl_mux.Handle("/_rpty/xterm-addon-fit.js",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm-addon-fit.js"}))
s.ctl_mux.Handle("/_rpty/xterm-addon-fit.js/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"}))
s.ctl_mux.Handle("/_rpty/xterm-addon-unicode11.js",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm-addon-unicode11.js"}))
s.ctl_mux.Handle("/_rpty/xterm-addon-unicode11.js/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"}))
s.ctl_mux.Handle("/_rpty/xterm.css",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm.css"}))
s.ctl_mux.Handle("/_rpty/xterm.css/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"}))
*/
s.ctl_mux.Handle("/_rpty/xterm.html", s.ctl_mux.Handle("/_rpty/xterm.html",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm.html", mode: "rpty"})) s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "xterm.html", mode: "rpty"}))
s.ctl_mux.Handle("/_rpty/xterm.html/", s.ctl_mux.Handle("/_rpty/xterm.html/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"})) s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_forbidden"}))
s.ctl_mux.Handle("/_rpty/", s.ctl_mux.Handle("/_rpty/{$}",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_redir:xterm.html"})) s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_CTL}, file: "_redir:xterm.html"}))
s.ctl_mux.Handle("/_rxc", s.ctl_mux.Handle("/_rxc",
@@ -1724,6 +1710,43 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi
} }
} }
// ---------------------------------------------------------
s.ect_mux = http.NewServeMux()
// make some possibly external services accessible on others ports for convenience
s.ect_mux.Handle("/_pty/ws",
s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_pty_ws{S: &s, Id: HS_ID_ECT, Auth: s.Cfg.EctAuth})))
s.ect_mux.Handle("/_pty/xterm.html",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_ECT}, file: "xterm.html", mode: "pty", auth: s.Cfg.EctAuth})) // override the auth field to not use s.Cfg.CtlAuth
s.ect_mux.Handle("/_pty/xterm.html/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_ECT}, file: "_forbidden"}))
s.ect_mux.Handle("/_pty/{$}",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_ECT}, file: "_redir:xterm.html"}))
s.ect_mux.Handle("/_rpty/ws",
s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_rpty_ws{S: &s, Id: HS_ID_ECT, Auth: s.Cfg.EctAuth})))
s.ect_mux.Handle("/_rpty/xterm.html",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_ECT}, file: "xterm.html", mode: "rpty", auth: s.Cfg.EctAuth})) // override the auth field to not use s.Cfg.CtlAuth
s.ect_mux.Handle("/_rpty/xterm.html/",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_ECT}, file: "_forbidden"}))
s.ect_mux.Handle("/_rpty/{$}",
s.WrapHttpHandler(&server_pty_xterm_file{ServerCtl: ServerCtl{S: &s, Id: HS_ID_ECT}, file: "_redir:xterm.html"}))
s.ect = make([]*http.Server, len(cfg.EctAddrs))
for i = 0; i < len(cfg.EctAddrs); i++ {
s.ect[i] = &http.Server{
Addr: cfg.EctAddrs[i],
Handler: s.ect_mux,
TLSConfig: cfg.EctTls.Clone(),
ReadHeaderTimeout: hs_read_header_timeout,
IdleTimeout: hs_idle_timeout,
MaxHeaderBytes: hs_max_header_bytes,
BaseContext: hs_base_ctx,
ErrorLog: hs_log_ect,
// TODO: more settings
}
}
// --------------------------------------------------------- // ---------------------------------------------------------
s.rpx_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh/ws,_http configurable... s.rpx_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh/ws,_http configurable...
@@ -1751,34 +1774,17 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi
s.pxy_mux.Handle("/_ssh/{conn_id}/", s.pxy_mux.Handle("/_ssh/{conn_id}/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_redirect"})) s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_redirect"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/", s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/{$}",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_redir:xterm.html"})) s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_redir:xterm.html"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm.html", s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm.html",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "xterm.html"})) s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "xterm.html", HttpAuth: s.Cfg.PxyAuth}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm.html/", s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm.html/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_forbidden"})) s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_forbidden"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm.css",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "xterm.css"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm.css/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_forbidden"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm.js",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "xterm.js"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm.js/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_forbidden"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm-addon-fit.js",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "xterm-addon-fit.js"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm-addon-fit.js/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_forbidden"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm-addon-unicode11.js",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "xterm-addon-unicode11.js"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/xterm-addon-unicode11.js/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_forbidden"}))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/ws", s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/ws",
s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_pxy_ssh_ws{S: &s, Id: HS_ID_PXY}))) s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_pxy_ssh_ws{S: &s, Id: HS_ID_PXY, HttpAuth: s.Cfg.PxyAuth})))
s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/session-info", s.pxy_mux.Handle("/_ssh/{conn_id}/{route_id}/session-info",
s.WrapHttpHandler(&server_ctl_server_conns_id_routes_id{ServerCtl{S: &s, Id: HS_ID_PXY, NoAuth: true}})) s.WrapHttpHandler(&server_ctl_server_conns_id_routes_id{ServerCtl: ServerCtl{S: &s, Id: HS_ID_PXY, NoAuth: true}, HttpAuth: s.Cfg.PxyAuth}))
s.pxy_mux.Handle("/_ssh/", s.pxy_mux.Handle("/_ssh/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_forbidden"})) s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_PXY}, file: "_forbidden"}))
s.pxy_mux.Handle("/favicon.ico", s.pxy_mux.Handle("/favicon.ico",
@@ -1809,33 +1815,17 @@ func NewServer(ctx context.Context, name string, logger Logger, cfg *ServerConfi
s.wpx_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh/ws,_http configurable... s.wpx_mux = http.NewServeMux() // TODO: make /_init,_ssh,_ssh/ws,_http configurable...
s.wpx_mux.Handle("/_ssh/{port_id}/", s.wpx_mux.Handle("/_ssh/{port_id}/{$}",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "_redir:xterm.html"})) s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "_redir:xterm.html"}))
s.wpx_mux.Handle("/_ssh/{port_id}/xterm.html", s.wpx_mux.Handle("/_ssh/{port_id}/xterm.html",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "xterm.html"})) s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "xterm.html", HttpAuth: s.Cfg.WpxAuth}))
s.wpx_mux.Handle("/_ssh/{port_id}/xterm.html/", s.wpx_mux.Handle("/_ssh/{port_id}/xterm.html/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "_forbidden"})) s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "_forbidden"}))
s.wpx_mux.Handle("/_ssh/{port_id}/xterm.css",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "xterm.css"}))
s.wpx_mux.Handle("/_ssh/{port_id}/xterm.css/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "_forbidden"}))
s.wpx_mux.Handle("/_ssh/{port_id}/xterm.js",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "xterm.js"}))
s.wpx_mux.Handle("/_ssh/{port_id}/xterm.js/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "_forbidden"}))
s.wpx_mux.Handle("/_ssh/{port_id}/xterm-addon-fit.js",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "xterm-addon-fit.js"}))
s.wpx_mux.Handle("/_ssh/{port_id}/xterm-addon-fit.js/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "_forbidden"}))
s.wpx_mux.Handle("/_ssh/{port_id}/xterm-addon-unicode11.js",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "xterm-addon-unicode11.js"}))
s.wpx_mux.Handle("/_ssh/{port_id}/xterm-addon-unicode11.js/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "_forbidden"}))
s.wpx_mux.Handle("/_ssh/{port_id}/ws", s.wpx_mux.Handle("/_ssh/{port_id}/ws",
s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_pxy_ssh_ws{S: &s, Id: HS_ID_WPX}))) s.SafeWrapWebsocketHandler(s.WrapWebsocketHandler(&server_pxy_ssh_ws{S: &s, Id: HS_ID_WPX, HttpAuth: s.Cfg.WpxAuth })))
s.wpx_mux.Handle("/_ssh/{port_id}/session-info", s.wpx_mux.Handle("/_ssh/{port_id}/session-info",
s.WrapHttpHandler(&server_ctl_server_conns_id_routes_id{ServerCtl{S: &s, Id: HS_ID_WPX, NoAuth: true}})) s.WrapHttpHandler(&server_ctl_server_conns_id_routes_id{ServerCtl: ServerCtl{S: &s, Id: HS_ID_WPX, NoAuth: true}, HttpAuth: s.Cfg.WpxAuth}))
s.wpx_mux.Handle("/_ssh/", s.wpx_mux.Handle("/_ssh/",
s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "_forbidden"})) s.WrapHttpHandler(&server_pxy_xterm_file{server_pxy: server_pxy{S: &s, Id: HS_ID_WPX}, file: "_forbidden"}))
@@ -2029,6 +2019,65 @@ func (s *Server) RunCtlTask(wg *sync.WaitGroup) {
l_wg.Wait() l_wg.Wait()
} }
func (s* Server) run_single_ect_server(i int, cs *http.Server, wg* sync.WaitGroup) {
var l net.Listener
var err error
defer wg.Done()
s.log.Write("", LOG_INFO, "Limited external control channel[%d] started on %s", i, cs.Addr)
if s.stop_req.Load() == false {
// defeat hard-coded "tcp" in ListenAndServe() and ListenAndServeTLS()
// err = cs.ListenAndServe()
// err = cs.ListenAndServeTLS("", "")
l, err = net.Listen(TcpAddrStrClass(cs.Addr), cs.Addr)
if err == nil {
if s.stop_req.Load() == false {
var node *list.Element
s.ect_addrs_mtx.Lock()
node = s.ect_addrs.PushBack(l.Addr().(*net.TCPAddr))
s.ect_addrs_mtx.Unlock()
if s.Cfg.EctTls == nil {
err = cs.Serve(l)
} else {
err = cs.ServeTLS(l, "", "") // s.Cfg.EctTls must provide a certificate and a key
}
s.ect_addrs_mtx.Lock()
s.ect_addrs.Remove(node)
s.ect_addrs_mtx.Unlock()
} else {
err = fmt.Errorf("stop requested")
}
l.Close()
}
} else {
err = fmt.Errorf("stop requested")
}
if err == nil || errors.Is(err, http.ErrServerClosed) {
s.log.Write("", LOG_INFO, "Limited external control channel[%d] ended", i)
} else {
s.log.Write("", LOG_ERROR, "Limited external control channel[%d] error - %s", i, err.Error())
}
}
func (s *Server) RunEctTask(wg *sync.WaitGroup) {
var ect *http.Server
var idx int
var l_wg sync.WaitGroup
defer wg.Done()
for idx, ect = range s.ect {
l_wg.Add(1)
go s.run_single_ect_server(idx, ect, &l_wg);
}
l_wg.Wait()
}
func (s *Server) run_single_rpx_server(i int, cs *http.Server, wg* sync.WaitGroup) { func (s *Server) run_single_rpx_server(i int, cs *http.Server, wg* sync.WaitGroup) {
var l net.Listener var l net.Listener
var err error var err error
@@ -2216,6 +2265,10 @@ func (s *Server) ReqStop() {
hs.Shutdown(s.Ctx) // to break s.ctl.Serve() hs.Shutdown(s.Ctx) // to break s.ctl.Serve()
} }
for _, hs = range s.ect {
hs.Shutdown(s.Ctx) // to break s.ect.Serve()
}
for _, hs = range s.rpx { for _, hs = range s.rpx {
hs.Shutdown(s.Ctx) // to break s.rpx.Serve() hs.Shutdown(s.Ctx) // to break s.rpx.Serve()
} }
@@ -2650,6 +2703,11 @@ func (s *Server) StartCtlService() {
go s.RunCtlTask(&s.wg) go s.RunCtlTask(&s.wg)
} }
func (s *Server) StartEctService() {
s.wg.Add(1)
go s.RunEctTask(&s.wg)
}
func (s *Server) StartRpxService() { func (s *Server) StartRpxService() {
s.wg.Add(1) s.wg.Add(1)
go s.RunRpxTask(&s.wg) go s.RunRpxTask(&s.wg)
+16 -15
View File
@@ -47,11 +47,6 @@ window.onload = function(event) {
const fit_addon = new FitAddon(); const fit_addon = new FitAddon();
const unicode11_addon = new Unicode11Addon(); const unicode11_addon = new Unicode11Addon();
const text_decoder = new TextDecoder(); const text_decoder = new TextDecoder();
let set_terminal_target;
let set_terminal_status;
let adjust_terminal_size_unconnected;
let fetch_session_info;
let toggle_login_form;
void event; void event;
@@ -72,12 +67,12 @@ window.onload = function(event) {
term.unicode.activeVersion = "11"; term.unicode.activeVersion = "11";
term.open(terminal_view_container); term.open(terminal_view_container);
set_terminal_target = function(name) { const set_terminal_target = function(name) {
terminal_target.innerText = name; terminal_target.innerText = name;
login_form_title.innerText = name; login_form_title.innerText = name;
}; };
set_terminal_status = function(msg, errmsg) { const set_terminal_status = function(msg, errmsg) {
if (msg != null) terminal_status.innerText = msg; if (msg != null) terminal_status.innerText = msg;
if (errmsg != null) { if (errmsg != null) {
if (errmsg != "") { if (errmsg != "") {
@@ -89,17 +84,24 @@ window.onload = function(event) {
} }
}; };
adjust_terminal_size_unconnected = function() { const adjust_terminal_size_unconnected = function() {
fit_addon.fit(); fit_addon.fit();
}; };
fetch_session_info = async function() { const fetch_session_info = async function() {
let url = window.location.protocol + "//" + window.location.host; let url = window.location.protocol + "//" + window.location.host;
let pathname = window.location.pathname; let pathname = window.location.pathname;
const qparams = new URLSearchParams(window.location.search);
const xparams = new URLSearchParams();
pathname = pathname.substring(0, pathname.lastIndexOf("/")); pathname = pathname.substring(0, pathname.lastIndexOf("/"));
url += pathname + "/session-info"; url += pathname + "/session-info";
const access_token = qparams.get("access-token");
if (access_token !== null && access_token != "") xparams.set("access-token", access_token);
if (xparams.size > 0) url += "?" + xparams.toString();
try { try {
const resp = await fetch(url); const resp = await fetch(url);
if (!resp.ok) { if (!resp.ok) {
@@ -120,7 +122,7 @@ window.onload = function(event) {
} }
}; };
toggle_login_form = function(visible) { const toggle_login_form = function(visible) {
if (visible && xt_mode == "ssh") fetch_session_info(); if (visible && xt_mode == "ssh") fetch_session_info();
login_container.style.visibility = (visible ? "visible" : "hidden"); login_container.style.visibility = (visible ? "visible" : "hidden");
terminal_disconnect.style.visibility = (visible ? "hidden" : "visible"); terminal_disconnect.style.visibility = (visible ? "hidden" : "visible");
@@ -156,14 +158,13 @@ window.onload = function(event) {
pathname = pathname.substring(0, pathname.lastIndexOf("/")); pathname = pathname.substring(0, pathname.lastIndexOf("/"));
url = prefix + window.location.host + pathname + "/ws"; url = prefix + window.location.host + pathname + "/ws";
const access_token = qparams.get("access-token");
if (access_token !== null && access_token != "") xparams.set("access-token", access_token);
if (xt_mode == "rpty") { if (xt_mode == "rpty") {
const access_token = qparams.get("access-token");
const client_token = qparams.get("client-token"); const client_token = qparams.get("client-token");
if (client_token !== null && client_token != "") xparams.set("client-token", client_token);
if (access_token != null && access_token != "") xparams.set("access-token", access_token);
if (client_token != null && client_token != "") xparams.set("client-token", client_token);
if (xparams.size > 0) url += "?" + xparams.toString();
} }
if (xparams.size > 0) url += "?" + xparams.toString();
socket = new WebSocket(url); socket = new WebSocket(url);
socket.binaryType = "arraybuffer"; socket.binaryType = "arraybuffer";
+6 -6
View File
File diff suppressed because one or more lines are too long