1.新增eab列表

2.申请证书新增http代理、新增ca选择(zerossl、google)、新增证书算法选择
3.修复数据库连接内存泄漏
pull/117/head
zhangchenhao 2025-05-21 11:31:36 +08:00
parent eb302776a8
commit f64d2b2764
19 changed files with 489 additions and 210 deletions

View File

@ -135,6 +135,148 @@ func DelAccess(c *gin.Context) {
return
}
func GetAllEAB(c *gin.Context) {
var form struct {
CA string `form:"ca"`
}
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
eabList, err := access.GetAllEAB(form.CA)
if err != nil {
public.FailMsg(c, err.Error())
return
}
public.SuccessData(c, eabList, 0)
return
}
func GetEABList(c *gin.Context) {
var form struct {
Search string `form:"search"`
Page int64 `form:"p"`
Limit int64 `form:"limit"`
}
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
eabList, count, err := access.GetEABList(form.Search, form.Page, form.Limit)
if err != nil {
public.FailMsg(c, err.Error())
return
}
public.SuccessData(c, eabList, count)
return
}
func AddEAB(c *gin.Context) {
var form struct {
Name string `form:"name"`
Kid string `form:"Kid"`
HmacEncoded string `form:"HmacEncoded"`
CA string `form:"ca"`
}
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
form.Name = strings.TrimSpace(form.Name)
form.Kid = strings.TrimSpace(form.Kid)
form.HmacEncoded = strings.TrimSpace(form.HmacEncoded)
form.CA = strings.TrimSpace(form.CA)
if form.Name == "" {
public.FailMsg(c, "名称不能为空")
return
}
if form.Kid == "" {
public.FailMsg(c, "ID不能为空")
return
}
if form.HmacEncoded == "" {
public.FailMsg(c, "HmacEncoded不能为空")
return
}
if form.CA == "" {
public.FailMsg(c, "CA不能为空")
return
}
err = access.AddEAB(form.Name, form.Kid, form.HmacEncoded, form.CA)
if err != nil {
public.FailMsg(c, err.Error())
}
public.SuccessMsg(c, "添加成功")
return
}
func UpdEAB(c *gin.Context) {
var form struct {
ID string `form:"id"`
Name string `form:"name"`
Kid string `form:"Kid"`
HmacEncoded string `form:"HmacEncoded"`
CA string `form:"ca"`
}
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
form.Name = strings.TrimSpace(form.Name)
form.Kid = strings.TrimSpace(form.Kid)
form.HmacEncoded = strings.TrimSpace(form.HmacEncoded)
form.CA = strings.TrimSpace(form.CA)
if form.Name == "" {
public.FailMsg(c, "名称不能为空")
return
}
if form.Kid == "" {
public.FailMsg(c, "ID不能为空")
return
}
if form.HmacEncoded == "" {
public.FailMsg(c, "HmacEncoded不能为空")
return
}
if form.CA == "" {
public.FailMsg(c, "CA不能为空")
return
}
err = access.UpdEAB(form.ID, form.Name, form.Kid, form.HmacEncoded, form.CA)
if err != nil {
public.FailMsg(c, err.Error())
}
public.SuccessMsg(c, "修改成功")
return
}
func DelEAB(c *gin.Context) {
var form struct {
ID string `form:"id"`
}
err := c.Bind(&form)
if err != nil {
public.FailMsg(c, err.Error())
return
}
form.ID = strings.TrimSpace(form.ID)
if form.ID == "" {
public.FailMsg(c, "ID不能为空")
return
}
err = access.DelEAB(form.ID)
if err != nil {
public.FailMsg(c, err.Error())
return
}
public.SuccessMsg(c, "删除成功")
return
}
func TestAccess(c *gin.Context) {
var form struct {
ID string `form:"id"`

View File

@ -32,7 +32,6 @@ func Sign(c *gin.Context) {
public.FailMsg(c, err.Error())
return
}
s.Connect()
defer s.Close()
s.TableName = "users"
res, err := s.Where("username=?", []interface{}{form.Username}).Select()

View File

@ -12,7 +12,6 @@ func GetSqlite() (*public.Sqlite, error) {
if err != nil {
return nil, err
}
s.Connect()
s.TableName = "access"
return s, nil
}

View File

@ -9,7 +9,6 @@ func GetSqliteAT() (*public.Sqlite, error) {
if err != nil {
return nil, err
}
s.Connect()
s.TableName = "access_type"
return s, nil
}

View File

@ -29,7 +29,7 @@ func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
return u.key
}
func SaveUserToDB(db *public.Sqlite, user *MyUser) error {
func SaveUserToDB(db *public.Sqlite, user *MyUser, Type string) error {
keyBytes, err := x509.MarshalPKCS8PrivateKey(user.key)
if err != nil {
return err
@ -53,13 +53,13 @@ func SaveUserToDB(db *public.Sqlite, user *MyUser) error {
"reg": regBytes,
"create_time": now,
"update_time": now,
"type": "Let's Encrypt",
"type": Type,
})
return err
}
func LoadUserFromDB(db *public.Sqlite, email string) (*MyUser, error) {
data, err := db.Where(`email=?`, []interface{}{email}).Select()
func LoadUserFromDB(db *public.Sqlite, email string, Type string) (*MyUser, error) {
data, err := db.Where(`email=? and type=?`, []interface{}{email, Type}).Select()
if err != nil {
return nil, err
}

View File

@ -22,17 +22,33 @@ import (
"github.com/go-acme/lego/v4/providers/dns/volcengine"
"github.com/go-acme/lego/v4/providers/dns/westcn"
"github.com/go-acme/lego/v4/registration"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
var AlgorithmMap = map[string]certcrypto.KeyType{
"RSA2048": certcrypto.RSA2048,
"RSA3072": certcrypto.RSA3072,
"RSA4096": certcrypto.RSA4096,
"RSA8192": certcrypto.RSA8192,
"EC256": certcrypto.EC256,
"EC384": certcrypto.EC384,
}
var CADirURLMap = map[string]string{
"Let's Encrypt": "https://acme-v02.api.letsencrypt.org/directory",
"zerossl": "https://acme.zerossl.com/v2/DV90",
"google": "https://dv.acme-v02.api.pki.goog/directory",
}
func GetSqlite() (*public.Sqlite, error) {
s, err := public.NewSqlite("data/data.db", "")
if err != nil {
return nil, err
}
s.Connect()
s.TableName = "_accounts"
return s, nil
}
@ -77,17 +93,179 @@ func GetDNSProvider(providerName string, creds map[string]string) (challenge.Pro
config.SecretKey = creds["secret_key"]
return volcengine.NewDNSProviderConfig(config)
// case "godaddy":
// config := godaddy.NewDefaultConfig()
// config.APIKey = creds["api_key"]
// config.APISecret = creds["api_secret"]
// return godaddy.NewDNSProviderConfig(config)
default:
return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName)
}
}
func GetAcmeClient(db *public.Sqlite, email, algorithm, ca, proxy, eabId string, logger *public.Logger) (*lego.Client, error) {
user, err := LoadUserFromDB(db, email, ca)
if err != nil {
logger.Debug("acme账号不存在注册新账号")
privateKey, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
user = &MyUser{
Email: email,
key: privateKey,
}
config := lego.NewConfig(user)
config.Certificate.KeyType = AlgorithmMap[algorithm]
config.CADirURL = CADirURLMap[ca]
if proxy != "" {
// 构建代理 HTTP 客户端
proxyURL, err := url.Parse(proxy) // 替换为你的代理地址
if err != nil {
return nil, fmt.Errorf("无效的代理地址: %v", err)
}
httpClient := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURL),
},
Timeout: 30 * time.Second,
}
config.HTTPClient = httpClient
}
client, err := lego.NewClient(config)
if err != nil {
return nil, err
}
logger.Debug("正在注册账号:" + email)
var reg *registration.Resource
switch ca {
case "Let's Encrypt":
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
case "zerossl", "google":
// 获取EAB参数
var eabData map[string]any
if eabId == "" {
data, err := access.GetAllEAB(ca)
if err != nil {
return nil, err
}
if len(data) <= 0 {
return nil, fmt.Errorf("未找到EAB信息")
}
eabData = data[0]
} else {
eabData, err = access.GetEAB(eabId)
if err != nil {
return nil, err
}
if eabData == nil {
return nil, fmt.Errorf("未找到EAB信息")
}
}
Kid := eabData["kid"].(string)
HmacEncoded := eabData["HmacEncoded"].(string)
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: Kid,
HmacEncoded: HmacEncoded,
})
default:
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
}
if err != nil {
return nil, err
}
user.Registration = reg
err = SaveUserToDB(db, user, ca)
if err != nil {
return nil, err
}
logger.Debug("acme账号注册并保存成功")
return client, nil
} else {
config := lego.NewConfig(user)
config.Certificate.KeyType = AlgorithmMap[algorithm]
config.CADirURL = CADirURLMap[ca]
if proxy != "" {
// 构建代理 HTTP 客户端
proxyURL, err := url.Parse(proxy) // 替换为你的代理地址
if err != nil {
return nil, fmt.Errorf("无效的代理地址: %v", err)
}
httpClient := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURL),
},
Timeout: 30 * time.Second,
}
config.HTTPClient = httpClient
}
// 初始化 ACME 客户端
client, err := lego.NewClient(config)
if err != nil {
return nil, err
}
return client, nil
}
}
func GetCert(runId string, domainArr []string, endDay int, logger *public.Logger) (map[string]any, error) {
if runId == "" {
return nil, fmt.Errorf("参数错误_runId")
}
s, err := public.NewSqlite("data/data.db", "")
if err != nil {
return nil, err
}
s.TableName = "workflow_history"
defer s.Close()
// 查询 workflowId
wh, err := s.Where("id=?", []interface{}{runId}).Select()
if err != nil {
return nil, err
}
if len(wh) <= 0 {
return nil, fmt.Errorf("未获取到对应的workflowId")
}
s.TableName = "cert"
certs, err := s.Where("workflow_id=?", []interface{}{wh[0]["workflow_id"]}).Select()
if err != nil {
return nil, err
}
if len(certs) <= 0 {
return nil, fmt.Errorf("未获取到当前工作流下的证书")
}
layout := "2006-01-02 15:04:05"
var maxDays float64
var maxItem map[string]any
for i := range certs {
if !public.ContainsAllIgnoreBRepeats(strings.Split(certs[i]["domains"].(string), ","), domainArr) {
continue
}
endTimeStr, ok := certs[i]["end_time"].(string)
if !ok {
continue
}
endTime, err := time.Parse(layout, endTimeStr)
if err != nil {
continue
}
diff := endTime.Sub(time.Now()).Hours() / 24
if diff > maxDays {
maxDays = diff
maxItem = certs[i]
}
}
if maxItem == nil {
return nil, fmt.Errorf("未获取到对应的证书")
}
if int(maxDays) <= endDay {
return nil, fmt.Errorf("证书已过期或即将过期,剩余天数:%d 小于%d天", int(maxDays), endDay)
}
// 证书未过期,直接返回
logger.Debug(fmt.Sprintf("上次证书申请成功,域名:%s剩余天数%d 大于%d天已跳过申请复用此证书", maxItem["domains"], int(maxDays), endDay))
return map[string]any{
"cert": maxItem["cert"],
"key": maxItem["key"],
"issuerCert": maxItem["issuer_cert"],
}, nil
}
func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) {
db, err := GetSqlite()
if err != nil {
@ -107,6 +285,44 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) {
if !ok {
return nil, fmt.Errorf("参数错误provider")
}
endDay := 30
switch v := cfg["end_day"].(type) {
case float64:
endDay = int(v)
case int:
endDay = v
case string:
if v != "" {
endDay, err = strconv.Atoi(v)
if err != nil {
return nil, fmt.Errorf("参数错误end_day")
}
}
case int64:
endDay = int(v)
}
algorithm, ok := cfg["algorithm"].(string)
if !ok {
algorithm = "RSA2048"
}
ca, ok := cfg["ca"].(string)
if !ok {
ca = "Let's Encrypt"
}
proxy, ok := cfg["proxy"].(string)
if !ok {
proxy = ""
}
var eabId string
switch v := cfg["eabId"].(type) {
case float64:
eabId = strconv.Itoa(int(v))
case string:
eabId = v
default:
eabId = ""
}
var providerID string
switch v := cfg["provider_id"].(type) {
case float64:
@ -178,100 +394,15 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) {
if !ok {
return nil, fmt.Errorf("参数错误_runId")
}
if runId != "" {
s, err := public.NewSqlite("data/data.db", "")
certData, err := GetCert(runId, domainArr, endDay, logger)
if err != nil {
return nil, err
}
s.Connect()
s.TableName = "workflow_history"
defer s.Close()
// 查询 workflowId
wh, err := s.Where("id=?", []interface{}{runId}).Select()
if err != nil {
return nil, err
}
if len(wh) > 0 {
s.TableName = "cert"
certs, err := s.Where("workflow_id=?", []interface{}{wh[0]["workflow_id"]}).Select()
if err != nil {
return nil, err
}
if len(certs) > 0 {
layout := "2006-01-02 15:04:05"
var maxDays float64
var maxItem map[string]any
for i := range certs {
if !public.ContainsAllIgnoreBRepeats(strings.Split(certs[i]["domains"].(string), ","), domainArr) {
continue
}
endTimeStr, ok := certs[i]["end_time"].(string)
if !ok {
continue
}
endTime, err := time.Parse(layout, endTimeStr)
if err != nil {
continue
}
diff := endTime.Sub(time.Now()).Hours() / 24
if diff > maxDays {
maxDays = diff
maxItem = certs[i]
}
}
certObj := maxItem
// 判断证书是否过期
cfgEnd, ok := cfg["end_day"].(int)
if !ok || cfgEnd <= 0 {
cfgEnd = 30
}
if int(maxDays) > cfgEnd {
// 证书未过期,直接返回
logger.Debug(fmt.Sprintf("上次证书申请成功,域名:%s剩余天数%d 大于%d天已跳过申请复用此证书", certObj["domains"], int(maxDays), cfgEnd))
return map[string]any{
"cert": certObj["cert"],
"key": certObj["key"],
"issuerCert": certObj["issuer_cert"],
}, nil
}
}
}
logger.Debug("未获取到符合条件的本地证书:" + err.Error())
} else {
return certData, nil
}
logger.Debug("正在申请证书,域名: " + domains)
user, err := LoadUserFromDB(db, email)
if err != nil {
logger.Debug("acme账号不存在注册新账号")
privateKey, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
user = &MyUser{
Email: email,
key: privateKey,
}
config := lego.NewConfig(user)
config.Certificate.KeyType = certcrypto.EC384
client, err := lego.NewClient(config)
if err != nil {
return nil, err
}
logger.Debug("正在注册账号:" + email)
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, err
}
user.Registration = reg
err = SaveUserToDB(db, user)
if err != nil {
return nil, err
}
logger.Debug("账号注册并保存成功")
}
// 初始化 ACME 客户端
client, err := lego.NewClient(lego.NewConfig(user))
// 创建 ACME 客户端
client, err := GetAcmeClient(db, email, algorithm, ca, proxy, eabId, logger)
if err != nil {
return nil, err
}

View File

@ -13,7 +13,6 @@ func GetSqlite() (*public.Sqlite, error) {
if err != nil {
return nil, err
}
s.Connect()
s.TableName = "cert"
return s, nil
}
@ -68,7 +67,6 @@ func AddCert(source, key, cert, issuer, issuerCert, domains, sha256, historyId,
if err != nil {
return err
}
s.Connect()
s.TableName = "workflow_history"
defer s.Close()
// 查询 workflowId

View File

@ -12,7 +12,6 @@ func GetWorkflowCount() (map[string]any, error) {
if err != nil {
return nil, err
}
s.Connect()
defer s.Close()
workflow, err := s.Query(`select count(*) as count,
count(case when exec_type='auto' then 1 end ) as active,
@ -71,7 +70,6 @@ func GetSiteMonitorCount() (map[string]any, error) {
if err != nil {
return nil, err
}
s.Connect()
defer s.Close()
cert, err := s.Query(`select count(*) as count,
count(case when state='' then 1 end ) as exception

View File

@ -16,7 +16,6 @@ func GetSqlite() (*public.Sqlite, error) {
if err != nil {
return nil, err
}
s.Connect()
s.TableName = "report"
return s, nil
}

View File

@ -29,7 +29,6 @@ func GetSqlite() (*public.Sqlite, error) {
if err != nil {
return nil, err
}
s.Connect()
s.TableName = "site_monitor"
return s, nil
}

View File

@ -13,7 +13,6 @@ func GetSqlite() (*public.Sqlite, error) {
if err != nil {
return nil, err
}
s.Connect()
s.TableName = "workflow"
return s, nil
}

View File

@ -13,7 +13,6 @@ func GetSqliteObjWH() (*public.Sqlite, error) {
if err != nil {
return nil, err
}
s.Connect()
s.TableName = "workflow_history"
return s, nil
}

View File

@ -45,7 +45,7 @@ func SessionAuthMiddleware() gin.HandlerFunc {
}
// 返回登录页
c.Redirect(http.StatusFound, "/login")
// c.Abort()
c.Abort()
return
} else {
if session.Get("secure") == nil || last == nil {

View File

@ -167,7 +167,7 @@ func init() {
workflow_id TEXT not null
);
create table workflow_deploy
create table IF NOT EXISTS workflow_deploy
(
id TEXT,
workflow_id TEXT,
@ -177,6 +177,19 @@ func init() {
primary key (id, workflow_id)
);
create table IF NOT EXISTS _eab
(
id integer not null
constraint _eab_pk
primary key autoincrement,
name TEXT,
Kid TEXT not null,
HmacEncoded TEXT not null,
ca TEXT not null,
create_time TEXT,
update_time TEXT
);
`)
insertDefaultData(db, "users", "INSERT INTO users (id, username, password, salt) VALUES (1, 'admin', 'xxxxxxx', '&*ghs^&%dag');")
insertDefaultData(db, "access_type", `

View File

@ -4,6 +4,7 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
@ -67,7 +68,7 @@ func VerifyCertificateAndKey(cert *x509.Certificate, privateKey crypto.PrivateKe
case *rsa.PrivateKey:
signature, err = rsa.SignPKCS1v15(nil, key, crypto.SHA256, message)
case *ecdsa.PrivateKey:
signature, err = key.Sign(nil, message, crypto.SHA256)
signature, err = key.Sign(rand.Reader, message, crypto.SHA256)
case ed25519.PrivateKey:
signature = ed25519.Sign(key, message)
default:

View File

@ -36,7 +36,7 @@ func EnsureDatabaseWithTables(targetDBPath string, baseDBPath string, tables []s
query := "SELECT sql FROM sqlite_master WHERE type='table' AND name=?"
err = baseDB.QueryRow(query, table).Scan(&createSQL)
if err != nil {
return fmt.Errorf("获取表 %s 的结构失败: %v", table, err)
return nil
}
// 2.2 在目标库中创建表

View File

@ -22,7 +22,6 @@ func GetSettingIgnoreError(key string) string {
if err != nil {
return ""
}
s.Connect()
defer s.Close()
s.TableName = "settings"
res, err := s.Where("key=?", []interface{}{key}).Select()
@ -44,7 +43,6 @@ func UpdateSetting(key, val string) error {
if err != nil {
return err
}
s.Connect()
defer s.Close()
s.TableName = "settings"
_, err = s.Where("key=?", []interface{}{key}).Update(map[string]any{"value": val})
@ -60,7 +58,6 @@ func GetSettingsFromType(typ string) ([]map[string]any, error) {
if err != nil {
return nil, err
}
s.Connect()
defer s.Close()
s.TableName = "settings"
res, err := s.Where("type=?", []interface{}{typ}).Select()

View File

@ -44,6 +44,12 @@ func Register(r *gin.Engine) {
access.POST("/upd_access", api.UpdateAccess)
access.POST("/get_all", api.GetAllAccess)
access.POST("/test_access", api.TestAccess)
access.POST("/get_eab_list", api.GetEABList)
access.POST("/add_eab", api.AddEAB)
access.POST("/del_eab", api.DelEAB)
access.POST("/upd_eab", api.UpdEAB)
access.POST("/get_all_eab", api.GetAllEAB)
}
cert := v1.Group("/cert")
{

View File

@ -83,8 +83,8 @@ func SiteMonitor() {
os.Remove(path)
}
}()
}
}
}
wg.Wait()
}
}
}
}