Files
SamWaf/api/waf_vpconfig_api.go
2025-11-13 17:01:09 +08:00

277 lines
6.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package api
import (
"SamWaf/global"
"SamWaf/model/common/response"
"SamWaf/model/request"
response2 "SamWaf/model/response"
"SamWaf/utils"
"SamWaf/wafconfig"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"path/filepath"
"time"
"github.com/gin-gonic/gin"
)
type WafVpConfigApi struct {
}
// UpdateIpWhitelistApi 更新IP白名单配置
func (w *WafVpConfigApi) UpdateIpWhitelistApi(c *gin.Context) {
var req request.WafVpConfigIpWhitelistUpdateReq
err := c.ShouldBindJSON(&req)
if err == nil {
// 调用配置文件更新函数
err = wafconfig.UpdateIpWhitelist(req.IpWhitelist)
if err != nil {
response.FailWithMessage("更新IP白名单失败: "+err.Error(), c)
} else {
response.OkWithMessage("更新IP白名单成功", c)
}
} else {
response.FailWithMessage("解析请求失败", c)
}
}
// GetIpWhitelistApi 获取IP白名单配置
func (w *WafVpConfigApi) GetIpWhitelistApi(c *gin.Context) {
// 直接从全局变量获取IP白名单
ipWhitelist := global.GWAF_IP_WHITELIST
// 构造响应数据
resp := response2.WafVpConfigIpWhitelistGetResp{
IpWhitelist: ipWhitelist,
}
response.OkWithDetailed(resp, "获取IP白名单成功", c)
}
// UpdateSslEnableApi 更新SSL启用状态
func (w *WafVpConfigApi) UpdateSslEnableApi(c *gin.Context) {
var req request.WafVpConfigSslEnableUpdateReq
err := c.ShouldBindJSON(&req)
if err == nil {
// 调用配置文件更新函数
err = wafconfig.UpdateSslEnable(req.SslEnable)
if err != nil {
response.FailWithMessage("更新SSL启用状态失败: "+err.Error(), c)
} else {
response.OkWithMessage("更新SSL启用状态成功需要重启管理端生效", c)
}
} else {
response.FailWithMessage("解析请求失败", c)
}
}
// GetSslStatusApi 获取SSL状态
func (w *WafVpConfigApi) GetSslStatusApi(c *gin.Context) {
// 获取SSL启用状态
sslEnable := global.GWAF_SSL_ENABLE
// 检查证书文件是否存在
certPath := filepath.Join(utils.GetCurrentDir(), "data", "ssl", "manager", "domain.crt")
keyPath := filepath.Join(utils.GetCurrentDir(), "data", "ssl", "manager", "domain.key")
hasCert := false
certExpireAt := ""
certDomain := ""
certContent := ""
keyContent := ""
// 检查证书文件是否存在
if _, err := os.Stat(certPath); err == nil {
if _, err := os.Stat(keyPath); err == nil {
hasCert = true
// 读取证书文件获取证书信息
certInfo := getCertInfo(certPath)
certExpireAt = certInfo.ExpireAt
certDomain = certInfo.Domain
// 读取证书内容
if certData, err := os.ReadFile(certPath); err == nil {
certContent = string(certData)
}
// 读取私钥内容
if keyData, err := os.ReadFile(keyPath); err == nil {
keyContent = string(keyData)
}
}
}
// 构造响应数据
resp := response2.WafVpConfigSslStatusGetResp{
SslEnable: sslEnable,
HasCert: hasCert,
CertExpireAt: certExpireAt,
CertDomain: certDomain,
CertContent: certContent,
KeyContent: keyContent,
}
response.OkWithDetailed(resp, "获取SSL状态成功", c)
}
// UploadSslCertApi 上传SSL证书
func (w *WafVpConfigApi) UploadSslCertApi(c *gin.Context) {
var req request.WafVpConfigSslUploadReq
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage("解析请求失败", c)
return
}
// 验证证书内容不为空
if req.CertContent == "" || req.KeyContent == "" {
response.FailWithMessage("证书内容和私钥内容不能为空", c)
return
}
// 校验证书可用性
if err := validateCertificate(req.CertContent, req.KeyContent); err != nil {
response.FailWithMessage("证书校验失败: "+err.Error(), c)
return
}
// 创建目录
sslDir := filepath.Join(utils.GetCurrentDir(), "data", "ssl", "manager")
if err := os.MkdirAll(sslDir, 0755); err != nil {
response.FailWithMessage("创建证书目录失败: "+err.Error(), c)
return
}
// 保存证书文件
certPath := filepath.Join(sslDir, "domain.crt")
keyPath := filepath.Join(sslDir, "domain.key")
// 写入证书文件
if err := os.WriteFile(certPath, []byte(req.CertContent), 0644); err != nil {
response.FailWithMessage("保存证书文件失败: "+err.Error(), c)
return
}
// 写入私钥文件
if err := os.WriteFile(keyPath, []byte(req.KeyContent), 0600); err != nil {
// 如果私钥保存失败,删除证书文件
os.Remove(certPath)
response.FailWithMessage("保存私钥文件失败: "+err.Error(), c)
return
}
response.OkWithMessage("上传SSL证书成功需要重启管理端生效", c)
}
// validateCertificate 校验证书可用性
func validateCertificate(certContent, keyContent string) error {
// 解析证书
certBlock, _ := pem.Decode([]byte(certContent))
if certBlock == nil {
return fmt.Errorf("无效的证书格式")
}
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return fmt.Errorf("解析证书失败: %v", err)
}
// 检查证书是否过期
now := time.Now()
if now.Before(cert.NotBefore) {
return fmt.Errorf("证书尚未生效")
}
if now.After(cert.NotAfter) {
return fmt.Errorf("证书已过期")
}
// 解析私钥
keyBlock, _ := pem.Decode([]byte(keyContent))
if keyBlock == nil {
return fmt.Errorf("无效的私钥格式")
}
// 尝试加载证书和私钥配对
_, err = tls.X509KeyPair([]byte(certContent), []byte(keyContent))
if err != nil {
return fmt.Errorf("证书和私钥不匹配: %v", err)
}
return nil
}
// CertInfo 证书信息
type CertInfo struct {
ExpireAt string // 过期时间
Domain string // 域名
}
// getCertInfo 获取证书信息
func getCertInfo(certPath string) CertInfo {
info := CertInfo{
ExpireAt: "",
Domain: "",
}
certData, err := os.ReadFile(certPath)
if err != nil {
return info
}
certBlock, _ := pem.Decode(certData)
if certBlock == nil {
return info
}
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return info
}
// 获取过期时间
info.ExpireAt = cert.NotAfter.Format("2006-01-02 15:04:05")
// 获取域名信息
if cert.Subject.CommonName != "" {
info.Domain = cert.Subject.CommonName
}
// 如果有SANSubject Alternative Name优先使用
if len(cert.DNSNames) > 0 {
// 将所有域名用逗号连接
info.Domain = ""
for i, dns := range cert.DNSNames {
if i > 0 {
info.Domain += ", "
}
info.Domain += dns
}
}
return info
}
// RestartManagerApi 重启管理端
func (w *WafVpConfigApi) RestartManagerApi(c *gin.Context) {
response.OkWithMessage("管理端将在1秒后重启请稍候5-10秒后重新访问", c)
// 延迟后重启,给时间返回响应
go func() {
time.Sleep(1 * time.Second)
// 触发重启通道
if global.GWAF_CHAN_MANAGER_RESTART != nil {
select {
case global.GWAF_CHAN_MANAGER_RESTART <- 1:
// 发送成功
default:
// 通道已满,说明已经有重启请求在处理中
}
}
}()
}