diff --git a/server/api/cert.go b/server/api/cert.go index 7a7ad22f..58a59d49 100644 --- a/server/api/cert.go +++ b/server/api/cert.go @@ -1,141 +1,139 @@ package api import ( - "github.com/0xJacky/Nginx-UI/server/model" - "github.com/0xJacky/Nginx-UI/server/pkg/cert" - "github.com/0xJacky/Nginx-UI/server/pkg/nginx" - "github.com/gin-gonic/gin" - "github.com/gorilla/websocket" - "log" - "net/http" - "strings" + "github.com/0xJacky/Nginx-UI/server/model" + "github.com/0xJacky/Nginx-UI/server/pkg/cert" + "github.com/0xJacky/Nginx-UI/server/pkg/nginx" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "log" + "net/http" + "strings" ) const ( - Success = "success" - Info = "info" - Error = "error" + Success = "success" + Info = "info" + Error = "error" ) type IssueCertResponse struct { - Status string `json:"status"` - Message string `json:"message"` - SSLCertificate string `json:"ssl_certificate,omitempty"` - SSLCertificateKey string `json:"ssl_certificate_key,omitempty"` + Status string `json:"status"` + Message string `json:"message"` + SSLCertificate string `json:"ssl_certificate,omitempty"` + SSLCertificateKey string `json:"ssl_certificate_key,omitempty"` } func handleIssueCertLogChan(conn *websocket.Conn, logChan chan string) { - defer func() { - if err := recover(); err != nil { - log.Println("api.handleIssueCertLogChan recover", err) - } - }() + defer func() { + if err := recover(); err != nil { + log.Println("api.handleIssueCertLogChan recover", err) + } + }() - for logString := range logChan { + for logString := range logChan { - err := conn.WriteJSON(IssueCertResponse{ - Status: Info, - Message: logString, - }) + err := conn.WriteJSON(IssueCertResponse{ + Status: Info, + Message: logString, + }) - if err != nil { - log.Println("Error handleIssueCertLogChan", err) - return - } + if err != nil { + log.Println("Error handleIssueCertLogChan", err) + return + } - } + } } func IssueCert(c *gin.Context) { - var upGrader = websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - return true - }, - } + var upGrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, + } - // upgrade http to websocket - ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) - if err != nil { - log.Println(err) - return - } + // upgrade http to websocket + ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Println(err) + return + } - defer func(ws *websocket.Conn) { - err := ws.Close() - if err != nil { - log.Println("defer websocket close err", err) - } - }(ws) + defer func(ws *websocket.Conn) { + err := ws.Close() + if err != nil { + log.Println("defer websocket close err", err) + } + }(ws) - // read - var buffer struct { - ServerName []string `json:"server_name"` - } + // read + var buffer struct { + ServerName []string `json:"server_name"` + } - err = ws.ReadJSON(&buffer) + err = ws.ReadJSON(&buffer) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + return + } - logChan := make(chan string, 1) - errChan := make(chan error, 1) + logChan := make(chan string, 1) + errChan := make(chan error, 1) - go cert.IssueCert(buffer.ServerName, logChan, errChan) + go cert.IssueCert(buffer.ServerName, logChan, errChan) - domain := strings.Join(buffer.ServerName, "_") + domain := strings.Join(buffer.ServerName, "_") - go handleIssueCertLogChan(ws, logChan) + go handleIssueCertLogChan(ws, logChan) - // block, unless errChan closed - for err = range errChan { - log.Println("Error cert.IssueCert", err) + // block, unless errChan closed + for err = range errChan { + log.Println("Error cert.IssueCert", err) - err = ws.WriteJSON(IssueCertResponse{ - Status: Error, - Message: err.Error(), - }) + err = ws.WriteJSON(IssueCertResponse{ + Status: Error, + Message: err.Error(), + }) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println("Error WriteJSON", err) + return + } - return - } + return + } - close(logChan) + close(logChan) - sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer") - sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key") + sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer") + sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/private.key") - certModel, err := model.FirstCert(domain) + certModel, err := model.FirstOrCreateCert(domain) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + } - err = certModel.Updates(&model.Cert{ - SSLCertificatePath: sslCertificatePath, - }) + err = certModel.Updates(&model.Cert{ + SSLCertificatePath: sslCertificatePath, + }) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + } - err = ws.WriteJSON(IssueCertResponse{ - Status: Success, - Message: "Issued certificate successfully", - SSLCertificate: sslCertificatePath, - SSLCertificateKey: sslCertificateKeyPath, - }) + err = ws.WriteJSON(IssueCertResponse{ + Status: Success, + Message: "Issued certificate successfully", + SSLCertificate: sslCertificatePath, + SSLCertificateKey: sslCertificateKeyPath, + }) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + return + } } diff --git a/server/api/domain.go b/server/api/domain.go index 636adfc4..00d5161c 100644 --- a/server/api/domain.go +++ b/server/api/domain.go @@ -1,358 +1,365 @@ package api import ( - "github.com/0xJacky/Nginx-UI/server/model" - "github.com/0xJacky/Nginx-UI/server/pkg/cert" - "github.com/0xJacky/Nginx-UI/server/pkg/config_list" - "github.com/0xJacky/Nginx-UI/server/pkg/nginx" - "github.com/gin-gonic/gin" - "log" - "net/http" - "os" - "path/filepath" - "strings" - "time" + "github.com/0xJacky/Nginx-UI/server/model" + "github.com/0xJacky/Nginx-UI/server/pkg/cert" + "github.com/0xJacky/Nginx-UI/server/pkg/config_list" + "github.com/0xJacky/Nginx-UI/server/pkg/nginx" + "github.com/gin-gonic/gin" + "log" + "net/http" + "os" + "path/filepath" + "strings" + "time" ) func GetDomains(c *gin.Context) { - name := c.Query("name") - orderBy := c.Query("order_by") - sort := c.DefaultQuery("sort", "desc") + name := c.Query("name") + orderBy := c.Query("order_by") + sort := c.DefaultQuery("sort", "desc") - mySort := map[string]string{ - "enabled": "bool", - "name": "string", - "modify": "time", - } + mySort := map[string]string{ + "enabled": "bool", + "name": "string", + "modify": "time", + } - configFiles, err := os.ReadDir(nginx.GetNginxConfPath("sites-available")) + configFiles, err := os.ReadDir(nginx.GetNginxConfPath("sites-available")) - if err != nil { - ErrHandler(c, err) - return - } + if err != nil { + ErrHandler(c, err) + return + } - enabledConfig, err := os.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled"))) + enabledConfig, err := os.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled"))) - if err != nil { - ErrHandler(c, err) - return - } + if err != nil { + ErrHandler(c, err) + return + } - enabledConfigMap := make(map[string]bool) - for i := range enabledConfig { - enabledConfigMap[enabledConfig[i].Name()] = true - } + enabledConfigMap := make(map[string]bool) + for i := range enabledConfig { + enabledConfigMap[enabledConfig[i].Name()] = true + } - var configs []gin.H + var configs []gin.H - for i := range configFiles { - file := configFiles[i] - fileInfo, _ := file.Info() - if !file.IsDir() { - if name != "" && !strings.Contains(file.Name(), name) { - continue - } - configs = append(configs, gin.H{ - "name": file.Name(), - "size": fileInfo.Size(), - "modify": fileInfo.ModTime(), - "enabled": enabledConfigMap[file.Name()], - }) - } - } + for i := range configFiles { + file := configFiles[i] + fileInfo, _ := file.Info() + if !file.IsDir() { + if name != "" && !strings.Contains(file.Name(), name) { + continue + } + configs = append(configs, gin.H{ + "name": file.Name(), + "size": fileInfo.Size(), + "modify": fileInfo.ModTime(), + "enabled": enabledConfigMap[file.Name()], + }) + } + } - configs = config_list.Sort(orderBy, sort, mySort[orderBy], configs) + configs = config_list.Sort(orderBy, sort, mySort[orderBy], configs) - c.JSON(http.StatusOK, gin.H{ - "data": configs, - }) + c.JSON(http.StatusOK, gin.H{ + "data": configs, + }) } type CertificateInfo struct { - SubjectName string `json:"subject_name"` - IssuerName string `json:"issuer_name"` - NotAfter time.Time `json:"not_after"` - NotBefore time.Time `json:"not_before"` + SubjectName string `json:"subject_name"` + IssuerName string `json:"issuer_name"` + NotAfter time.Time `json:"not_after"` + NotBefore time.Time `json:"not_before"` } func GetDomain(c *gin.Context) { - rewriteName, ok := c.Get("rewriteConfigFileName") + rewriteName, ok := c.Get("rewriteConfigFileName") - name := c.Param("name") + name := c.Param("name") - // for modify filename - if ok { - name = rewriteName.(string) - } + // for modify filename + if ok { + name = rewriteName.(string) + } - path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name) + path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name) - enabled := true - if _, err := os.Stat(filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) { - enabled = false - } + enabled := true + if _, err := os.Stat(filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) { + enabled = false + } - config, err := nginx.ParseNgxConfig(path) + config, err := nginx.ParseNgxConfig(path) - if err != nil { - ErrHandler(c, err) - return - } + if err != nil { + ErrHandler(c, err) + return + } - certInfoMap := make(map[int]CertificateInfo) - for serverIdx, server := range config.Servers { - for _, directive := range server.Directives { - if directive.Directive == "ssl_certificate" { + certInfoMap := make(map[int]CertificateInfo) + var serverName string + for serverIdx, server := range config.Servers { + for _, directive := range server.Directives { - pubKey, err := cert.GetCertInfo(directive.Params) + if directive.Directive == "server_name" { + serverName = strings.ReplaceAll(directive.Params, " ", "_") + continue + } - if err != nil { - log.Println("Failed to get certificate information", err) - break - } + if directive.Directive == "ssl_certificate" { - certInfoMap[serverIdx] = CertificateInfo{ - SubjectName: pubKey.Subject.CommonName, - IssuerName: pubKey.Issuer.CommonName, - NotAfter: pubKey.NotAfter, - NotBefore: pubKey.NotBefore, - } + pubKey, err := cert.GetCertInfo(directive.Params) - break - } - } - } + if err != nil { + log.Println("Failed to get certificate information", err) + break + } - _, err = model.FirstCert(name) + certInfoMap[serverIdx] = CertificateInfo{ + SubjectName: pubKey.Subject.CommonName, + IssuerName: pubKey.Issuer.CommonName, + NotAfter: pubKey.NotAfter, + NotBefore: pubKey.NotBefore, + } - c.JSON(http.StatusOK, gin.H{ - "enabled": enabled, - "name": name, - "config": config.BuildConfig(), - "tokenized": config, - "auto_cert": err == nil, - "cert_info": certInfoMap, - }) + break + } + } + } + + _, err = model.FirstCert(serverName) + + c.JSON(http.StatusOK, gin.H{ + "enabled": enabled, + "name": name, + "config": config.BuildConfig(), + "tokenized": config, + "auto_cert": err == nil, + "cert_info": certInfoMap, + }) } func EditDomain(c *gin.Context) { - name := c.Param("name") + name := c.Param("name") - if name == "" { - c.JSON(http.StatusNotAcceptable, gin.H{ - "message": "param name is empty", - }) - return - } + if name == "" { + c.JSON(http.StatusNotAcceptable, gin.H{ + "message": "param name is empty", + }) + return + } - var json struct { - Name string `json:"name" binding:"required"` - Content string `json:"content"` - } + var json struct { + Name string `json:"name" binding:"required"` + Content string `json:"content"` + } - if !BindAndValid(c, &json) { - return - } + if !BindAndValid(c, &json) { + return + } - path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name) + path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name) - err := os.WriteFile(path, []byte(json.Content), 0644) - if err != nil { - ErrHandler(c, err) - return - } - enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name) - // rename the config file if needed - if name != json.Name { - newPath := filepath.Join(nginx.GetNginxConfPath("sites-available"), json.Name) - // recreate soft link - log.Println(enabledConfigFilePath) - if _, err = os.Stat(enabledConfigFilePath); err == nil { - log.Println(enabledConfigFilePath) - _ = os.Remove(enabledConfigFilePath) - enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), json.Name) - err = os.Symlink(newPath, enabledConfigFilePath) + err := os.WriteFile(path, []byte(json.Content), 0644) + if err != nil { + ErrHandler(c, err) + return + } + enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name) + // rename the config file if needed + if name != json.Name { + newPath := filepath.Join(nginx.GetNginxConfPath("sites-available"), json.Name) + // recreate soft link + log.Println(enabledConfigFilePath) + if _, err = os.Stat(enabledConfigFilePath); err == nil { + log.Println(enabledConfigFilePath) + _ = os.Remove(enabledConfigFilePath) + enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), json.Name) + err = os.Symlink(newPath, enabledConfigFilePath) - if err != nil { - ErrHandler(c, err) - return - } - } - err = os.Rename(path, newPath) - if err != nil { - ErrHandler(c, err) - return - } - name = json.Name - c.Set("rewriteConfigFileName", name) + if err != nil { + ErrHandler(c, err) + return + } + } + err = os.Rename(path, newPath) + if err != nil { + ErrHandler(c, err) + return + } + name = json.Name + c.Set("rewriteConfigFileName", name) - } + } - enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name) - if _, err = os.Stat(enabledConfigFilePath); err == nil { - // Test nginx configuration - err = nginx.TestNginxConf() - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "message": err.Error(), - }) - return - } + enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name) + if _, err = os.Stat(enabledConfigFilePath); err == nil { + // Test nginx configuration + err = nginx.TestNginxConf() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": err.Error(), + }) + return + } - output := nginx.ReloadNginx() + output := nginx.ReloadNginx() - if output != "" && strings.Contains(output, "error") { - c.JSON(http.StatusInternalServerError, gin.H{ - "message": output, - }) - return - } - } + if output != "" && strings.Contains(output, "error") { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": output, + }) + return + } + } - GetDomain(c) + GetDomain(c) } func EnableDomain(c *gin.Context) { - configFilePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), c.Param("name")) - enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name")) + configFilePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), c.Param("name")) + enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name")) - _, err := os.Stat(configFilePath) + _, err := os.Stat(configFilePath) - if err != nil { - ErrHandler(c, err) - return - } + if err != nil { + ErrHandler(c, err) + return + } - if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) { - err = os.Symlink(configFilePath, enabledConfigFilePath) + if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) { + err = os.Symlink(configFilePath, enabledConfigFilePath) - if err != nil { - ErrHandler(c, err) - return - } - } + if err != nil { + ErrHandler(c, err) + return + } + } - // Test nginx config, if not pass then rollback. - err = nginx.TestNginxConf() - if err != nil { - _ = os.Remove(enabledConfigFilePath) - c.JSON(http.StatusInternalServerError, gin.H{ - "message": err.Error(), - }) - return - } + // Test nginx config, if not pass then rollback. + err = nginx.TestNginxConf() + if err != nil { + _ = os.Remove(enabledConfigFilePath) + c.JSON(http.StatusInternalServerError, gin.H{ + "message": err.Error(), + }) + return + } - output := nginx.ReloadNginx() + output := nginx.ReloadNginx() - if output != "" && strings.Contains(output, "error") { - c.JSON(http.StatusInternalServerError, gin.H{ - "message": output, - }) - return - } + if output != "" && strings.Contains(output, "error") { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": output, + }) + return + } - c.JSON(http.StatusOK, gin.H{ - "message": "ok", - }) + c.JSON(http.StatusOK, gin.H{ + "message": "ok", + }) } func DisableDomain(c *gin.Context) { - enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name")) + enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name")) - _, err := os.Stat(enabledConfigFilePath) + _, err := os.Stat(enabledConfigFilePath) - if err != nil { - ErrHandler(c, err) - return - } + if err != nil { + ErrHandler(c, err) + return + } - err = os.Remove(enabledConfigFilePath) + err = os.Remove(enabledConfigFilePath) - if err != nil { - ErrHandler(c, err) - return - } + if err != nil { + ErrHandler(c, err) + return + } - // delete auto cert record - certModel := model.Cert{Domain: c.Param("name")} - err = certModel.Remove() - if err != nil { - ErrHandler(c, err) - return - } + // delete auto cert record + certModel := model.Cert{Domain: c.Param("name")} + err = certModel.Remove() + if err != nil { + ErrHandler(c, err) + return + } - output := nginx.ReloadNginx() + output := nginx.ReloadNginx() - if output != "" { - c.JSON(http.StatusInternalServerError, gin.H{ - "message": output, - }) - return - } + if output != "" { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": output, + }) + return + } - c.JSON(http.StatusOK, gin.H{ - "message": "ok", - }) + c.JSON(http.StatusOK, gin.H{ + "message": "ok", + }) } func DeleteDomain(c *gin.Context) { - var err error - name := c.Param("name") - availablePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), name) - enabledPath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name) + var err error + name := c.Param("name") + availablePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), name) + enabledPath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name) - if _, err = os.Stat(availablePath); os.IsNotExist(err) { - c.JSON(http.StatusNotFound, gin.H{ - "message": "site not found", - }) - return - } + if _, err = os.Stat(availablePath); os.IsNotExist(err) { + c.JSON(http.StatusNotFound, gin.H{ + "message": "site not found", + }) + return + } - if _, err = os.Stat(enabledPath); err == nil { - c.JSON(http.StatusNotAcceptable, gin.H{ - "message": "site is enabled", - }) - return - } + if _, err = os.Stat(enabledPath); err == nil { + c.JSON(http.StatusNotAcceptable, gin.H{ + "message": "site is enabled", + }) + return + } - certModel := model.Cert{Domain: name} - _ = certModel.Remove() + certModel := model.Cert{Domain: name} + _ = certModel.Remove() - err = os.Remove(availablePath) + err = os.Remove(availablePath) - if err != nil { - ErrHandler(c, err) - return - } + if err != nil { + ErrHandler(c, err) + return + } - c.JSON(http.StatusOK, gin.H{ - "message": "ok", - }) + c.JSON(http.StatusOK, gin.H{ + "message": "ok", + }) } func AddDomainToAutoCert(c *gin.Context) { - domain := c.Param("domain") + domain := c.Param("domain") - certModel, err := model.FirstOrCreateCert(domain) - if err != nil { - ErrHandler(c, err) - return - } - c.JSON(http.StatusOK, certModel) + certModel, err := model.FirstOrCreateCert(domain) + if err != nil { + ErrHandler(c, err) + return + } + c.JSON(http.StatusOK, certModel) } func RemoveDomainFromAutoCert(c *gin.Context) { - certModel := model.Cert{ - Domain: c.Param("domain"), - } - err := certModel.Remove() + certModel := model.Cert{ + Domain: c.Param("domain"), + } + err := certModel.Remove() - if err != nil { - ErrHandler(c, err) - return - } - c.JSON(http.StatusOK, nil) + if err != nil { + ErrHandler(c, err) + return + } + c.JSON(http.StatusOK, nil) } diff --git a/server/pkg/cert/auto_cert.go b/server/pkg/cert/auto_cert.go index 97eeecf2..9d5e0804 100644 --- a/server/pkg/cert/auto_cert.go +++ b/server/pkg/cert/auto_cert.go @@ -3,6 +3,7 @@ package cert import ( "github.com/0xJacky/Nginx-UI/server/model" "log" + "strings" "time" ) @@ -56,7 +57,8 @@ func AutoCert() { logChan := make(chan string, 1) errChan := make(chan error, 1) - go IssueCert([]string{domain}, logChan, errChan) + // support SAN certification + go IssueCert(strings.Split(domain, "_"), logChan, errChan) go handleIssueCertLogChan(logChan) diff --git a/server/pkg/cert/cert.go b/server/pkg/cert/cert.go index cbdc9910..710c5b99 100644 --- a/server/pkg/cert/cert.go +++ b/server/pkg/cert/cert.go @@ -105,7 +105,7 @@ func IssueCert(domain []string, logChan chan string, errChan chan error) { errChan <- errors.Wrap(err, "issue cert fail to obtain") return } - name := strings.Join(domain, "_") + name := strings.Join(domain, " ") saveDir := nginx.GetNginxConfPath("ssl/" + name) if _, err = os.Stat(saveDir); os.IsNotExist(err) { err = os.MkdirAll(saveDir, 0755) @@ -127,7 +127,7 @@ func IssueCert(domain []string, logChan chan string, errChan chan error) { } logChan <- "Writing certificate private key to disk" - err = os.WriteFile(filepath.Join(saveDir, name+".key"), + err = os.WriteFile(filepath.Join(saveDir, "private.key"), certificates.PrivateKey, 0644) if err != nil {