feat: configure HTTP/HTTPS public entry and managed certificates
This commit is contained in:
1 parent
35596cda9d
commit
88ea6b7854
21 files changed
+1309
-149
No files matched your search
+88
-11
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
@@ -26,9 +27,13 @@ func (s *Server) nginxSetupStatus(c *gin.Context) {
|
||||
func (s *Server) activateNginx(c *gin.Context) {
|
||||
status := tlsservice.Status()
|
||||
if status.Mode != "nginx" || status.ConfiguredMode != "nginx" || status.RestartRequired || status.ConfiguredDomain == "" {
|
||||
c.JSON(http.StatusPreconditionFailed, gin.H{"error": "请先保存 Nginx 模式和域名,重启 opsx-vpn 后再启用站点"})
|
||||
c.JSON(http.StatusPreconditionFailed, gin.H{"error": "请先保存 Nginx 模式和公网入口,重启 opsx-vpn 后再启用站点"})
|
||||
return
|
||||
}
|
||||
scheme := status.ConfiguredScheme
|
||||
if scheme == "" {
|
||||
scheme = "https"
|
||||
}
|
||||
if !nginxgateway.Begin() {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "入口配置正在运行,请等待完成"})
|
||||
return
|
||||
@@ -43,14 +48,19 @@ func (s *Server) activateNginx(c *gin.Context) {
|
||||
fmt.Fprintf(c.Writer, "%s\n", payload)
|
||||
c.Writer.Flush()
|
||||
}
|
||||
write("info", "检查现有站点并启用 HTTP-01 验证。\n")
|
||||
if err := nginxgateway.Run(status.ConfiguredDomain, s.router.Routes(), func(message string) { write("output", message) }); err != nil {
|
||||
if scheme == "http" {
|
||||
write("info", "启用 HTTP 公网入口(不申请证书)。\n")
|
||||
} else {
|
||||
write("info", "检查现有站点并启用 HTTP-01 验证。\n")
|
||||
}
|
||||
opts := nginxconfig.RenderOptions{Domain: status.ConfiguredDomain, Scheme: scheme, Port: status.ConfiguredPort}
|
||||
if err := nginxgateway.RunEntry(opts, s.router.Routes(), func(message string) { write("output", message) }); err != nil {
|
||||
store.Audit(c.GetString("username"), "nginx_activate", "failed", err.Error())
|
||||
write("error", err.Error())
|
||||
return
|
||||
}
|
||||
store.Audit(c.GetString("username"), "nginx_activate", "completed", status.ConfiguredDomain)
|
||||
write("done", "站点和证书已就绪。")
|
||||
store.Audit(c.GetString("username"), "nginx_activate", "completed", status.ConfiguredPublicBase)
|
||||
write("done", "站点和入口已就绪。")
|
||||
}
|
||||
|
||||
func (s *Server) initializeNginx(c *gin.Context) {
|
||||
@@ -91,17 +101,24 @@ func (s *Server) tlsStatus(c *gin.Context) {
|
||||
func (s *Server) nginxPreview(c *gin.Context) {
|
||||
status := tlsservice.Status()
|
||||
if status.ConfiguredDomain == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请先在页面保存域名"})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请先在页面保存公网入口"})
|
||||
return
|
||||
}
|
||||
httpSite, httpsSite, err := nginxconfig.Render(status.ConfiguredDomain, s.router.Routes())
|
||||
scheme := status.ConfiguredScheme
|
||||
if scheme == "" {
|
||||
scheme = "https"
|
||||
}
|
||||
httpSite, httpsSite, err := nginxconfig.RenderWithOptions(nginxconfig.RenderOptions{
|
||||
Domain: status.ConfiguredDomain, Scheme: scheme, Port: status.ConfiguredPort,
|
||||
}, s.router.Routes(), 0)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"http_site": httpSite, "https_site": httpsSite,
|
||||
"warning": "仅预览:先安装 Nginx/Certbot,停用 Go 的 80/443 监听并签出证书;认证域名须单独配置。",
|
||||
"public_base": status.ConfiguredPublicBase,
|
||||
"warning": "仅预览:保存并重启后按协议启用入口;认证域名须单独配置。",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -125,16 +142,76 @@ func (s *Server) setTLSMode(c *gin.Context) {
|
||||
func (s *Server) setTLSDomain(c *gin.Context) {
|
||||
var req struct {
|
||||
Domain string `json:"domain"`
|
||||
Scheme string `json:"scheme"`
|
||||
Port *int `json:"port"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请输入域名"})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请输入公网入口配置"})
|
||||
return
|
||||
}
|
||||
if err := tlsservice.ConfigureDomain(service.BaseDir(), req.Domain); err != nil {
|
||||
scheme := strings.ToLower(strings.TrimSpace(req.Scheme))
|
||||
if scheme == "" {
|
||||
scheme = "https"
|
||||
}
|
||||
port := 0
|
||||
if req.Port != nil {
|
||||
port = *req.Port
|
||||
}
|
||||
if err := tlsservice.ConfigurePublicEntry(service.BaseDir(), scheme, req.Domain, port); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
store.Audit(c.GetString("username"), "tls_domain_configured", req.Domain, "需重启后生效")
|
||||
store.Audit(c.GetString("username"), "tls_domain_configured", tlsservice.PublicBaseURL(scheme, strings.ToLower(strings.TrimSpace(req.Domain)), port), "需重启后生效")
|
||||
status := tlsservice.Status()
|
||||
c.JSON(http.StatusOK, gin.H{"status": status, "restart_required": status.RestartRequired})
|
||||
}
|
||||
|
||||
func (s *Server) uploadTLSCertificate(c *gin.Context) {
|
||||
var req struct {
|
||||
CertificatePEM string `json:"certificate_pem"`
|
||||
PrivateKeyPEM string `json:"private_key_pem"`
|
||||
Host string `json:"host"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请提交证书与私钥 PEM"})
|
||||
return
|
||||
}
|
||||
status := tlsservice.Status()
|
||||
domain := strings.ToLower(strings.TrimSpace(req.Host))
|
||||
if domain == "" {
|
||||
domain = status.ConfiguredDomain
|
||||
}
|
||||
if domain == "" {
|
||||
domain = status.Domain
|
||||
}
|
||||
if domain == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请先填写公网域名"})
|
||||
return
|
||||
}
|
||||
if domain != status.ConfiguredDomain && domain != status.Domain {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "证书域名必须与公网入口一致"})
|
||||
return
|
||||
}
|
||||
apply := func() error { return nil }
|
||||
if status.Mode == "nginx" && status.ConfiguredMode == "nginx" && !status.RestartRequired &&
|
||||
(status.ConfiguredScheme == "https" || status.Scheme == "https" || status.ConfiguredScheme == "") &&
|
||||
nginxgateway.Stage() == "https" && s.router != nil {
|
||||
apply = func() error {
|
||||
s.gatewayMu.Lock()
|
||||
defer s.gatewayMu.Unlock()
|
||||
port := 0
|
||||
if pb := tlsservice.Status().PublicBase; pb != "" {
|
||||
port, _ = authGatewayPort(pb)
|
||||
}
|
||||
return nginxgateway.RefreshAuthEntry(nginxconfig.RenderOptions{
|
||||
Domain: domain, Scheme: "https", Port: status.ConfiguredPort,
|
||||
}, s.router.Routes(), port)
|
||||
}
|
||||
}
|
||||
if err := tlsservice.InstallUploadedCertificateWithApply(service.BaseDir(), domain, req.CertificatePEM, req.PrivateKeyPEM, apply); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
store.Audit(c.GetString("username"), "tls_certificate_uploaded", domain, "upload")
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true, "status": tlsservice.Status()})
|
||||
}
|
||||
Reference in new issue
Block a user