Files
GoTunnel/cmd/server/main.go
Flik 42e11e0aca
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
feat(plugin): 实现插件安全验证和审计日志功能
- 添加插件签名验证机制,支持远程证书吊销列表
- 增加插件安装时的安全检查和签名验证
- 实现插件版本存储的HMAC完整性校验
- 添加插件审计日志记录插件安装和验证事件
- 增加JS插件沙箱安全限制配置
- 添加插件商店API的签名URL字段支持
- 实现安全配置的自动刷新机制
2025-12-30 22:06:33 +08:00

223 lines
5.8 KiB
Go
Raw 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 main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/gotunnel/internal/server/app"
"github.com/gotunnel/internal/server/config"
"github.com/gotunnel/internal/server/db"
"github.com/gotunnel/internal/server/tunnel"
"github.com/gotunnel/pkg/crypto"
"github.com/gotunnel/pkg/plugin"
"github.com/gotunnel/pkg/plugin/builtin"
"github.com/gotunnel/pkg/plugin/sign"
)
func main() {
configPath := flag.String("c", "server.yaml", "config file path")
flag.Parse()
// 加载 YAML 配置
cfg, err := config.LoadServerConfig(*configPath)
if err != nil {
log.Fatalf("Load config error: %v", err)
}
// 初始化数据库
clientStore, err := db.NewSQLiteStore(cfg.Server.DBPath)
if err != nil {
log.Fatalf("Init database error: %v", err)
}
defer clientStore.Close()
// 打印 token便于客户端连接
log.Printf("[Server] Token: %s", cfg.Server.Token)
// 创建隧道服务
server := tunnel.NewServer(
clientStore,
cfg.Server.BindAddr,
cfg.Server.BindPort,
cfg.Server.Token,
cfg.Server.HeartbeatSec,
cfg.Server.HeartbeatTimeout,
)
// 配置 TLS默认启用
if !cfg.Server.TLSDisabled {
tlsConfig, err := crypto.GenerateTLSConfig()
if err != nil {
log.Fatalf("Generate TLS config error: %v", err)
}
server.SetTLSConfig(tlsConfig)
log.Printf("[Server] TLS enabled")
}
// 初始化安全配置(撤销列表和公钥列表)
initSecurityConfig()
// 初始化插件系统
registry := plugin.NewRegistry()
if err := registry.RegisterAllServer(builtin.GetServerPlugins()); err != nil {
log.Fatalf("[Plugin] Register error: %v", err)
}
server.SetPluginRegistry(registry)
log.Printf("[Plugin] Registered %d plugins", len(builtin.GetServerPlugins()))
// 加载 JS 插件配置
if len(cfg.JSPlugins) > 0 {
jsPlugins := loadJSPlugins(cfg.JSPlugins)
server.LoadJSPlugins(jsPlugins)
}
// 启动 Web 控制台
if cfg.Web.Enabled {
// 强制生成 Web 凭据(如果未配置)
if config.GenerateWebCredentials(cfg) {
log.Printf("[Web] Auto-generated credentials - Username: %s, Password: %s",
cfg.Web.Username, cfg.Web.Password)
log.Printf("[Web] Please save these credentials and update your config file")
// 保存配置以持久化凭据
if err := config.SaveServerConfig(*configPath, cfg); err != nil {
log.Printf("[Web] Warning: failed to save config: %v", err)
}
}
ws := app.NewWebServer(clientStore, server, cfg, *configPath, clientStore)
addr := fmt.Sprintf("%s:%d", cfg.Web.BindAddr, cfg.Web.BindPort)
go func() {
// 始终使用 JWT 认证
err := ws.RunWithJWT(addr, cfg.Web.Username, cfg.Web.Password, cfg.Server.Token)
if err != nil {
log.Printf("[Web] Server error: %v", err)
}
}()
log.Printf("[Web] Console running at http://%s (authentication required)", addr)
}
// 优雅关闭信号处理
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-quit
log.Printf("[Server] Received shutdown signal")
server.Shutdown(30 * time.Second)
os.Exit(0)
}()
log.Fatal(server.Run())
}
// loadJSPlugins 加载 JS 插件文件
func loadJSPlugins(configs []config.JSPluginConfig) []tunnel.JSPluginEntry {
var plugins []tunnel.JSPluginEntry
for _, cfg := range configs {
source, err := os.ReadFile(cfg.Path)
if err != nil {
log.Printf("[JSPlugin] Failed to load %s: %v", cfg.Path, err)
continue
}
// 加载签名文件
sigPath := cfg.SigPath
if sigPath == "" {
sigPath = cfg.Path + ".sig"
}
signature, err := os.ReadFile(sigPath)
if err != nil {
log.Printf("[JSPlugin] Failed to load signature for %s: %v", cfg.Name, err)
continue
}
// 服务端也验证签名,防止配置文件被篡改
if err := verifyPluginSignature(cfg.Name, string(source), string(signature)); err != nil {
log.Printf("[JSPlugin] Signature verification failed for %s: %v", cfg.Name, err)
continue
}
plugins = append(plugins, tunnel.JSPluginEntry{
Name: cfg.Name,
Source: string(source),
Signature: string(signature),
AutoPush: cfg.AutoPush,
Config: cfg.Config,
AutoStart: cfg.AutoStart,
})
log.Printf("[JSPlugin] Loaded: %s from %s (verified)", cfg.Name, cfg.Path)
}
return plugins
}
// verifyPluginSignature 验证插件签名
func verifyPluginSignature(name, source, signature string) error {
// 解码签名
signed, err := sign.DecodeSignedPlugin(signature)
if err != nil {
return fmt.Errorf("decode signature: %w", err)
}
// 检查插件是否被撤销
if revoked, reason := sign.IsPluginRevoked(name, signed.Payload.Version); revoked {
return fmt.Errorf("plugin revoked: %s", reason)
}
// 检查密钥是否已吊销
if sign.IsKeyRevoked(signed.Payload.KeyID) {
return fmt.Errorf("signing key revoked: %s", signed.Payload.KeyID)
}
// 获取公钥
pubKey, err := sign.GetPublicKeyByID(signed.Payload.KeyID)
if err != nil {
return err
}
// 验证插件名称
if signed.Payload.Name != name {
return fmt.Errorf("name mismatch: %s vs %s", signed.Payload.Name, name)
}
// 验证签名
return sign.VerifyPlugin(pubKey, signed, source)
}
// initSecurityConfig 初始化安全配置
func initSecurityConfig() {
// 配置撤销列表
sign.SetRevocationConfig(sign.RevocationConfig{
RemoteURL: config.OfficialRevocationURL,
FetchInterval: 1 * time.Hour,
RequestTimeout: 10 * time.Second,
VerifySignature: true,
})
// 配置公钥列表
sign.SetKeyListConfig(sign.KeyListConfig{
RemoteURL: config.OfficialKeyListURL,
FetchInterval: 24 * time.Hour,
RequestTimeout: 10 * time.Second,
})
// 启动后台刷新
stopCh := make(chan struct{})
go sign.StartRevocationRefresher(stopCh)
// 立即拉取一次公钥列表
if err := sign.FetchRemoteKeyList(); err != nil {
log.Printf("[Security] Fetch key list failed: %v", err)
}
log.Printf("[Security] Initialized with remote revocation and key list")
}