no longer need to reboot to start / stop tls service

This commit is contained in:
Siyuan Miao 2025-04-08 16:05:04 +02:00
parent 720c6f8951
commit ffb43da96e
5 changed files with 112 additions and 41 deletions

View File

@ -109,6 +109,8 @@ func SaveConfig() error {
configLock.Lock() configLock.Lock()
defer configLock.Unlock() defer configLock.Unlock()
logger.Tracef("Saving config to %s", configPath)
file, err := os.Create(configPath) file, err := os.Create(configPath)
if err != nil { if err != nil {
return fmt.Errorf("failed to create config file: %w", err) return fmt.Errorf("failed to create config file: %w", err)

View File

@ -405,8 +405,8 @@ func rpcGetTLSState() TLSState {
return getTLSState() return getTLSState()
} }
func rpcSetTLSState(tlsState TLSState) error { func rpcSetTLSState(state TLSState) error {
err := setTLSState(tlsState) err := setTLSState(state)
if err != nil { if err != nil {
return fmt.Errorf("failed to set TLS state: %w", err) return fmt.Errorf("failed to set TLS state: %w", err)
} }

View File

@ -70,10 +70,12 @@ func Main() {
//go RunFuseServer() //go RunFuseServer()
go RunWebServer() go RunWebServer()
go RunWebSecureServer()
// Web secure server is started only if TLS mode is enabled
if config.TLSMode != "" { if config.TLSMode != "" {
initCertStore() startWebSecureServer()
go RunWebSecureServer()
} }
// As websocket client already checks if the cloud token is set, we can start it here. // As websocket client already checks if the cloud token is set, we can start it here.
go RunWebsocketClient() go RunWebsocketClient()

View File

@ -161,7 +161,12 @@ export default function SettingsAccessIndexRoute() {
}; };
const handleTlsUpdate = useCallback(() => { const handleTlsUpdate = useCallback(() => {
send("setTLSState", { state: { mode: tlsMode, certificate: tlsCert, privateKey: tlsKey } as TLSState }, resp => { const state = { mode: tlsMode } as TLSState;
if (tlsMode !== "disabled") {
state.certificate = tlsCert;
state.privateKey = tlsKey;
}
send("setTLSState", { state }, resp => {
if ("error" in resp) { if ("error" in resp) {
notifications.error(`Failed to update TLS settings: ${resp.error.data || "Unknown error"}`); notifications.error(`Failed to update TLS settings: ${resp.error.data || "Unknown error"}`);
return; return;
@ -171,17 +176,6 @@ export default function SettingsAccessIndexRoute() {
}); });
}, [send, tlsMode, tlsCert, tlsKey]); }, [send, tlsMode, tlsCert, tlsKey]);
const handleReboot = useCallback(() => {
send("reboot", { force: false }, resp => {
if ("error" in resp) {
notifications.error(`Failed to reboot: ${resp.error.data || "Unknown error"}`);
return;
}
notifications.success("Device will restart shortly, it might take a few seconds to boot up again.");
});
}, [send]);
// Fetch device ID and cloud state on component mount // Fetch device ID and cloud state on component mount
useEffect(() => { useEffect(() => {
getCloudState(); getCloudState();
@ -211,8 +205,8 @@ export default function SettingsAccessIndexRoute() {
<SettingsItem <SettingsItem
title="HTTPS/TLS Mode" title="HTTPS/TLS Mode"
description={<> description={<>
Select the TLS mode for your device (beta)<br /> Select the TLS mode for your device <sup>experimental</sup><br />
<small>changing this setting might restart the device</small> <small>The feature might not work as expected, please report any issues if you encounter any.</small>
</>} </>}
> >
<SelectMenuBasic <SelectMenuBasic
@ -227,27 +221,6 @@ export default function SettingsAccessIndexRoute() {
/> />
</SettingsItem> </SettingsItem>
<div className="space-y-4">
<p className="text-xs text-slate-600 dark:text-slate-400">
If TLS wasn't enabled before, you'll need to <strong>reboot</strong> the device to apply the changes.<br />
</p>
<div className="flex items-center gap-x-2">
<Button
size="SM"
theme="primary"
text="Update TLS Settings"
onClick={handleTlsUpdate}
/>
<Button
size="SM"
theme="danger"
text="Reboot"
onClick={handleReboot}
/>
</div>
</div>
{tlsMode === "custom" && ( {tlsMode === "custom" && (
<div className="mt-4 space-y-4"> <div className="mt-4 space-y-4">
<div className="space-y-4"> <div className="space-y-4">
@ -282,6 +255,19 @@ export default function SettingsAccessIndexRoute() {
</div> </div>
</div> </div>
)} )}
<div className="space-y-4">
<div className="flex items-center gap-x-2">
<Button
size="SM"
theme="primary"
text="Update TLS Settings"
onClick={handleTlsUpdate}
/>
</div>
</div>
<SettingsItem <SettingsItem
title="Authentication Mode" title="Authentication Mode"
description={`Current mode: ${loaderData.authMode === "password" ? "Password protected" : "No password"}`} description={`Current mode: ${loaderData.authMode === "password" ? "Password protected" : "No password"}`}

View File

@ -1,10 +1,13 @@
package kvm package kvm
import ( import (
"context"
"crypto/tls" "crypto/tls"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"sync"
"github.com/jetkvm/kvm/internal/websecure" "github.com/jetkvm/kvm/internal/websecure"
) )
@ -31,6 +34,10 @@ type TLSState struct {
} }
func initCertStore() { func initCertStore() {
if certStore != nil {
logger.Warnf("TLS store already initialized, it should not be initialized again")
return
}
certStore = websecure.NewCertStore(tlsStorePath) certStore = websecure.NewCertStore(tlsStorePath)
certStore.LoadCertificates() certStore.LoadCertificates()
@ -87,10 +94,18 @@ func getTLSState() TLSState {
} }
func setTLSState(s TLSState) error { func setTLSState(s TLSState) error {
var isChanged = false
switch s.Mode { switch s.Mode {
case "disabled": case "disabled":
if config.TLSMode != "" {
isChanged = true
}
config.TLSMode = "" config.TLSMode = ""
case "custom": case "custom":
if config.TLSMode == "" {
isChanged = true
}
// parse pem to cert and key // parse pem to cert and key
err, _ := certStore.ValidateAndSaveCertificate(webSecureCustomCertificateName, s.Certificate, s.PrivateKey, true) err, _ := certStore.ValidateAndSaveCertificate(webSecureCustomCertificateName, s.Certificate, s.PrivateKey, true)
// warn doesn't matter as ... we don't know the hostname yet // warn doesn't matter as ... we don't know the hostname yet
@ -99,15 +114,47 @@ func setTLSState(s TLSState) error {
} }
config.TLSMode = "custom" config.TLSMode = "custom"
case "self-signed": case "self-signed":
if config.TLSMode == "" {
isChanged = true
}
config.TLSMode = "self-signed" config.TLSMode = "self-signed"
default: default:
return fmt.Errorf("invalid TLS mode: %s", s.Mode) return fmt.Errorf("invalid TLS mode: %s", s.Mode)
} }
if !isChanged {
logger.Tracef("TLS enabled state is not changed, not starting/stopping websecure server")
return nil
}
if config.TLSMode == "" {
logger.Tracef("Stopping websecure server, as TLS mode is disabled")
stopWebSecureServer()
} else {
logger.Tracef("Starting websecure server, as TLS mode is enabled")
startWebSecureServer()
}
return nil return nil
} }
var (
startTLS = make(chan struct{})
stopTLS = make(chan struct{})
tlsServiceLock = sync.Mutex{}
tlsStarted = false
)
// RunWebSecureServer runs a web server with TLS. // RunWebSecureServer runs a web server with TLS.
func RunWebSecureServer() { func runWebSecureServer() {
tlsServiceLock.Lock()
defer tlsServiceLock.Unlock()
tlsStarted = true
defer func() {
tlsStarted = false
}()
r := setupRouter() r := setupRouter()
server := &http.Server{ server := &http.Server{
@ -120,8 +167,42 @@ func RunWebSecureServer() {
}, },
} }
logger.Infof("Starting websecure server on %s", webSecureListen) logger.Infof("Starting websecure server on %s", webSecureListen)
go func() {
for _ = range stopTLS {
logger.Infof("Shutting down websecure server")
server.Shutdown(context.Background())
}
}()
err := server.ListenAndServeTLS("", "") err := server.ListenAndServeTLS("", "")
if err != nil { if !errors.Is(err, http.ErrServerClosed) {
panic(err) panic(err)
} }
} }
func stopWebSecureServer() {
if !tlsStarted {
logger.Warnf("Websecure server is not running, not stopping it")
return
}
stopTLS <- struct{}{}
}
func startWebSecureServer() {
if tlsStarted {
logger.Warnf("Websecure server is already running, not starting it again")
return
}
startTLS <- struct{}{}
}
func RunWebSecureServer() {
for _ = range startTLS {
logger.Tracef("Starting websecure server, as we have received a start signal")
if certStore == nil {
initCertStore()
}
go runWebSecureServer()
}
}