feat:customize acme server

#426
This commit is contained in:
samwaf
2025-08-27 11:10:06 +08:00
parent d8e187f1aa
commit 805265a488
15 changed files with 350 additions and 14 deletions

View File

@@ -44,6 +44,7 @@ type APIGroup struct {
WafVpConfigApi
WafFileApi
WafSystemMonitorApi
WafCaServerInfoApi
}
var APIGroupAPP = new(APIGroup)
@@ -102,4 +103,6 @@ var (
wafTunnelService = waf_service.WafTunnelServiceApp
wafMonitorService = waf_service.WafSystemMonitorServiceApp
wafCaServerInfoService = waf_service.WafCaServerInfoServiceApp
)

View File

@@ -0,0 +1,95 @@
package api
import (
"SamWaf/model/common/response"
"SamWaf/model/request"
"errors"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type WafCaServerInfoApi struct {
}
func (w *WafCaServerInfoApi) AddApi(c *gin.Context) {
var req request.WafCaServerInfoAddReq
err := c.ShouldBindJSON(&req)
if err == nil {
cnt := wafCaServerInfoService.CheckIsExistApi(req)
if cnt == 0 {
err = wafCaServerInfoService.AddApi(req)
if err == nil {
response.OkWithMessage("添加成功", c)
} else {
response.FailWithMessage("添加失败", c)
}
return
} else {
response.FailWithMessage("当前记录已经存在", c)
return
}
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafCaServerInfoApi) GetDetailApi(c *gin.Context) {
var req request.WafCaServerInfoDetailReq
err := c.ShouldBind(&req)
if err == nil {
bean := wafCaServerInfoService.GetDetailApi(req)
response.OkWithDetailed(bean, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafCaServerInfoApi) GetListApi(c *gin.Context) {
var req request.WafCaServerInfoSearchReq
err := c.ShouldBindJSON(&req)
if err == nil {
CaServerInfo, total, _ := wafCaServerInfoService.GetListApi(req)
response.OkWithDetailed(response.PageResult{
List: CaServerInfo,
Total: total,
PageIndex: req.PageIndex,
PageSize: req.PageSize,
}, "获取成功", c)
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafCaServerInfoApi) DelApi(c *gin.Context) {
var req request.WafCaServerInfoDelReq
err := c.ShouldBind(&req)
if err == nil {
err = wafCaServerInfoService.DelApi(req)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
response.FailWithMessage("请检测参数", c)
} else if err != nil {
response.FailWithMessage("发生错误", c)
} else {
response.OkWithMessage("删除成功", c)
}
} else {
response.FailWithMessage("解析失败", c)
}
}
func (w *WafCaServerInfoApi) ModifyApi(c *gin.Context) {
var req request.WafCaServerInfoEditReq
err := c.ShouldBindJSON(&req)
if err == nil {
err = wafCaServerInfoService.ModifyApi(req)
if err != nil {
response.FailWithMessage("编辑发生错误"+err.Error(), c)
} else {
response.OkWithMessage("编辑成功", c)
}
} else {
response.FailWithMessage("解析失败", c)
}
}

View File

@@ -9,8 +9,7 @@ import (
func TestCodeGeneration(t *testing.T) {
// 唯一校验定义字段信息的字符串
fieldDefs := []string{
"PrivateGroupName:private_group_name:string",
"PrivateGroupBelongCloud:private_group_belong_cloud:string",
"CaServerName:ca_server_name:string",
}
// 构造 `uniFields` 列表
@@ -28,6 +27,6 @@ func TestCodeGeneration(t *testing.T) {
}
}
fields := GetStructFields(model.PrivateGroup{})
CodeGeneration("PrivateGroup", fields, uniFields)
fields := GetStructFields(model.CaServerInfo{})
CodeGeneration("CaServerInfo", fields, uniFields)
}

10
model/ca_server_info.go Normal file
View File

@@ -0,0 +1,10 @@
package model
import "SamWaf/model/baseorm"
type CaServerInfo struct {
baseorm.BaseOrm
CaServerName string `json:"ca_server_name"` //CA服务器名称
CaServerAddress string `json:"ca_server_address"` //CA服务器地址
Remarks string `json:"remarks"` //备注
}

View File

@@ -0,0 +1,25 @@
package request
import "SamWaf/model/common/request"
type WafCaServerInfoAddReq struct {
CaServerName string `json:"ca_server_name" form:"ca_server_name"`
CaServerAddress string `json:"ca_server_address" form:"ca_server_address"`
Remarks string `json:"remarks" form:"remarks"`
}
type WafCaServerInfoEditReq struct {
Id string `json:"id"`
CaServerName string `json:"ca_server_name" form:"ca_server_name"`
CaServerAddress string `json:"ca_server_address" form:"ca_server_address"`
Remarks string `json:"remarks" form:"remarks"`
}
type WafCaServerInfoDetailReq struct {
Id string `json:"id" form:"id"`
}
type WafCaServerInfoDelReq struct {
Id string `json:"id" form:"id"`
}
type WafCaServerInfoSearchReq struct {
request.PageInfo
}

View File

@@ -42,6 +42,7 @@ type ApiGroup struct {
WafVpConfigRouter
WafFileRouter
WafSystemMonitorRouter
WafCaServerInfoRouter
}
type PublicApiGroup struct {
LoginRouter

View File

@@ -0,0 +1,19 @@
package router
import (
"SamWaf/api"
"github.com/gin-gonic/gin"
)
type WafCaServerInfoRouter struct {
}
func (receiver *WafCaServerInfoRouter) InitWafCaServerInfoRouter(group *gin.RouterGroup) {
api := api.APIGroupAPP.WafCaServerInfoApi
router := group.Group("")
router.POST("/samwaf/wafhost/caserverinfo/add", api.AddApi)
router.POST("/samwaf/wafhost/caserverinfo/list", api.GetListApi)
router.GET("/samwaf/wafhost/caserverinfo/detail", api.GetDetailApi)
router.POST("/samwaf/wafhost/caserverinfo/edit", api.ModifyApi)
router.GET("/samwaf/wafhost/caserverinfo/del", api.DelApi)
}

View File

@@ -0,0 +1,133 @@
package waf_service
import (
"SamWaf/common/uuid"
"SamWaf/customtype"
"SamWaf/global"
"SamWaf/model"
"SamWaf/model/baseorm"
"SamWaf/model/request"
"errors"
"time"
)
type WafCaServerInfoService struct{}
var WafCaServerInfoServiceApp = new(WafCaServerInfoService)
func (receiver *WafCaServerInfoService) AddApi(req request.WafCaServerInfoAddReq) error {
var bean = &model.CaServerInfo{
BaseOrm: baseorm.BaseOrm{
Id: uuid.GenUUID(),
USER_CODE: global.GWAF_USER_CODE,
Tenant_ID: global.GWAF_TENANT_ID,
CREATE_TIME: customtype.JsonTime(time.Now()),
UPDATE_TIME: customtype.JsonTime(time.Now()),
},
CaServerName: req.CaServerName,
CaServerAddress: req.CaServerAddress,
Remarks: req.Remarks,
}
global.GWAF_LOCAL_DB.Create(bean)
return nil
}
func (receiver *WafCaServerInfoService) CheckIsExistApi(req request.WafCaServerInfoAddReq) int {
var total int64 = 0
/*where条件*/
var whereField = ""
var whereValues []interface{}
//where字段
whereField = ""
if len(req.CaServerName) > 0 {
if len(whereField) > 0 {
whereField = whereField + " and "
}
whereField = whereField + " ca_server_name=? "
}
//where字段赋值
if len(req.CaServerName) > 0 {
if len(whereField) > 0 {
whereValues = append(whereValues, req.CaServerName)
}
}
global.GWAF_LOCAL_DB.Model(&model.CaServerInfo{}).Where(whereField, whereValues...).Count(&total)
return int(total)
}
func (receiver *WafCaServerInfoService) ModifyApi(req request.WafCaServerInfoEditReq) error {
// 根据唯一字段生成查询条件只有在UniFields不为空时才进行存在性检查
var total int64 = 0
/*where条件*/
var whereField = ""
var whereValues []interface{}
//where字段
whereField = ""
if len(req.CaServerName) > 0 {
if len(whereField) > 0 {
whereField = whereField + " and "
}
whereField = whereField + " ca_server_name=? "
}
//where字段赋值
if len(req.CaServerName) > 0 {
whereValues = append(whereValues, req.CaServerName)
}
global.GWAF_LOCAL_DB.Model(&model.CaServerInfo{}).Where(whereField, whereValues...).Count(&total)
// 查询是否已存在记录
var bean model.CaServerInfo
global.GWAF_LOCAL_DB.Model(&model.CaServerInfo{}).Where(whereField, whereValues...).Limit(1).Find(&bean)
if int(total) > 0 && bean.Id != "" && bean.Id != req.Id {
return errors.New("当前记录已经存在")
}
beanMap := map[string]interface{}{
"CaServerName": req.CaServerName,
"CaServerAddress": req.CaServerAddress,
"Remarks": req.Remarks,
"UPDATE_TIME": customtype.JsonTime(time.Now()),
}
err := global.GWAF_LOCAL_DB.Model(model.CaServerInfo{}).Where("id = ?", req.Id).Updates(beanMap).Error
return err
}
func (receiver *WafCaServerInfoService) GetDetailApi(req request.WafCaServerInfoDetailReq) model.CaServerInfo {
var bean model.CaServerInfo
global.GWAF_LOCAL_DB.Where("id=?", req.Id).Find(&bean)
return bean
}
func (receiver *WafCaServerInfoService) GetDetailByIdApi(id string) model.CaServerInfo {
var bean model.CaServerInfo
global.GWAF_LOCAL_DB.Where("id=?", id).Find(&bean)
return bean
}
func (receiver *WafCaServerInfoService) GetListApi(req request.WafCaServerInfoSearchReq) ([]model.CaServerInfo, int64, error) {
var list []model.CaServerInfo
var total int64 = 0
global.GWAF_LOCAL_DB.Model(&model.CaServerInfo{}).Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&list)
global.GWAF_LOCAL_DB.Model(&model.CaServerInfo{}).Count(&total)
return list, total, nil
}
func (receiver *WafCaServerInfoService) DelApi(req request.WafCaServerInfoDelReq) error {
var bean model.CaServerInfo
err := global.GWAF_LOCAL_DB.Where("id = ?", req.Id).First(&bean).Error
if err != nil {
return err
}
err = global.GWAF_LOCAL_DB.Where("id = ?", req.Id).Delete(model.CaServerInfo{}).Error
return err
}

View File

@@ -171,3 +171,27 @@ func (receiver *WafSSLOrderService) GetLastedInfo(hostCode string) (model.SslOrd
return bean, nil
}
// GetCAServerAddress 根据CA服务器名称获取地址
func GetCAServerAddress(caServerName string) string {
// 如果没有指定CA服务器名称默认使用letsencrypt
if caServerName == "" {
caServerName = "letsencrypt"
}
// 从数据库查询CA服务器信息
var caServer model.CaServerInfo
err := global.GWAF_LOCAL_DB.Where("ca_server_name = ?", caServerName).First(&caServer).Error
// 如果查询失败或没有找到记录返回默认的letsencrypt地址
if err != nil {
return "https://acme-v02.api.letsencrypt.org/directory"
}
// 如果找到记录但地址为空,也返回默认地址
if caServer.CaServerAddress == "" {
return "https://acme-v02.api.letsencrypt.org/directory"
}
return caServer.CaServerAddress
}

View File

@@ -38,7 +38,7 @@ func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
return u.key
}
func RegistrationSSL(order model.SslOrder, savePath string) (model.SslOrder, error) {
func RegistrationSSL(order model.SslOrder, savePath string, caServerAddress string) (model.SslOrder, error) {
myUser := MyUser{
Email: order.ApplyEmail,
}
@@ -67,8 +67,7 @@ func RegistrationSSL(order model.SslOrder, savePath string) (model.SslOrder, err
//order.ApplyKey = privateKey
config := lego.NewConfig(&myUser)
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
config.CADirURL = lego.LEDirectoryProduction // 测试用 LEDirectoryStaging 正式用 LEDirectoryProduction
config.CADirURL = caServerAddress
config.Certificate.KeyType = certcrypto.RSA2048
// A client facilitates communication with the CA server.
@@ -137,7 +136,7 @@ func RegistrationSSL(order model.SslOrder, savePath string) (model.SslOrder, err
return order, nil
}
func ReNewSSL(order model.SslOrder, savePath string) (model.SslOrder, error) {
func ReNewSSL(order model.SslOrder, savePath string, caServerAddress string) (model.SslOrder, error) {
myUser := MyUser{
Email: order.ApplyEmail,
}
@@ -165,9 +164,7 @@ func ReNewSSL(order model.SslOrder, savePath string) (model.SslOrder, error) {
//order.ApplyKey = privateKey
config := lego.NewConfig(&myUser)
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
config.CADirURL = lego.LEDirectoryProduction // 测试用 LEDirectoryStaging 正式用 LEDirectoryProduction
config.CADirURL = caServerAddress
config.Certificate.KeyType = certcrypto.RSA2048
// A client facilitates communication with the CA server.

View File

@@ -24,5 +24,5 @@ func TestRegistrationSSL(t *testing.T) {
ResultCSR: nil,
Remarks: "",
}
RegistrationSSL(order, "C:\\huawei\\goproject\\SamWaf\\data\\vhost\\ssl#samwaf#com")
RegistrationSSL(order, "C:\\huawei\\goproject\\SamWaf\\data\\vhost\\ssl#samwaf#com", "https://acme-v02.api.letsencrypt.org/directory")
}

View File

@@ -158,6 +158,9 @@ func InitCoreDb(currentDir string) (bool, error) {
//隧道
db.AutoMigrate(&model.Tunnel{})
//CA服务器信息
db.AutoMigrate(&model.CaServerInfo{})
global.GWAF_LOCAL_DB.Callback().Query().Before("gorm:query").Register("tenant_plugin:before_query", before_query)
global.GWAF_LOCAL_DB.Callback().Query().Before("gorm:update").Register("tenant_plugin:before_update", before_update)

View File

@@ -203,6 +203,32 @@ func pathCoreSql(db *gorm.DB) {
} else {
zlog.Info("db", "hosts :static_site_json sensitive_paths update successfully")
}
//20250827 初始化 letsencrypt CA 服务器记录
var letsencryptCount int64
db.Model(&model.CaServerInfo{}).Where("ca_server_name = ?", "letsencrypt").Count(&letsencryptCount)
// 如果不存在 letsencrypt 记录,则创建
if letsencryptCount == 0 {
letsencryptCA := model.CaServerInfo{
BaseOrm: baseorm.BaseOrm{
Id: uuid.GenUUID(),
USER_CODE: global.GWAF_USER_CODE,
Tenant_ID: global.GWAF_TENANT_ID,
CREATE_TIME: customtype.JsonTime(time.Now()),
UPDATE_TIME: customtype.JsonTime(time.Now()),
},
CaServerName: "letsencrypt",
CaServerAddress: "https://acme-v02.api.letsencrypt.org/directory",
Remarks: "Let's Encrypt",
}
err := db.Create(&letsencryptCA).Error
if err != nil {
zlog.Error("db", "init letsencrypt CA server fail", "error", err.Error())
} else {
zlog.Info("db", "init letsencrypt CA server success")
}
}
// 记录结束时间并计算耗时
duration := time.Since(startTime)
zlog.Info("create core default value completely", "duration", duration.String())

View File

@@ -41,7 +41,7 @@ func (waf *WafEngine) ApplySSLOrder(chanType int, bean model.SslOrder) {
if filePathErr != nil {
zlog.Error("ApplySSLOrder", filePathErr.Error())
}
updateSSLOrder, err := ssl.RegistrationSSL(bean, filePath)
updateSSLOrder, err := ssl.RegistrationSSL(bean, filePath, waf_service.GetCAServerAddress(bean.ApplyPlatform))
if err == nil {
zlog.Info(fmt.Sprintf("%s 首次证书申请成功", bean.ApplyDomain))
@@ -75,7 +75,7 @@ func (waf *WafEngine) ApplySSLOrder(chanType int, bean model.SslOrder) {
if filePathErr != nil {
zlog.Error("ApplySSLOrder", filePathErr.Error())
}
updateSSLOrder, err := ssl.ReNewSSL(bean, filePath)
updateSSLOrder, err := ssl.ReNewSSL(bean, filePath, waf_service.GetCAServerAddress(bean.ApplyPlatform))
if err == nil {
zlog.Info(fmt.Sprintf("%s 证书续期申请成功", bean.ApplyDomain))

View File

@@ -76,6 +76,7 @@ func (web *WafWebManager) initRouter(r *gin.Engine) {
router.ApiGroupApp.InitWafVpConfigRouter(RouterGroup)
router.ApiGroupApp.InitWafFileRouter(RouterGroup)
router.ApiGroupApp.InitWafSystemMonitorRouter(RouterGroup)
router.ApiGroupApp.InitWafCaServerInfoRouter(RouterGroup)
}
if global.GWAF_RELEASE == "true" {