🎨 Implement HTTPS network serving (#16912)

* Add use TLS for network serving configuration option

* kernel: Implement TLS certificate generation

* kernel: server: Use https for fixed port proxy when needed

* Allow exporting the CA Certificate file

* Implement import and export of CA Certs
This commit is contained in:
Davide Garberi 2026-01-27 05:59:11 +01:00 committed by GitHub
parent e7621b7a5f
commit 43ea6757d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 759 additions and 10 deletions

View file

@ -42,6 +42,10 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/system/setAccessAuthCode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAccessAuthCode)
ginServer.Handle("POST", "/api/system/setFollowSystemLockScreen", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setFollowSystemLockScreen)
ginServer.Handle("POST", "/api/system/setNetworkServe", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNetworkServe)
ginServer.Handle("POST", "/api/system/setNetworkServeTLS", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNetworkServeTLS)
ginServer.Handle("POST", "/api/system/exportTLSCACert", model.CheckAuth, model.CheckAdminRole, exportTLSCACert)
ginServer.Handle("POST", "/api/system/exportTLSCABundle", model.CheckAuth, model.CheckAdminRole, exportTLSCABundle)
ginServer.Handle("POST", "/api/system/importTLSCABundle", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importTLSCABundle)
ginServer.Handle("POST", "/api/system/setAutoLaunch", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAutoLaunch)
ginServer.Handle("POST", "/api/system/setDownloadInstallPkg", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setDownloadInstallPkg)
ginServer.Handle("POST", "/api/system/setNetworkProxy", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNetworkProxy)

View file

@ -720,6 +720,173 @@ func setNetworkServe(c *gin.Context) {
time.Sleep(time.Second * 3)
}
func setNetworkServeTLS(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
networkServeTLS := arg["networkServeTLS"].(bool)
model.Conf.System.NetworkServeTLS = networkServeTLS
model.Conf.Save()
util.PushMsg(model.Conf.Language(42), 1000*15)
time.Sleep(time.Second * 3)
}
func exportTLSCACert(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
caCertPath := filepath.Join(util.ConfDir, util.TLSCACertFilename)
if !gulu.File.IsExist(caCertPath) {
ret.Code = -1
ret.Msg = "CA certificate not found"
return
}
tmpDir := filepath.Join(util.TempDir, "export")
if err := os.MkdirAll(tmpDir, 0755); err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
exportPath := filepath.Join(tmpDir, util.TLSCACertFilename)
if err := gulu.File.CopyFile(caCertPath, exportPath); err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"path": "/export/" + util.TLSCACertFilename,
}
}
func exportTLSCABundle(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
caCertPath := filepath.Join(util.ConfDir, util.TLSCACertFilename)
caKeyPath := filepath.Join(util.ConfDir, util.TLSCAKeyFilename)
if !gulu.File.IsExist(caCertPath) || !gulu.File.IsExist(caKeyPath) {
ret.Code = -1
ret.Msg = "CA certificate not found, please enable TLS first"
return
}
tmpDir := filepath.Join(util.TempDir, "export", "ca-bundle")
os.RemoveAll(tmpDir)
if err := os.MkdirAll(tmpDir, 0755); err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
defer os.RemoveAll(tmpDir)
if err := gulu.File.CopyFile(caCertPath, filepath.Join(tmpDir, util.TLSCACertFilename)); err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
if err := gulu.File.CopyFile(caKeyPath, filepath.Join(tmpDir, util.TLSCAKeyFilename)); err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
zipPath := filepath.Join(util.TempDir, "export", "ca-bundle.zip")
zipFile, err := gulu.Zip.Create(zipPath)
if err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
if err := zipFile.AddDirectory("", tmpDir); err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
if err := zipFile.Close(); err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"path": "/export/ca-bundle.zip",
}
}
func importTLSCABundle(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
file, err := c.FormFile("file")
if err != nil {
ret.Code = -1
ret.Msg = "file is required: " + err.Error()
return
}
tmpDir := filepath.Join(util.TempDir, "import")
if err := os.MkdirAll(tmpDir, 0755); err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
tmpZipPath := filepath.Join(tmpDir, "ca-bundle.zip")
if err := c.SaveUploadedFile(file, tmpZipPath); err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
defer os.Remove(tmpZipPath)
extractDir := filepath.Join(tmpDir, "ca-bundle")
os.RemoveAll(extractDir)
if err := gulu.Zip.Unzip(tmpZipPath, extractDir); err != nil {
ret.Code = -1
ret.Msg = "failed to extract zip file: " + err.Error()
return
}
defer os.RemoveAll(extractDir)
caCertPath := filepath.Join(extractDir, util.TLSCACertFilename)
caCertPEM, err := os.ReadFile(caCertPath)
if err != nil {
ret.Code = -1
ret.Msg = "ca.crt not found in zip file"
return
}
caKeyPath := filepath.Join(extractDir, util.TLSCAKeyFilename)
caKeyPEM, err := os.ReadFile(caKeyPath)
if err != nil {
ret.Code = -1
ret.Msg = "ca.key not found in zip file"
return
}
if err := util.ImportCABundle(string(caCertPEM), string(caKeyPEM)); err != nil {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"msg": "CA bundle imported successfully. Please restart to apply changes.",
}
}
func setAutoLaunch(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)