From 8b59a3e3875c096a55d3d4f8e399e2c39e8bb694 Mon Sep 17 00:00:00 2001 From: Siyuan Miao Date: Fri, 7 Mar 2025 20:01:14 +0100 Subject: [PATCH 1/2] chore(prometheus): move prometheus to a new file --- main.go | 2 ++ prometheus.go | 17 +++++++++++++++++ web.go | 5 ----- 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 prometheus.go diff --git a/main.go b/main.go index e23e9c8..a3caba7 100644 --- a/main.go +++ b/main.go @@ -36,6 +36,8 @@ func Main() { StartNativeCtrlSocketServer() StartNativeVideoSocketServer() + initPrometheus() + go func() { err = ExtractAndRunNativeBin() if err != nil { diff --git a/prometheus.go b/prometheus.go new file mode 100644 index 0000000..8ebf259 --- /dev/null +++ b/prometheus.go @@ -0,0 +1,17 @@ +package kvm + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" + versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version" + "github.com/prometheus/common/version" +) + +var promHandler http.Handler + +func initPrometheus() { + // A Prometheus metrics endpoint. + version.Version = builtAppVersion + prometheus.MustRegister(versioncollector.NewCollector("jetkvm")) +} diff --git a/web.go b/web.go index dea3e17..b35a2db 100644 --- a/web.go +++ b/web.go @@ -12,10 +12,7 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" - "github.com/prometheus/client_golang/prometheus" - versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/prometheus/common/version" "golang.org/x/crypto/bcrypt" ) @@ -90,8 +87,6 @@ func setupRouter() *gin.Engine { r.POST("/device/setup", handleSetup) // A Prometheus metrics endpoint. - version.Version = builtAppVersion - prometheus.MustRegister(versioncollector.NewCollector("jetkvm")) r.GET("/metrics", gin.WrapH(promhttp.Handler())) // Protected routes (allows both password and noPassword modes) From 285de31ade13d9eb8988842381c608092af3bc71 Mon Sep 17 00:00:00 2001 From: Siyuan Miao Date: Fri, 7 Mar 2025 20:14:22 +0100 Subject: [PATCH 2/2] feat(tls): add simple tls support --- config.go | 2 + main.go | 3 ++ web_tls.go | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 web_tls.go diff --git a/config.go b/config.go index e4e27d7..304d8f6 100644 --- a/config.go +++ b/config.go @@ -37,6 +37,7 @@ type Config struct { DisplayMaxBrightness int `json:"display_max_brightness"` DisplayDimAfterSec int `json:"display_dim_after_sec"` DisplayOffAfterSec int `json:"display_off_after_sec"` + TLSMode string `json:"tls_mode"` UsbConfig *UsbConfig `json:"usb_config"` } @@ -50,6 +51,7 @@ var defaultConfig = &Config{ DisplayMaxBrightness: 64, DisplayDimAfterSec: 120, // 2 minutes DisplayOffAfterSec: 1800, // 30 minutes + TLSMode: "", UsbConfig: &UsbConfig{ VendorId: "0x1d6b", //The Linux Foundation ProductId: "0x0104", //Multifunction Composite Gadget diff --git a/main.go b/main.go index a3caba7..2c8c22a 100644 --- a/main.go +++ b/main.go @@ -68,6 +68,9 @@ func Main() { }() //go RunFuseServer() go RunWebServer() + if config.TLSMode != "" { + go RunWebSecureServer() + } // If the cloud token isn't set, the client won't be started by default. // However, if the user adopts the device via the web interface, handleCloudRegister will start the client. if config.CloudToken != "" { diff --git a/web_tls.go b/web_tls.go new file mode 100644 index 0000000..fff9253 --- /dev/null +++ b/web_tls.go @@ -0,0 +1,132 @@ +package kvm + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "log" + "math/big" + "net" + "net/http" + "strings" + "sync" + "time" +) + +const ( + WebSecureListen = ":443" + WebSecureSelfSignedDefaultDomain = "jetkvm.local" + WebSecureSelfSignedDuration = 365 * 24 * time.Hour +) + +var ( + tlsCerts = make(map[string]*tls.Certificate) + tlsCertLock = &sync.Mutex{} +) + +// RunWebSecureServer runs a web server with TLS. +func RunWebSecureServer() { + r := setupRouter() + + server := &http.Server{ + Addr: WebSecureListen, + Handler: r, + TLSConfig: &tls.Config{ + // TODO: cache certificate in persistent storage + GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { + hostname := WebSecureSelfSignedDefaultDomain + if info.ServerName != "" { + hostname = info.ServerName + } else { + hostname = strings.Split(info.Conn.LocalAddr().String(), ":")[0] + } + + logger.Infof("TLS handshake for %s, SupportedProtos: %v", hostname, info.SupportedProtos) + + cert := createSelfSignedCert(hostname) + + return cert, nil + }, + }, + } + logger.Infof("Starting websecure server on %s", RunWebSecureServer) + err := server.ListenAndServeTLS("", "") + if err != nil { + panic(err) + } + return +} + +func createSelfSignedCert(hostname string) *tls.Certificate { + if tlsCert := tlsCerts[hostname]; tlsCert != nil { + return tlsCert + } + tlsCertLock.Lock() + defer tlsCertLock.Unlock() + + logger.Infof("Creating self-signed certificate for %s", hostname) + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + log.Fatalf("Failed to generate private key: %v", err) + } + keyUsage := x509.KeyUsageDigitalSignature + + notBefore := time.Now() + notAfter := notBefore.AddDate(1, 0, 0) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + logger.Errorf("Failed to generate serial number: %v", err) + } + + dnsName := hostname + ip := net.ParseIP(hostname) + if ip != nil { + dnsName = WebSecureSelfSignedDefaultDomain + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: hostname, + Organization: []string{"JetKVM"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: keyUsage, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + + DNSNames: []string{dnsName}, + IPAddresses: []net.IP{}, + } + + if ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + logger.Errorf("Failed to create certificate: %v", err) + } + + cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + if cert == nil { + logger.Errorf("Failed to encode certificate") + } + + tlsCert := &tls.Certificate{ + Certificate: [][]byte{derBytes}, + PrivateKey: priv, + } + tlsCerts[hostname] = tlsCert + + return tlsCert +}