feat(plugin): 实现插件安全验证和审计日志功能
Some checks failed
Build Multi-Platform Binaries / build-frontend (push) Failing after 19s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Has been skipped
Some checks failed
Build Multi-Platform Binaries / build-frontend (push) Failing after 19s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Has been skipped
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Has been skipped
- 添加插件签名验证机制,支持远程证书吊销列表 - 增加插件安装时的安全检查和签名验证 - 实现插件版本存储的HMAC完整性校验 - 添加插件审计日志记录插件安装和验证事件 - 增加JS插件沙箱安全限制配置 - 添加插件商店API的签名URL字段支持 - 实现安全配置的自动刷新机制
This commit is contained in:
@@ -1,15 +1,26 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// versionStoreData 版本存储数据结构(带 HMAC)
|
||||
type versionStoreData struct {
|
||||
Versions map[string]string `json:"versions"`
|
||||
HMAC string `json:"hmac"`
|
||||
}
|
||||
|
||||
// PluginVersionStore 插件版本存储
|
||||
type PluginVersionStore struct {
|
||||
path string
|
||||
hmacKey []byte
|
||||
versions map[string]string // pluginName -> version
|
||||
mu sync.RWMutex
|
||||
}
|
||||
@@ -18,6 +29,7 @@ type PluginVersionStore struct {
|
||||
func NewPluginVersionStore(dataDir string) (*PluginVersionStore, error) {
|
||||
store := &PluginVersionStore{
|
||||
path: filepath.Join(dataDir, "plugin_versions.json"),
|
||||
hmacKey: deriveHMACKey(dataDir),
|
||||
versions: make(map[string]string),
|
||||
}
|
||||
if err := store.load(); err != nil {
|
||||
@@ -26,6 +38,15 @@ func NewPluginVersionStore(dataDir string) (*PluginVersionStore, error) {
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// deriveHMACKey 从数据目录派生 HMAC 密钥
|
||||
func deriveHMACKey(dataDir string) []byte {
|
||||
// 使用数据目录路径和机器特征派生密钥
|
||||
hostname, _ := os.Hostname()
|
||||
seed := fmt.Sprintf("gotunnel-version-store:%s:%s", dataDir, hostname)
|
||||
hash := sha256.Sum256([]byte(seed))
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
// load 从文件加载版本信息
|
||||
func (s *PluginVersionStore) load() error {
|
||||
data, err := os.ReadFile(s.path)
|
||||
@@ -35,7 +56,26 @@ func (s *PluginVersionStore) load() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, &s.versions)
|
||||
|
||||
var storeData versionStoreData
|
||||
if err := json.Unmarshal(data, &storeData); err != nil {
|
||||
// 尝试兼容旧格式(无 HMAC)
|
||||
if err := json.Unmarshal(data, &s.versions); err != nil {
|
||||
return fmt.Errorf("invalid version store format: %w", err)
|
||||
}
|
||||
// 迁移到新格式
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// 验证 HMAC
|
||||
if !s.verifyHMAC(storeData.Versions, storeData.HMAC) {
|
||||
// HMAC 验证失败,可能被篡改,重置版本信息
|
||||
s.versions = make(map[string]string)
|
||||
return fmt.Errorf("version store integrity check failed, data may be tampered")
|
||||
}
|
||||
|
||||
s.versions = storeData.Versions
|
||||
return nil
|
||||
}
|
||||
|
||||
// save 保存版本信息到文件
|
||||
@@ -44,7 +84,16 @@ func (s *PluginVersionStore) save() error {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.MarshalIndent(s.versions, "", " ")
|
||||
|
||||
// 计算 HMAC
|
||||
hmacValue := s.computeHMAC(s.versions)
|
||||
|
||||
storeData := versionStoreData{
|
||||
Versions: s.versions,
|
||||
HMAC: hmacValue,
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(storeData, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -65,3 +114,17 @@ func (s *PluginVersionStore) SetVersion(name, version string) error {
|
||||
s.versions[name] = version
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// computeHMAC 计算版本数据的 HMAC
|
||||
func (s *PluginVersionStore) computeHMAC(versions map[string]string) string {
|
||||
data, _ := json.Marshal(versions)
|
||||
h := hmac.New(sha256.New, s.hmacKey)
|
||||
h.Write(data)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
// verifyHMAC 验证 HMAC
|
||||
func (s *PluginVersionStore) verifyHMAC(versions map[string]string, expectedHMAC string) bool {
|
||||
computed := s.computeHMAC(versions)
|
||||
return hmac.Equal([]byte(computed), []byte(expectedHMAC))
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ type PluginStoreSettings struct {
|
||||
// 官方插件商店(不可配置)
|
||||
const OfficialPluginStoreURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/store.json"
|
||||
|
||||
// 官方安全配置 URL
|
||||
const (
|
||||
OfficialRevocationURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/security/revocation.json"
|
||||
OfficialKeyListURL = "https://git.92coco.cn:8443/flik/GoTunnel-Plugins/raw/branch/main/security/keys.json"
|
||||
)
|
||||
|
||||
// ServerSettings 服务端设置
|
||||
type ServerSettings struct {
|
||||
BindAddr string `yaml:"bind_addr"`
|
||||
|
||||
@@ -574,20 +574,22 @@ func (h *APIHandler) installPluginsToClient(rw http.ResponseWriter, r *http.Requ
|
||||
|
||||
// StorePluginInfo 扩展商店插件信息
|
||||
type StorePluginInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Author string `json:"author"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
DownloadURL string `json:"download_url,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Author string `json:"author"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
DownloadURL string `json:"download_url,omitempty"`
|
||||
SignatureURL string `json:"signature_url,omitempty"`
|
||||
}
|
||||
|
||||
// StorePluginInstallRequest 从商店安装插件的请求
|
||||
type StorePluginInstallRequest struct {
|
||||
PluginName string `json:"plugin_name"`
|
||||
DownloadURL string `json:"download_url"`
|
||||
ClientID string `json:"client_id"`
|
||||
PluginName string `json:"plugin_name"`
|
||||
DownloadURL string `json:"download_url"`
|
||||
SignatureURL string `json:"signature_url"`
|
||||
ClientID string `json:"client_id"`
|
||||
}
|
||||
|
||||
// handleStorePlugins 处理扩展商店插件列表
|
||||
@@ -628,8 +630,7 @@ func (h *APIHandler) handleStorePlugins(rw http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
h.jsonResponse(rw, map[string]interface{}{
|
||||
"plugins": plugins,
|
||||
"store_url": storeURL,
|
||||
"plugins": plugins,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -646,8 +647,8 @@ func (h *APIHandler) handleStoreInstall(rw http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if req.PluginName == "" || req.DownloadURL == "" || req.ClientID == "" {
|
||||
http.Error(rw, "plugin_name, download_url and client_id required", http.StatusBadRequest)
|
||||
if req.PluginName == "" || req.DownloadURL == "" || req.ClientID == "" || req.SignatureURL == "" {
|
||||
http.Error(rw, "plugin_name, download_url, signature_url and client_id required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -678,10 +679,30 @@ func (h *APIHandler) handleStoreInstall(rw http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
// 下载签名文件
|
||||
sigResp, err := client.Get(req.SignatureURL)
|
||||
if err != nil {
|
||||
http.Error(rw, "Failed to download signature: "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer sigResp.Body.Close()
|
||||
|
||||
if sigResp.StatusCode != http.StatusOK {
|
||||
http.Error(rw, "Signature download failed with status: "+sigResp.Status, http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := io.ReadAll(sigResp.Body)
|
||||
if err != nil {
|
||||
http.Error(rw, "Failed to read signature: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 安装到客户端
|
||||
installReq := JSPluginInstallRequest{
|
||||
PluginName: req.PluginName,
|
||||
Source: string(source),
|
||||
Signature: string(signature),
|
||||
RuleName: req.PluginName,
|
||||
AutoStart: true,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user