Skip to content

Commit c4348c7

Browse files
authored
feat: simple TLS support (#247)
2 parents 38d6f57 + 369bd3f commit c4348c7

File tree

4 files changed

+154
-5
lines changed

4 files changed

+154
-5
lines changed

main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ func Main() {
3535
StartNativeCtrlSocketServer()
3636
StartNativeVideoSocketServer()
3737

38+
initPrometheus()
39+
3840
go func() {
3941
err = ExtractAndRunNativeBin()
4042
if err != nil {
@@ -67,6 +69,9 @@ func Main() {
6769
}()
6870
//go RunFuseServer()
6971
go RunWebServer()
72+
if config.TLSMode != "" {
73+
go RunWebSecureServer()
74+
}
7075
// If the cloud token isn't set, the client won't be started by default.
7176
// However, if the user adopts the device via the web interface, handleCloudRegister will start the client.
7277
if config.CloudToken != "" {

prometheus.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package kvm
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/prometheus/client_golang/prometheus"
7+
versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version"
8+
"github.com/prometheus/common/version"
9+
)
10+
11+
var promHandler http.Handler
12+
13+
func initPrometheus() {
14+
// A Prometheus metrics endpoint.
15+
version.Version = builtAppVersion
16+
prometheus.MustRegister(versioncollector.NewCollector("jetkvm"))
17+
}

web.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ import (
1212

1313
"github.com/gin-gonic/gin"
1414
"github.com/google/uuid"
15-
"github.com/prometheus/client_golang/prometheus"
16-
versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version"
1715
"github.com/prometheus/client_golang/prometheus/promhttp"
18-
"github.com/prometheus/common/version"
1916
"golang.org/x/crypto/bcrypt"
2017
)
2118

@@ -90,8 +87,6 @@ func setupRouter() *gin.Engine {
9087
r.POST("/device/setup", handleSetup)
9188

9289
// A Prometheus metrics endpoint.
93-
version.Version = builtAppVersion
94-
prometheus.MustRegister(versioncollector.NewCollector("jetkvm"))
9590
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
9691

9792
// Protected routes (allows both password and noPassword modes)

web_tls.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package kvm
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"crypto/rand"
7+
"crypto/tls"
8+
"crypto/x509"
9+
"crypto/x509/pkix"
10+
"encoding/pem"
11+
"log"
12+
"math/big"
13+
"net"
14+
"net/http"
15+
"strings"
16+
"sync"
17+
"time"
18+
)
19+
20+
const (
21+
WebSecureListen = ":443"
22+
WebSecureSelfSignedDefaultDomain = "jetkvm.local"
23+
WebSecureSelfSignedDuration = 365 * 24 * time.Hour
24+
)
25+
26+
var (
27+
tlsCerts = make(map[string]*tls.Certificate)
28+
tlsCertLock = &sync.Mutex{}
29+
)
30+
31+
// RunWebSecureServer runs a web server with TLS.
32+
func RunWebSecureServer() {
33+
r := setupRouter()
34+
35+
server := &http.Server{
36+
Addr: WebSecureListen,
37+
Handler: r,
38+
TLSConfig: &tls.Config{
39+
// TODO: cache certificate in persistent storage
40+
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
41+
hostname := WebSecureSelfSignedDefaultDomain
42+
if info.ServerName != "" {
43+
hostname = info.ServerName
44+
} else {
45+
hostname = strings.Split(info.Conn.LocalAddr().String(), ":")[0]
46+
}
47+
48+
logger.Infof("TLS handshake for %s, SupportedProtos: %v", hostname, info.SupportedProtos)
49+
50+
cert := createSelfSignedCert(hostname)
51+
52+
return cert, nil
53+
},
54+
},
55+
}
56+
logger.Infof("Starting websecure server on %s", RunWebSecureServer)
57+
err := server.ListenAndServeTLS("", "")
58+
if err != nil {
59+
panic(err)
60+
}
61+
return
62+
}
63+
64+
func createSelfSignedCert(hostname string) *tls.Certificate {
65+
if tlsCert := tlsCerts[hostname]; tlsCert != nil {
66+
return tlsCert
67+
}
68+
tlsCertLock.Lock()
69+
defer tlsCertLock.Unlock()
70+
71+
logger.Infof("Creating self-signed certificate for %s", hostname)
72+
73+
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
74+
if err != nil {
75+
log.Fatalf("Failed to generate private key: %v", err)
76+
}
77+
keyUsage := x509.KeyUsageDigitalSignature
78+
79+
notBefore := time.Now()
80+
notAfter := notBefore.AddDate(1, 0, 0)
81+
82+
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
83+
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
84+
if err != nil {
85+
logger.Errorf("Failed to generate serial number: %v", err)
86+
}
87+
88+
dnsName := hostname
89+
ip := net.ParseIP(hostname)
90+
if ip != nil {
91+
dnsName = WebSecureSelfSignedDefaultDomain
92+
}
93+
94+
template := x509.Certificate{
95+
SerialNumber: serialNumber,
96+
Subject: pkix.Name{
97+
CommonName: hostname,
98+
Organization: []string{"JetKVM"},
99+
},
100+
NotBefore: notBefore,
101+
NotAfter: notAfter,
102+
103+
KeyUsage: keyUsage,
104+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
105+
BasicConstraintsValid: true,
106+
107+
DNSNames: []string{dnsName},
108+
IPAddresses: []net.IP{},
109+
}
110+
111+
if ip != nil {
112+
template.IPAddresses = append(template.IPAddresses, ip)
113+
}
114+
115+
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
116+
if err != nil {
117+
logger.Errorf("Failed to create certificate: %v", err)
118+
}
119+
120+
cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
121+
if cert == nil {
122+
logger.Errorf("Failed to encode certificate")
123+
}
124+
125+
tlsCert := &tls.Certificate{
126+
Certificate: [][]byte{derBytes},
127+
PrivateKey: priv,
128+
}
129+
tlsCerts[hostname] = tlsCert
130+
131+
return tlsCert
132+
}

0 commit comments

Comments
 (0)