feat: add local web server loopback mode configuration

- Introduced a new configuration option `LocalWebServerLoopbackOnly` to restrict the web server to listen only on the loopback interface.
- Added RPC methods `rpcGetLocalWebServerLoopbackOnly` and `rpcSetLocalWebServerLoopbackOnly` for retrieving and updating this setting.
- Updated the web server startup logic to bind to the appropriate address based on the new configuration.
- Modified the `LocalDevice` struct to include the loopback setting in the response.
This commit is contained in:
Alex Goodkind 2025-05-22 18:10:02 -07:00
parent c1d771cced
commit 15f5a25f23
No known key found for this signature in database
3 changed files with 165 additions and 117 deletions

View File

@ -75,46 +75,48 @@ func (m *KeyboardMacro) Validate() error {
} }
type Config struct { type Config struct {
CloudURL string `json:"cloud_url"` CloudURL string `json:"cloud_url"`
CloudAppURL string `json:"cloud_app_url"` CloudAppURL string `json:"cloud_app_url"`
CloudToken string `json:"cloud_token"` CloudToken string `json:"cloud_token"`
GoogleIdentity string `json:"google_identity"` GoogleIdentity string `json:"google_identity"`
JigglerEnabled bool `json:"jiggler_enabled"` JigglerEnabled bool `json:"jiggler_enabled"`
AutoUpdateEnabled bool `json:"auto_update_enabled"` AutoUpdateEnabled bool `json:"auto_update_enabled"`
IncludePreRelease bool `json:"include_pre_release"` IncludePreRelease bool `json:"include_pre_release"`
HashedPassword string `json:"hashed_password"` HashedPassword string `json:"hashed_password"`
LocalAuthToken string `json:"local_auth_token"` LocalAuthToken string `json:"local_auth_token"`
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"` WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
KeyboardMacros []KeyboardMacro `json:"keyboard_macros"` KeyboardMacros []KeyboardMacro `json:"keyboard_macros"`
KeyboardLayout string `json:"keyboard_layout"` KeyboardLayout string `json:"keyboard_layout"`
EdidString string `json:"hdmi_edid_string"` EdidString string `json:"hdmi_edid_string"`
ActiveExtension string `json:"active_extension"` ActiveExtension string `json:"active_extension"`
DisplayRotation string `json:"display_rotation"` DisplayRotation string `json:"display_rotation"`
DisplayMaxBrightness int `json:"display_max_brightness"` DisplayMaxBrightness int `json:"display_max_brightness"`
DisplayDimAfterSec int `json:"display_dim_after_sec"` DisplayDimAfterSec int `json:"display_dim_after_sec"`
DisplayOffAfterSec int `json:"display_off_after_sec"` DisplayOffAfterSec int `json:"display_off_after_sec"`
TLSMode string `json:"tls_mode"` // options: "self-signed", "user-defined", "" TLSMode string `json:"tls_mode"` // options: "self-signed", "user-defined", ""
UsbConfig *usbgadget.Config `json:"usb_config"` LocalWebServerLoopbackOnly bool `json:"local_web_server_loopback_only"`
UsbDevices *usbgadget.Devices `json:"usb_devices"` UsbConfig *usbgadget.Config `json:"usb_config"`
NetworkConfig *network.NetworkConfig `json:"network_config"` UsbDevices *usbgadget.Devices `json:"usb_devices"`
DefaultLogLevel string `json:"default_log_level"` NetworkConfig *network.NetworkConfig `json:"network_config"`
DefaultLogLevel string `json:"default_log_level"`
} }
const configPath = "/userdata/kvm_config.json" const configPath = "/userdata/kvm_config.json"
var defaultConfig = &Config{ var defaultConfig = &Config{
CloudURL: "https://api.jetkvm.com", CloudURL: "https://api.jetkvm.com",
CloudAppURL: "https://app.jetkvm.com", CloudAppURL: "https://app.jetkvm.com",
AutoUpdateEnabled: true, // Set a default value AutoUpdateEnabled: true, // Set a default value
ActiveExtension: "", ActiveExtension: "",
KeyboardMacros: []KeyboardMacro{}, KeyboardMacros: []KeyboardMacro{},
DisplayRotation: "270", DisplayRotation: "270",
KeyboardLayout: "en-US", KeyboardLayout: "en-US",
DisplayMaxBrightness: 64, DisplayMaxBrightness: 64,
DisplayDimAfterSec: 120, // 2 minutes DisplayDimAfterSec: 120, // 2 minutes
DisplayOffAfterSec: 1800, // 30 minutes DisplayOffAfterSec: 1800, // 30 minutes
TLSMode: "", TLSMode: "",
LocalWebServerLoopbackOnly: false, // Allow access from any network interface by default
UsbConfig: &usbgadget.Config{ UsbConfig: &usbgadget.Config{
VendorId: "0x1d6b", //The Linux Foundation VendorId: "0x1d6b", //The Linux Foundation
ProductId: "0x0104", //Multifunction Composite Gadget ProductId: "0x0104", //Multifunction Composite Gadget

View File

@ -1006,81 +1006,117 @@ func setKeyboardMacros(params KeyboardMacrosParams) (interface{}, error) {
return nil, nil return nil, nil
} }
var rpcHandlers = map[string]RPCHandler{ func rpcGetLocalWebServerLoopbackOnly() (bool, error) {
"ping": {Func: rpcPing}, return config.LocalWebServerLoopbackOnly, nil
"reboot": {Func: rpcReboot, Params: []string{"force"}}, }
"getDeviceID": {Func: rpcGetDeviceID},
"deregisterDevice": {Func: rpcDeregisterDevice}, func rpcSetLocalWebServerLoopbackOnly(enabled bool) error {
"getCloudState": {Func: rpcGetCloudState}, // Check if the setting is actually changing
"getNetworkState": {Func: rpcGetNetworkState}, if config.LocalWebServerLoopbackOnly == enabled {
"getNetworkSettings": {Func: rpcGetNetworkSettings}, return nil
"setNetworkSettings": {Func: rpcSetNetworkSettings, Params: []string{"settings"}}, }
"renewDHCPLease": {Func: rpcRenewDHCPLease},
"keyboardReport": {Func: rpcKeyboardReport, Params: []string{"modifier", "keys"}}, // Update the setting
"getKeyboardLedState": {Func: rpcGetKeyboardLedState}, config.LocalWebServerLoopbackOnly = enabled
"absMouseReport": {Func: rpcAbsMouseReport, Params: []string{"x", "y", "buttons"}}, if err := SaveConfig(); err != nil {
"relMouseReport": {Func: rpcRelMouseReport, Params: []string{"dx", "dy", "buttons"}}, return fmt.Errorf("failed to save config: %w", err)
"wheelReport": {Func: rpcWheelReport, Params: []string{"wheelY"}}, }
"getVideoState": {Func: rpcGetVideoState},
"getUSBState": {Func: rpcGetUSBState}, // Log the change
"unmountImage": {Func: rpcUnmountImage}, if enabled {
"rpcMountBuiltInImage": {Func: rpcMountBuiltInImage, Params: []string{"filename"}}, logger.Info().Msg("Web server now set to only listen on loopback interface, this will take effect after reboot")
"setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}}, } else {
"getJigglerState": {Func: rpcGetJigglerState}, logger.Info().Msg("Web server now set to listen on all interfaces, this will take effect after reboot")
"sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}}, }
"getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
"setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}}, // Return a message that changes require a reboot
"getAutoUpdateState": {Func: rpcGetAutoUpdateState}, message := "Web server binding changed. You must reboot the device for this change to take effect."
"setAutoUpdateState": {Func: rpcSetAutoUpdateState, Params: []string{"enabled"}}, if enabled {
"getEDID": {Func: rpcGetEDID}, message = "Web server set to loopback-only mode. After reboot, the web interface will only be accessible from the device itself. You must reboot the device for this change to take effect."
"setEDID": {Func: rpcSetEDID, Params: []string{"edid"}}, } else {
"getDevChannelState": {Func: rpcGetDevChannelState}, message = "Web server set to listen on all interfaces. After reboot, the web interface will be accessible from other devices on the network. You must reboot the device for this change to take effect."
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}}, }
"getUpdateStatus": {Func: rpcGetUpdateStatus},
"tryUpdate": {Func: rpcTryUpdate}, return fmt.Errorf(message)
"getDevModeState": {Func: rpcGetDevModeState}, }
"setDevModeState": {Func: rpcSetDevModeState, Params: []string{"enabled"}},
"getSSHKeyState": {Func: rpcGetSSHKeyState}, var rpcHandlers = map[string]RPCHandler{
"setSSHKeyState": {Func: rpcSetSSHKeyState, Params: []string{"sshKey"}}, "ping": {Func: rpcPing},
"getTLSState": {Func: rpcGetTLSState}, "reboot": {Func: rpcReboot, Params: []string{"force"}},
"setTLSState": {Func: rpcSetTLSState, Params: []string{"state"}}, "getDeviceID": {Func: rpcGetDeviceID},
"setMassStorageMode": {Func: rpcSetMassStorageMode, Params: []string{"mode"}}, "deregisterDevice": {Func: rpcDeregisterDevice},
"getMassStorageMode": {Func: rpcGetMassStorageMode}, "getCloudState": {Func: rpcGetCloudState},
"isUpdatePending": {Func: rpcIsUpdatePending}, "getNetworkState": {Func: rpcGetNetworkState},
"getUsbEmulationState": {Func: rpcGetUsbEmulationState}, "getNetworkSettings": {Func: rpcGetNetworkSettings},
"setUsbEmulationState": {Func: rpcSetUsbEmulationState, Params: []string{"enabled"}}, "setNetworkSettings": {Func: rpcSetNetworkSettings, Params: []string{"settings"}},
"getUsbConfig": {Func: rpcGetUsbConfig}, "renewDHCPLease": {Func: rpcRenewDHCPLease},
"setUsbConfig": {Func: rpcSetUsbConfig, Params: []string{"usbConfig"}}, "keyboardReport": {Func: rpcKeyboardReport, Params: []string{"modifier", "keys"}},
"checkMountUrl": {Func: rpcCheckMountUrl, Params: []string{"url"}}, "getKeyboardLedState": {Func: rpcGetKeyboardLedState},
"getVirtualMediaState": {Func: rpcGetVirtualMediaState}, "absMouseReport": {Func: rpcAbsMouseReport, Params: []string{"x", "y", "buttons"}},
"getStorageSpace": {Func: rpcGetStorageSpace}, "relMouseReport": {Func: rpcRelMouseReport, Params: []string{"dx", "dy", "buttons"}},
"mountWithHTTP": {Func: rpcMountWithHTTP, Params: []string{"url", "mode"}}, "wheelReport": {Func: rpcWheelReport, Params: []string{"wheelY"}},
"mountWithWebRTC": {Func: rpcMountWithWebRTC, Params: []string{"filename", "size", "mode"}}, "getVideoState": {Func: rpcGetVideoState},
"mountWithStorage": {Func: rpcMountWithStorage, Params: []string{"filename", "mode"}}, "getUSBState": {Func: rpcGetUSBState},
"listStorageFiles": {Func: rpcListStorageFiles}, "unmountImage": {Func: rpcUnmountImage},
"deleteStorageFile": {Func: rpcDeleteStorageFile, Params: []string{"filename"}}, "rpcMountBuiltInImage": {Func: rpcMountBuiltInImage, Params: []string{"filename"}},
"startStorageFileUpload": {Func: rpcStartStorageFileUpload, Params: []string{"filename", "size"}}, "setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}},
"getWakeOnLanDevices": {Func: rpcGetWakeOnLanDevices}, "getJigglerState": {Func: rpcGetJigglerState},
"setWakeOnLanDevices": {Func: rpcSetWakeOnLanDevices, Params: []string{"params"}}, "sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}},
"resetConfig": {Func: rpcResetConfig}, "getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
"setDisplayRotation": {Func: rpcSetDisplayRotation, Params: []string{"params"}}, "setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}},
"getDisplayRotation": {Func: rpcGetDisplayRotation}, "getAutoUpdateState": {Func: rpcGetAutoUpdateState},
"setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"params"}}, "setAutoUpdateState": {Func: rpcSetAutoUpdateState, Params: []string{"enabled"}},
"getBacklightSettings": {Func: rpcGetBacklightSettings}, "getEDID": {Func: rpcGetEDID},
"getDCPowerState": {Func: rpcGetDCPowerState}, "setEDID": {Func: rpcSetEDID, Params: []string{"edid"}},
"setDCPowerState": {Func: rpcSetDCPowerState, Params: []string{"enabled"}}, "getDevChannelState": {Func: rpcGetDevChannelState},
"getActiveExtension": {Func: rpcGetActiveExtension}, "setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}}, "getUpdateStatus": {Func: rpcGetUpdateStatus},
"getATXState": {Func: rpcGetATXState}, "tryUpdate": {Func: rpcTryUpdate},
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}}, "getDevModeState": {Func: rpcGetDevModeState},
"getSerialSettings": {Func: rpcGetSerialSettings}, "setDevModeState": {Func: rpcSetDevModeState, Params: []string{"enabled"}},
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}}, "getSSHKeyState": {Func: rpcGetSSHKeyState},
"getUsbDevices": {Func: rpcGetUsbDevices}, "setSSHKeyState": {Func: rpcSetSSHKeyState, Params: []string{"sshKey"}},
"setUsbDevices": {Func: rpcSetUsbDevices, Params: []string{"devices"}}, "getTLSState": {Func: rpcGetTLSState},
"setUsbDeviceState": {Func: rpcSetUsbDeviceState, Params: []string{"device", "enabled"}}, "setTLSState": {Func: rpcSetTLSState, Params: []string{"state"}},
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"apiUrl", "appUrl"}}, "setMassStorageMode": {Func: rpcSetMassStorageMode, Params: []string{"mode"}},
"getKeyboardLayout": {Func: rpcGetKeyboardLayout}, "getMassStorageMode": {Func: rpcGetMassStorageMode},
"setKeyboardLayout": {Func: rpcSetKeyboardLayout, Params: []string{"layout"}}, "isUpdatePending": {Func: rpcIsUpdatePending},
"getKeyboardMacros": {Func: getKeyboardMacros}, "getUsbEmulationState": {Func: rpcGetUsbEmulationState},
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}}, "setUsbEmulationState": {Func: rpcSetUsbEmulationState, Params: []string{"enabled"}},
"getUsbConfig": {Func: rpcGetUsbConfig},
"setUsbConfig": {Func: rpcSetUsbConfig, Params: []string{"usbConfig"}},
"checkMountUrl": {Func: rpcCheckMountUrl, Params: []string{"url"}},
"getVirtualMediaState": {Func: rpcGetVirtualMediaState},
"getStorageSpace": {Func: rpcGetStorageSpace},
"mountWithHTTP": {Func: rpcMountWithHTTP, Params: []string{"url", "mode"}},
"mountWithWebRTC": {Func: rpcMountWithWebRTC, Params: []string{"filename", "size", "mode"}},
"mountWithStorage": {Func: rpcMountWithStorage, Params: []string{"filename", "mode"}},
"listStorageFiles": {Func: rpcListStorageFiles},
"deleteStorageFile": {Func: rpcDeleteStorageFile, Params: []string{"filename"}},
"startStorageFileUpload": {Func: rpcStartStorageFileUpload, Params: []string{"filename", "size"}},
"getWakeOnLanDevices": {Func: rpcGetWakeOnLanDevices},
"setWakeOnLanDevices": {Func: rpcSetWakeOnLanDevices, Params: []string{"params"}},
"resetConfig": {Func: rpcResetConfig},
"setDisplayRotation": {Func: rpcSetDisplayRotation, Params: []string{"params"}},
"getDisplayRotation": {Func: rpcGetDisplayRotation},
"setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"params"}},
"getBacklightSettings": {Func: rpcGetBacklightSettings},
"getDCPowerState": {Func: rpcGetDCPowerState},
"setDCPowerState": {Func: rpcSetDCPowerState, Params: []string{"enabled"}},
"getActiveExtension": {Func: rpcGetActiveExtension},
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}},
"getATXState": {Func: rpcGetATXState},
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}},
"getSerialSettings": {Func: rpcGetSerialSettings},
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}},
"getUsbDevices": {Func: rpcGetUsbDevices},
"setUsbDevices": {Func: rpcSetUsbDevices, Params: []string{"devices"}},
"setUsbDeviceState": {Func: rpcSetUsbDeviceState, Params: []string{"device", "enabled"}},
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"apiUrl", "appUrl"}},
"getKeyboardLayout": {Func: rpcGetKeyboardLayout},
"setKeyboardLayout": {Func: rpcSetKeyboardLayout, Params: []string{"layout"}},
"getKeyboardMacros": {Func: getKeyboardMacros},
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
"getLocalWebServerLoopbackOnly": {Func: rpcGetLocalWebServerLoopbackOnly},
"setLocalWebServerLoopbackOnly": {Func: rpcSetLocalWebServerLoopbackOnly, Params: []string{"enabled"}},
} }

20
web.go
View File

@ -52,8 +52,9 @@ type ChangePasswordRequest struct {
} }
type LocalDevice struct { type LocalDevice struct {
AuthMode *string `json:"authMode"` AuthMode *string `json:"authMode"`
DeviceID string `json:"deviceId"` DeviceID string `json:"deviceId"`
LocalWebServerLoopbackOnly bool `json:"localWebServerLoopbackOnly"`
} }
type DeviceStatus struct { type DeviceStatus struct {
@ -532,7 +533,15 @@ func basicAuthProtectedMiddleware(requireDeveloperMode bool) gin.HandlerFunc {
func RunWebServer() { func RunWebServer() {
r := setupRouter() r := setupRouter()
err := r.Run(":80")
// Determine the binding address based on the config
bindAddress := ":80" // Default to all interfaces
if config.LocalWebServerLoopbackOnly {
bindAddress = "localhost:80" // Loopback only (both IPv4 and IPv6)
}
logger.Info().Str("bindAddress", bindAddress).Bool("loopbackOnly", config.LocalWebServerLoopbackOnly).Msg("Starting web server")
err := r.Run(bindAddress)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -540,8 +549,9 @@ func RunWebServer() {
func handleDevice(c *gin.Context) { func handleDevice(c *gin.Context) {
response := LocalDevice{ response := LocalDevice{
AuthMode: &config.LocalAuthMode, AuthMode: &config.LocalAuthMode,
DeviceID: GetDeviceID(), DeviceID: GetDeviceID(),
LocalWebServerLoopbackOnly: config.LocalWebServerLoopbackOnly,
} }
c.JSON(http.StatusOK, response) c.JSON(http.StatusOK, response)