This commit is contained in:
0xJacky 2022-02-27 00:27:17 +08:00
parent 1b4abab47f
commit 460480c64a
28 changed files with 527 additions and 357 deletions

View file

@ -1,150 +1,163 @@
package api
import (
"encoding/json"
"github.com/0xJacky/Nginx-UI/server/tool"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"log"
"net/http"
"os"
"encoding/json"
"github.com/0xJacky/Nginx-UI/server/settings"
"github.com/0xJacky/Nginx-UI/server/tool"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"log"
"net/http"
"os"
)
func CertInfo(c *gin.Context) {
domain := c.Param("domain")
domain := c.Param("domain")
key := tool.GetCertInfo(domain)
key := tool.GetCertInfo(domain)
c.JSON(http.StatusOK, gin.H{
"subject_name": key.Subject.CommonName,
"issuer_name": key.Issuer.CommonName,
"not_after": key.NotAfter,
"not_before": key.NotBefore,
})
c.JSON(http.StatusOK, gin.H{
"subject_name": key.Subject.CommonName,
"issuer_name": key.Issuer.CommonName,
"not_after": key.NotAfter,
"not_before": key.NotBefore,
})
}
func IssueCert(c *gin.Context) {
domain := c.Param("domain")
var upGrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
domain := c.Param("domain")
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(err)
return
}
}(ws)
defer func(ws *websocket.Conn) {
err := ws.Close()
if err != nil {
log.Println(err)
return
}
}(ws)
for {
// read
mt, message, err := ws.ReadMessage()
if err != nil {
break
}
if string(message) == "go" {
var m []byte
for {
// read
mt, message, err := ws.ReadMessage()
if err != nil {
break
}
if string(message) == "go" {
var m []byte
err = tool.IssueCert(domain)
if err != nil {
m, err = json.Marshal(gin.H{
"status": "error",
"message": err.Error(),
})
if settings.ServerSettings.Demo {
m, _ = json.Marshal(gin.H{
"status": "error",
"message": "this feature is not available in demo",
})
_ = ws.WriteMessage(mt, m)
return
}
if err != nil {
log.Println(err)
return
}
err = tool.IssueCert(domain)
err = ws.WriteMessage(mt, m)
if err != nil {
if err != nil {
log.Println(err)
return
}
log.Println(err)
log.Println(err)
return
}
m, err = json.Marshal(gin.H{
"status": "error",
"message": err.Error(),
})
sslCertificatePath := tool.GetNginxConfPath("ssl/" + domain + "/fullchain.cer")
_, err = os.Stat(sslCertificatePath)
if err != nil {
log.Println(err)
return
}
if err != nil {
log.Println(err)
return
}
err = ws.WriteMessage(mt, m)
log.Println("[found]", "fullchain.cer")
m, err = json.Marshal(gin.H{
"status": "success",
"message": "[found] fullchain.cer",
})
if err != nil {
log.Println(err)
return
}
if err != nil {
log.Println(err)
return
}
return
}
err = ws.WriteMessage(mt, m)
sslCertificatePath := tool.GetNginxConfPath("ssl/" + domain + "/fullchain.cer")
_, err = os.Stat(sslCertificatePath)
if err != nil {
log.Println(err)
return
}
if err != nil {
log.Println(err)
return
}
sslCertificateKeyPath := tool.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key")
_, err = os.Stat(sslCertificateKeyPath)
log.Println("[found]", "fullchain.cer")
m, err = json.Marshal(gin.H{
"status": "success",
"message": "[found] fullchain.cer",
})
if err != nil {
log.Println(err)
return
}
if err != nil {
log.Println(err)
return
}
log.Println("[found]", "cert key")
m, err = json.Marshal(gin.H{
"status": "success",
"message": "[found] cert key",
})
err = ws.WriteMessage(mt, m)
if err != nil {
log.Println(err)
}
if err != nil {
log.Println(err)
return
}
err = ws.WriteMessage(mt, m)
sslCertificateKeyPath := tool.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key")
_, err = os.Stat(sslCertificateKeyPath)
if err != nil {
log.Println(err)
}
if err != nil {
log.Println(err)
return
}
log.Println("申请成功")
m, err = json.Marshal(gin.H{
"status": "success",
"message": "申请成功",
"ssl_certificate": sslCertificatePath,
"ssl_certificate_key": sslCertificateKeyPath,
})
log.Println("[found]", "cert key")
m, err = json.Marshal(gin.H{
"status": "success",
"message": "[found] cert key",
})
if err != nil {
log.Println(err)
}
if err != nil {
log.Println(err)
}
err = ws.WriteMessage(mt, m)
err = ws.WriteMessage(mt, m)
if err != nil {
log.Println(err)
}
}
}
if err != nil {
log.Println(err)
}
log.Println("申请成功")
m, err = json.Marshal(gin.H{
"status": "success",
"message": "申请成功",
"ssl_certificate": sslCertificatePath,
"ssl_certificate_key": sslCertificateKeyPath,
})
if err != nil {
log.Println(err)
}
err = ws.WriteMessage(mt, m)
if err != nil {
log.Println(err)
}
}
}
}

View file

@ -2,18 +2,16 @@ package api
import (
"github.com/0xJacky/Nginx-UI/server/settings"
"github.com/0xJacky/Nginx-UI/server/template"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
)
func GetTemplate(c *gin.Context) {
name := c.Param("name")
path := filepath.Join("template", name)
content, err := ioutil.ReadFile(path)
content, err := template.DistFS.ReadFile(name)
_content := string(content)
_content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}",

View file

@ -1,77 +1,85 @@
package router
import (
"bufio"
"github.com/0xJacky/Nginx-UI/server/api"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
"net/http"
"strings"
"bufio"
"github.com/0xJacky/Nginx-UI/server/api"
"github.com/0xJacky/Nginx-UI/server/settings"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(gin.Recovery())
r.Use(static.Serve("/", mustFS("")))
r.Use(static.Serve("/", mustFS("")))
r.NoRoute(func(c *gin.Context) {
accept := c.Request.Header.Get("Accept")
if strings.Contains(accept, "text/html") {
file, _ := mustFS("").Open("index.html")
stat, _ := file.Stat()
c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
bufio.NewReader(file), nil)
}
})
r.NoRoute(func(c *gin.Context) {
accept := c.Request.Header.Get("Accept")
if strings.Contains(accept, "text/html") {
file, _ := mustFS("").Open("index.html")
stat, _ := file.Stat()
c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
bufio.NewReader(file), nil)
}
})
g := r.Group("/api")
{
g.GET("install", api.InstallLockCheck)
g.POST("install", api.InstallNginxUI)
g := r.Group("/api")
{
g.POST("/login", api.Login)
g.DELETE("/logout", api.Logout)
g.GET("settings", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"demo": settings.ServerSettings.Demo,
})
})
g := g.Group("/", authRequired())
{
g.GET("/analytic", api.Analytic)
g.GET("/analytic/init", api.GetAnalyticInit)
g.GET("install", api.InstallLockCheck)
g.POST("install", api.InstallNginxUI)
g.GET("/users", api.GetUsers)
g.GET("/user/:id", api.GetUser)
g.POST("/user", api.AddUser)
g.POST("/user/:id", api.EditUser)
g.DELETE("/user/:id", api.DeleteUser)
g.POST("/login", api.Login)
g.DELETE("/logout", api.Logout)
g.GET("domains", api.GetDomains)
g.GET("domain/:name", api.GetDomain)
g.POST("domain/:name", api.EditDomain)
g.POST("domain/:name/enable", api.EnableDomain)
g.POST("domain/:name/disable", api.DisableDomain)
g.DELETE("domain/:name", api.DeleteDomain)
g := g.Group("/", authRequired())
{
g.GET("/analytic", api.Analytic)
g.GET("/analytic/init", api.GetAnalyticInit)
g.GET("configs", api.GetConfigs)
g.GET("config/:name", api.GetConfig)
g.POST("config", api.AddConfig)
g.POST("config/:name", api.EditConfig)
g.GET("/users", api.GetUsers)
g.GET("/user/:id", api.GetUser)
g.POST("/user", api.AddUser)
g.POST("/user/:id", api.EditUser)
g.DELETE("/user/:id", api.DeleteUser)
g.GET("backups", api.GetFileBackupList)
g.GET("backup/:id", api.GetFileBackup)
g.GET("domains", api.GetDomains)
g.GET("domain/:name", api.GetDomain)
g.POST("domain/:name", api.EditDomain)
g.POST("domain/:name/enable", api.EnableDomain)
g.POST("domain/:name/disable", api.DisableDomain)
g.DELETE("domain/:name", api.DeleteDomain)
g.GET("template/:name", api.GetTemplate)
g.GET("configs", api.GetConfigs)
g.GET("config/:name", api.GetConfig)
g.POST("config", api.AddConfig)
g.POST("config/:name", api.EditConfig)
g.GET("cert/issue/:domain", api.IssueCert)
g.GET("cert/:domain/info", api.CertInfo)
g.GET("backups", api.GetFileBackupList)
g.GET("backup/:id", api.GetFileBackup)
// 添加域名到自动续期列表
g.POST("cert/:domain", api.AddDomainToAutoCert)
// 从自动续期列表中删除域名
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
}
}
g.GET("template/:name", api.GetTemplate)
return r
g.GET("cert/issue/:domain", api.IssueCert)
g.GET("cert/:domain/info", api.CertInfo)
// 添加域名到自动续期列表
g.POST("cert/:domain", api.AddDomainToAutoCert)
// 从自动续期列表中删除域名
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
}
}
return r
}

View file

@ -15,6 +15,7 @@ type Server struct {
HTTPChallengePort string
Email string
Database string
Demo bool
}
var ServerSettings = &Server{
@ -22,6 +23,7 @@ var ServerSettings = &Server{
RunMode: "debug",
HTTPChallengePort: "9180",
Database: "database",
Demo: false,
}
var ConfPath string

View file

@ -0,0 +1,6 @@
package template
import "embed"
//go:embed http-conf https-conf
var DistFS embed.FS

View file

@ -48,8 +48,8 @@ func TestLego(t *testing.T) {
config := lego.NewConfig(&myUser)
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
// This CA URL is configured for a local dev instance of Boulder running in Dockerfile in a VM.
config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
config.Certificate.KeyType = certcrypto.RSA2048
// A client facilitates communication with the CA server.

View file

@ -1,169 +1,171 @@
package tool
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"github.com/0xJacky/Nginx-UI/server/model"
"github.com/0xJacky/Nginx-UI/server/settings"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"time"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"github.com/0xJacky/Nginx-UI/server/model"
"github.com/0xJacky/Nginx-UI/server/settings"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"time"
)
// MyUser You'll need a user or account type that implements acme.User
type MyUser struct {
Email string
Registration *registration.Resource
key crypto.PrivateKey
Email string
Registration *registration.Resource
key crypto.PrivateKey
}
func (u *MyUser) GetEmail() string {
return u.Email
return u.Email
}
func (u MyUser) GetRegistration() *registration.Resource {
return u.Registration
return u.Registration
}
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
return u.key
return u.key
}
func AutoCert() {
for {
log.Println("[AutoCert] Start")
autoCertList := model.GetAutoCertList()
for i := range autoCertList {
domain := autoCertList[i].Domain
key := GetCertInfo(domain)
// 未到一个月
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
continue
}
// 过一个月了
err := IssueCert(domain)
if err != nil {
log.Println(err)
}
}
time.Sleep(1 * time.Hour)
}
for {
log.Println("[AutoCert] Start")
autoCertList := model.GetAutoCertList()
for i := range autoCertList {
domain := autoCertList[i].Domain
key := GetCertInfo(domain)
// 未到一个月
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
continue
}
// 过一个月了
err := IssueCert(domain)
if err != nil {
log.Println(err)
}
}
time.Sleep(1 * time.Hour)
}
}
func GetCertInfo(domain string) (key *x509.Certificate) {
ts := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
ts := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: ts}
client := &http.Client{Transport: ts}
response, err := client.Get("https://" + domain)
response, err := client.Get("https://" + domain)
if err != nil {
return
}
if err != nil {
return
}
defer func(Body io.ReadCloser) {
err = Body.Close()
if err != nil {
log.Println(err)
return
}
}(response.Body)
defer func(Body io.ReadCloser) {
err = Body.Close()
if err != nil {
log.Println(err)
return
}
}(response.Body)
key = response.TLS.PeerCertificates[0]
key = response.TLS.PeerCertificates[0]
return
return
}
func IssueCert(domain string) error {
// Create a user. New accounts need an email and private key to start.
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Println(err)
return err
}
// Create a user. New accounts need an email and private key to start.
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Println(err)
return err
}
myUser := MyUser{
Email: settings.ServerSettings.Email,
key: privateKey,
}
myUser := MyUser{
Email: settings.ServerSettings.Email,
key: privateKey,
}
config := lego.NewConfig(&myUser)
config := lego.NewConfig(&myUser)
//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
config.Certificate.KeyType = certcrypto.RSA2048
if settings.ServerSettings.Demo {
config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
}
config.Certificate.KeyType = certcrypto.RSA2048
// A client facilitates communication with the CA server.
client, err := lego.NewClient(config)
if err != nil {
log.Println(err)
return err
}
// A client facilitates communication with the CA server.
client, err := lego.NewClient(config)
if err != nil {
log.Println(err)
return err
}
err = client.Challenge.SetHTTP01Provider(
http01.NewProviderServer("",
settings.ServerSettings.HTTPChallengePort,
),
)
if err != nil {
log.Println(err)
return err
}
err = client.Challenge.SetHTTP01Provider(
http01.NewProviderServer("",
settings.ServerSettings.HTTPChallengePort,
),
)
if err != nil {
log.Println(err)
return err
}
// New users will need to register
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
log.Println(err)
return err
}
myUser.Registration = reg
// New users will need to register
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
log.Println(err)
return err
}
myUser.Registration = reg
request := certificate.ObtainRequest{
Domains: []string{domain},
Bundle: true,
}
certificates, err := client.Certificate.Obtain(request)
if err != nil {
log.Println(err)
return err
}
saveDir := GetNginxConfPath("ssl/" + domain)
if _, err := os.Stat(saveDir); os.IsNotExist(err) {
err = os.Mkdir(saveDir, 0755)
if err != nil {
log.Println("fail to create", saveDir)
return err
}
}
request := certificate.ObtainRequest{
Domains: []string{domain},
Bundle: true,
}
certificates, err := client.Certificate.Obtain(request)
if err != nil {
log.Println(err)
return err
}
saveDir := GetNginxConfPath("ssl/" + domain)
if _, err := os.Stat(saveDir); os.IsNotExist(err) {
err = os.Mkdir(saveDir, 0755)
if err != nil {
log.Println("fail to create", saveDir)
return err
}
}
// Each certificate comes back with the cert bytes, the bytes of the client's
// private key, and a certificate URL. SAVE THESE TO DISK.
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
certificates.Certificate, 0644)
if err != nil {
log.Println(err)
return err
}
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
certificates.PrivateKey, 0644)
if err != nil {
log.Println(err)
return err
}
// Each certificate comes back with the cert bytes, the bytes of the client's
// private key, and a certificate URL. SAVE THESE TO DISK.
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
certificates.Certificate, 0644)
if err != nil {
log.Println(err)
return err
}
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
certificates.PrivateKey, 0644)
if err != nil {
log.Println(err)
return err
}
ReloadNginx()
ReloadNginx()
return nil
return nil
}