update
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m23s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 56s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 52s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m42s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m19s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 54s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 2m3s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m1s
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m23s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 56s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 52s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m42s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m19s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 54s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 2m3s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m1s
This commit is contained in:
@@ -4,11 +4,17 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -62,3 +68,90 @@ func ClientTLSConfig() *tls.Config {
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
|
||||
// ClientTLSConfigWithTOFU 创建带 TOFU 验证的客户端 TLS 配置
|
||||
// serverAddr: 服务器地址,用于存储指纹
|
||||
// dataDir: 数据目录,用于存储指纹文件
|
||||
// skipVerify: 是否跳过验证(测试环境使用)
|
||||
func ClientTLSConfigWithTOFU(serverAddr, dataDir string, skipVerify bool) *tls.Config {
|
||||
if skipVerify {
|
||||
return &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
InsecureSkipVerify: true, // 必须为 true,因为是自签名证书
|
||||
MinVersion: tls.VersionTLS12,
|
||||
VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
return VerifyCertFingerprint(rawCerts, serverAddr, dataDir)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CertFingerprint 计算证书指纹 (SHA256)
|
||||
func CertFingerprint(certDER []byte) string {
|
||||
hash := sha256.Sum256(certDER)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// GetFingerprintPath 获取指纹文件路径
|
||||
func GetFingerprintPath(serverAddr, dataDir string) string {
|
||||
// 将服务器地址转换为安全的文件名
|
||||
safeName := strings.ReplaceAll(serverAddr, ":", "_")
|
||||
safeName = strings.ReplaceAll(safeName, "/", "_")
|
||||
return filepath.Join(dataDir, ".fingerprint_"+safeName)
|
||||
}
|
||||
|
||||
// LoadFingerprint 加载已保存的证书指纹
|
||||
func LoadFingerprint(serverAddr, dataDir string) (string, error) {
|
||||
path := GetFingerprintPath(serverAddr, dataDir)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(data)), nil
|
||||
}
|
||||
|
||||
// SaveFingerprint 保存证书指纹
|
||||
func SaveFingerprint(serverAddr, dataDir, fingerprint string) error {
|
||||
if err := os.MkdirAll(dataDir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
path := GetFingerprintPath(serverAddr, dataDir)
|
||||
return os.WriteFile(path, []byte(fingerprint), 0600)
|
||||
}
|
||||
|
||||
// VerifyCertFingerprint 验证证书指纹 (TOFU 模式)
|
||||
func VerifyCertFingerprint(rawCerts [][]byte, serverAddr, dataDir string) error {
|
||||
if len(rawCerts) == 0 {
|
||||
return fmt.Errorf("no certificate provided")
|
||||
}
|
||||
|
||||
// 计算当前证书指纹
|
||||
currentFP := CertFingerprint(rawCerts[0])
|
||||
|
||||
// 尝试加载已保存的指纹
|
||||
savedFP, err := LoadFingerprint(serverAddr, dataDir)
|
||||
if err != nil {
|
||||
// 首次连接,保存指纹
|
||||
if os.IsNotExist(err) {
|
||||
if saveErr := SaveFingerprint(serverAddr, dataDir, currentFP); saveErr != nil {
|
||||
return fmt.Errorf("failed to save fingerprint: %w", saveErr)
|
||||
}
|
||||
return nil // 首次连接,信任此证书
|
||||
}
|
||||
return fmt.Errorf("failed to load fingerprint: %w", err)
|
||||
}
|
||||
|
||||
// 验证指纹是否匹配
|
||||
if savedFP != currentFP {
|
||||
return fmt.Errorf("certificate fingerprint mismatch: possible MITM attack\n"+
|
||||
" Expected: %s\n Got: %s\n"+
|
||||
" If the server certificate was legitimately changed, delete: %s",
|
||||
savedFP, currentFP, GetFingerprintPath(serverAddr, dataDir))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
123
pkg/security/audit.go
Normal file
123
pkg/security/audit.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// EventType 安全事件类型
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
EventAuthSuccess EventType = "AUTH_SUCCESS"
|
||||
EventAuthFailed EventType = "AUTH_FAILED"
|
||||
EventInvalidToken EventType = "INVALID_TOKEN"
|
||||
EventInvalidClientID EventType = "INVALID_CLIENT_ID"
|
||||
EventConnRejected EventType = "CONN_REJECTED"
|
||||
EventConnLimit EventType = "CONN_LIMIT"
|
||||
EventWebLoginOK EventType = "WEB_LOGIN_OK"
|
||||
EventWebLoginFail EventType = "WEB_LOGIN_FAIL"
|
||||
)
|
||||
|
||||
// AuditEvent 审计事件
|
||||
type AuditEvent struct {
|
||||
Time time.Time
|
||||
Type EventType
|
||||
ClientIP string
|
||||
ClientID string
|
||||
Message string
|
||||
}
|
||||
|
||||
// AuditLogger 审计日志记录器
|
||||
type AuditLogger struct {
|
||||
mu sync.Mutex
|
||||
events []AuditEvent
|
||||
maxLen int
|
||||
}
|
||||
|
||||
var (
|
||||
defaultLogger *AuditLogger
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// GetAuditLogger 获取默认审计日志记录器
|
||||
func GetAuditLogger() *AuditLogger {
|
||||
once.Do(func() {
|
||||
defaultLogger = &AuditLogger{
|
||||
events: make([]AuditEvent, 0, 1000),
|
||||
maxLen: 1000,
|
||||
}
|
||||
})
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
// Log 记录安全事件
|
||||
func (l *AuditLogger) Log(eventType EventType, clientIP, clientID, message string) {
|
||||
event := AuditEvent{
|
||||
Time: time.Now(),
|
||||
Type: eventType,
|
||||
ClientIP: clientIP,
|
||||
ClientID: clientID,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
// 输出到标准日志
|
||||
log.Printf("[Security] %s | IP=%s | ID=%s | %s",
|
||||
eventType, clientIP, clientID, message)
|
||||
|
||||
// 保存到内存(用于审计查询)
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
l.events = append(l.events, event)
|
||||
if len(l.events) > l.maxLen {
|
||||
l.events = l.events[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// GetRecentEvents 获取最近的安全事件
|
||||
func (l *AuditLogger) GetRecentEvents(limit int) []AuditEvent {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
if limit <= 0 || limit > len(l.events) {
|
||||
limit = len(l.events)
|
||||
}
|
||||
|
||||
start := len(l.events) - limit
|
||||
result := make([]AuditEvent, limit)
|
||||
copy(result, l.events[start:])
|
||||
return result
|
||||
}
|
||||
|
||||
// 便捷函数
|
||||
func LogAuthSuccess(clientIP, clientID string) {
|
||||
GetAuditLogger().Log(EventAuthSuccess, clientIP, clientID, "authentication successful")
|
||||
}
|
||||
|
||||
func LogAuthFailed(clientIP, clientID, reason string) {
|
||||
GetAuditLogger().Log(EventAuthFailed, clientIP, clientID,
|
||||
fmt.Sprintf("authentication failed: %s", reason))
|
||||
}
|
||||
|
||||
func LogInvalidToken(clientIP string) {
|
||||
GetAuditLogger().Log(EventInvalidToken, clientIP, "", "invalid token provided")
|
||||
}
|
||||
|
||||
func LogInvalidClientID(clientIP, clientID string) {
|
||||
GetAuditLogger().Log(EventInvalidClientID, clientIP, clientID, "invalid client ID format")
|
||||
}
|
||||
|
||||
func LogConnRejected(clientIP, reason string) {
|
||||
GetAuditLogger().Log(EventConnRejected, clientIP, "", reason)
|
||||
}
|
||||
|
||||
func LogWebLogin(clientIP, username string, success bool) {
|
||||
if success {
|
||||
GetAuditLogger().Log(EventWebLoginOK, clientIP, username, "web login successful")
|
||||
} else {
|
||||
GetAuditLogger().Log(EventWebLoginFail, clientIP, username, "web login failed")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user