kvm/web_tls.go
Aveline 189b84380b
network enhanecment / refactor (#361)
* chore(network): improve connectivity check

* refactor(network): rewrite network and timesync component

* feat(display): show cloud connection status

* chore: change logging verbosity

* chore(websecure): update log message

* fix(ota): validate root certificate when downloading update

* feat(ui): add network settings tab

* fix(display): cloud connecting animation

* fix: golintci issues

* feat: add network settings tab

* feat(timesync): query servers in parallel

* refactor(network): move to internal/network package

* feat(timesync): add metrics

* refactor(log): move log to internal/logging package

* refactor(mdms): move mdns to internal/mdns package

* feat(developer): add pprof endpoint

* feat(logging): add a simple logging streaming endpoint

* fix(mdns): do not start mdns until network is up

* feat(network): allow users to update network settings from ui

* fix(network): handle errors when net.IPAddr is nil

* fix(mdns): scopedLogger SIGSEGV

* fix(dhcp): watch directory instead of file to catch fsnotify.Create event

* refactor(nbd): move platform-specific code to different files

* refactor(native): move platform-specific code to different files

* chore: fix linter issues

* chore(dev_deploy): allow to override PION_LOG_TRACE
2025-04-16 01:39:23 +02:00

213 lines
4.8 KiB
Go

package kvm
import (
"context"
"crypto/tls"
"encoding/pem"
"errors"
"fmt"
"net/http"
"sync"
"github.com/jetkvm/kvm/internal/websecure"
)
const (
tlsStorePath = "/userdata/jetkvm/tls"
webSecureListen = ":443"
webSecureSelfSignedDefaultDomain = "jetkvm.local"
webSecureSelfSignedCAName = "JetKVM Self-Signed CA"
webSecureSelfSignedOrganization = "JetKVM"
webSecureSelfSignedOU = "JetKVM Self-Signed"
webSecureCustomCertificateName = "user-defined"
)
var (
certStore *websecure.CertStore
certSigner *websecure.SelfSigner
)
type TLSState struct {
Mode string `json:"mode"`
Certificate string `json:"certificate"`
PrivateKey string `json:"privateKey"`
}
func initCertStore() {
if certStore != nil {
websecureLogger.Warn().Msg("TLS store already initialized, it should not be initialized again")
return
}
certStore = websecure.NewCertStore(tlsStorePath, websecureLogger)
certStore.LoadCertificates()
certSigner = websecure.NewSelfSigner(
certStore,
websecureLogger,
webSecureSelfSignedDefaultDomain,
webSecureSelfSignedOrganization,
webSecureSelfSignedOU,
webSecureSelfSignedCAName,
)
}
func getCertificate(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
switch config.TLSMode {
case "self-signed":
if isTimeSyncNeeded() || !timeSync.IsSyncSuccess() {
return nil, fmt.Errorf("time is not synced")
}
return certSigner.GetCertificate(info)
case "custom":
return certStore.GetCertificate(webSecureCustomCertificateName), nil
}
websecureLogger.Info().Msg("TLS mode is disabled but WebSecure is running, returning nil")
return nil, nil
}
func getTLSState() TLSState {
s := TLSState{}
switch config.TLSMode {
case "disabled":
s.Mode = "disabled"
case "custom":
s.Mode = "custom"
cert := certStore.GetCertificate(webSecureCustomCertificateName)
if cert != nil {
var certPEM []byte
// convert to pem format
for _, c := range cert.Certificate {
block := pem.Block{
Type: "CERTIFICATE",
Bytes: c,
}
certPEM = append(certPEM, pem.EncodeToMemory(&block)...)
}
s.Certificate = string(certPEM)
}
case "self-signed":
s.Mode = "self-signed"
}
return s
}
func setTLSState(s TLSState) error {
var isChanged = false
switch s.Mode {
case "disabled":
if config.TLSMode != "" {
isChanged = true
}
config.TLSMode = ""
case "custom":
if config.TLSMode == "" {
isChanged = true
}
// parse pem to cert and key
err, _ := certStore.ValidateAndSaveCertificate(webSecureCustomCertificateName, s.Certificate, s.PrivateKey, true)
// warn doesn't matter as ... we don't know the hostname yet
if err != nil {
return fmt.Errorf("failed to save certificate: %w", err)
}
config.TLSMode = "custom"
case "self-signed":
if config.TLSMode == "" {
isChanged = true
}
config.TLSMode = "self-signed"
default:
return fmt.Errorf("invalid TLS mode: %s", s.Mode)
}
if !isChanged {
websecureLogger.Info().Msg("TLS enabled state is not changed, not starting/stopping websecure server")
return nil
}
if config.TLSMode == "" {
websecureLogger.Info().Msg("Stopping websecure server, as TLS mode is disabled")
stopWebSecureServer()
} else {
websecureLogger.Info().Msg("Starting websecure server, as TLS mode is enabled")
startWebSecureServer()
}
return nil
}
var (
startTLS = make(chan struct{})
stopTLS = make(chan struct{})
tlsServiceLock = sync.Mutex{}
tlsStarted = false
)
// RunWebSecureServer runs a web server with TLS.
func runWebSecureServer() {
tlsServiceLock.Lock()
defer tlsServiceLock.Unlock()
tlsStarted = true
defer func() {
tlsStarted = false
}()
r := setupRouter()
server := &http.Server{
Addr: webSecureListen,
Handler: r,
TLSConfig: &tls.Config{
MaxVersion: tls.VersionTLS13,
CurvePreferences: []tls.CurveID{},
GetCertificate: getCertificate,
},
}
websecureLogger.Info().Str("listen", webSecureListen).Msg("Starting websecure server")
go func() {
for range stopTLS {
websecureLogger.Info().Msg("Shutting down websecure server")
err := server.Shutdown(context.Background())
if err != nil {
websecureLogger.Error().Err(err).Msg("failed to shutdown websecure server")
}
}
}()
err := server.ListenAndServeTLS("", "")
if !errors.Is(err, http.ErrServerClosed) {
panic(err)
}
}
func stopWebSecureServer() {
if !tlsStarted {
websecureLogger.Info().Msg("Websecure server is not running, not stopping it")
return
}
stopTLS <- struct{}{}
}
func startWebSecureServer() {
if tlsStarted {
websecureLogger.Info().Msg("Websecure server is already running, not starting it again")
return
}
startTLS <- struct{}{}
}
func RunWebSecureServer() {
for range startTLS {
websecureLogger.Info().Msg("Starting websecure server, as we have received a start signal")
if certStore == nil {
initCertStore()
}
go runWebSecureServer()
}
}