mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-14 16:26:57 +08:00
feat:migrate for crypto
This commit is contained in:
76
scripts/generate_rsa_keys/main.go
Normal file
76
scripts/generate_rsa_keys/main.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
keysDir := "keys"
|
||||||
|
if err := os.MkdirAll(keysDir, 0700); err != nil {
|
||||||
|
fmt.Printf("创建keys目录失败: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKeyPath := filepath.Join(keysDir, "rsa_private.key")
|
||||||
|
publicKeyPath := filepath.Join(keysDir, "rsa_private.key.pub")
|
||||||
|
|
||||||
|
if _, err := os.Stat(privateKeyPath); err == nil {
|
||||||
|
fmt.Println("RSA密钥对已存在:")
|
||||||
|
fmt.Printf(" 私钥: %s\n", privateKeyPath)
|
||||||
|
fmt.Printf(" 公钥: %s\n", publicKeyPath)
|
||||||
|
|
||||||
|
publicKeyPEM, err := ioutil.ReadFile(publicKeyPath)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("\n公钥内容:")
|
||||||
|
fmt.Println(string(publicKeyPEM))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("生成新的RSA密钥对...")
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("生成RSA密钥失败: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(privateKeyPath, privateKeyPEM, 0600); err != nil {
|
||||||
|
fmt.Printf("保存私钥失败: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyDER, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("编码公钥失败: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "PUBLIC KEY",
|
||||||
|
Bytes: publicKeyDER,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(publicKeyPath, publicKeyPEM, 0644); err != nil {
|
||||||
|
fmt.Printf("保存公钥失败: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("✓ RSA密钥对生成成功!")
|
||||||
|
fmt.Printf(" 私钥: %s\n", privateKeyPath)
|
||||||
|
fmt.Printf(" 公钥: %s\n", publicKeyPath)
|
||||||
|
fmt.Println("\n公钥内容(可用于前端配置):")
|
||||||
|
fmt.Println(string(publicKeyPEM))
|
||||||
|
fmt.Println("\n注意: 请妥善保管私钥文件,不要提交到版本控制系统中!")
|
||||||
|
}
|
||||||
278
scripts/migrate_sensitive_data/main.go
Normal file
278
scripts/migrate_sensitive_data/main.go
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"nofx/crypto"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
privateKeyPath := flag.String("key", "keys/rsa_private.key", "RSA 私钥路径")
|
||||||
|
dryRun := flag.Bool("dry-run", false, "仅检查需要迁移的数据,不写入数据库")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if err := run(*privateKeyPath, *dryRun); err != nil {
|
||||||
|
log.Fatalf("迁移失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(privateKeyPath string, dryRun bool) error {
|
||||||
|
log.SetFlags(0)
|
||||||
|
|
||||||
|
cryptoService, err := crypto.NewCryptoService(privateKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("初始化加密服务失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := openPostgres()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("连接数据库失败: %w", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
log.Printf("开始迁移 AI 模型密钥 (dry-run=%v)", dryRun)
|
||||||
|
if err := migrateAIModels(db, cryptoService, dryRun); err != nil {
|
||||||
|
return fmt.Errorf("迁移 AI 模型失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("开始迁移交易所密钥 (dry-run=%v)", dryRun)
|
||||||
|
if err := migrateExchanges(db, cryptoService, dryRun); err != nil {
|
||||||
|
return fmt.Errorf("迁移交易所失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("✓ 敏感数据迁移完成")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openPostgres() (*sql.DB, error) {
|
||||||
|
host := getEnv("POSTGRES_HOST", "localhost")
|
||||||
|
port := getEnv("POSTGRES_PORT", "5432")
|
||||||
|
dbname := getEnv("POSTGRES_DB", "nofx")
|
||||||
|
user := getEnv("POSTGRES_USER", "nofx")
|
||||||
|
password := getEnv("POSTGRES_PASSWORD", "nofx123456")
|
||||||
|
|
||||||
|
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||||
|
host, port, user, password, dbname)
|
||||||
|
|
||||||
|
db, err := sql.Open("postgres", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SetMaxOpenConns(5)
|
||||||
|
db.SetMaxIdleConns(2)
|
||||||
|
db.SetConnMaxLifetime(5 * time.Minute)
|
||||||
|
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateAIModels(db *sql.DB, cryptoService *crypto.CryptoService, dryRun bool) error {
|
||||||
|
type record struct {
|
||||||
|
ID string
|
||||||
|
UserID string
|
||||||
|
APIKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Query(`
|
||||||
|
SELECT id, user_id, COALESCE(api_key, '')
|
||||||
|
FROM ai_models
|
||||||
|
WHERE COALESCE(deleted, FALSE) = FALSE
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var records []record
|
||||||
|
for rows.Next() {
|
||||||
|
var r record
|
||||||
|
if err := rows.Scan(&r.ID, &r.UserID, &r.APIKey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
records = append(records, r)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var updated int
|
||||||
|
for _, r := range records {
|
||||||
|
if r.APIKey == "" || cryptoService.IsEncryptedStorageValue(r.APIKey) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypted, err := cryptoService.EncryptForStorage(r.APIKey, r.UserID, r.ID, "api_key")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加密 AI 模型 %s (%s) 失败: %w", r.ID, r.UserID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated++
|
||||||
|
if dryRun {
|
||||||
|
log.Printf("[DRY-RUN] AI 模型 %s (%s) 将被加密", r.ID, r.UserID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
UPDATE ai_models
|
||||||
|
SET api_key = $1, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $2 AND user_id = $3
|
||||||
|
`, encrypted, r.ID, r.UserID); err != nil {
|
||||||
|
return fmt.Errorf("更新 AI 模型 %s (%s) 失败: %w", r.ID, r.UserID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("AI 模型处理完成,需更新 %d 条记录", updated)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateExchanges(db *sql.DB, cryptoService *crypto.CryptoService, dryRun bool) error {
|
||||||
|
type record struct {
|
||||||
|
ID string
|
||||||
|
UserID string
|
||||||
|
APIKey string
|
||||||
|
SecretKey string
|
||||||
|
HyperliquidWallet string
|
||||||
|
AsterUser string
|
||||||
|
AsterSigner string
|
||||||
|
AsterPrivateKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Query(`
|
||||||
|
SELECT id, user_id,
|
||||||
|
COALESCE(api_key, '') AS api_key,
|
||||||
|
COALESCE(secret_key, '') AS secret_key,
|
||||||
|
COALESCE(hyperliquid_wallet_addr, '') AS hyperliquid_wallet_addr,
|
||||||
|
COALESCE(aster_user, '') AS aster_user,
|
||||||
|
COALESCE(aster_signer, '') AS aster_signer,
|
||||||
|
COALESCE(aster_private_key, '') AS aster_private_key
|
||||||
|
FROM exchanges
|
||||||
|
WHERE COALESCE(deleted, FALSE) = FALSE
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var records []record
|
||||||
|
for rows.Next() {
|
||||||
|
var r record
|
||||||
|
if err := rows.Scan(
|
||||||
|
&r.ID, &r.UserID,
|
||||||
|
&r.APIKey, &r.SecretKey,
|
||||||
|
&r.HyperliquidWallet,
|
||||||
|
&r.AsterUser, &r.AsterSigner, &r.AsterPrivateKey,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
records = append(records, r)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var updated int
|
||||||
|
for _, r := range records {
|
||||||
|
newAPIKey := r.APIKey
|
||||||
|
newSecretKey := r.SecretKey
|
||||||
|
newHyper := r.HyperliquidWallet
|
||||||
|
newAsterUser := r.AsterUser
|
||||||
|
newAsterSigner := r.AsterSigner
|
||||||
|
newAsterPrivate := r.AsterPrivateKey
|
||||||
|
|
||||||
|
changed := false
|
||||||
|
|
||||||
|
if r.APIKey != "" && !cryptoService.IsEncryptedStorageValue(r.APIKey) {
|
||||||
|
enc, err := cryptoService.EncryptForStorage(r.APIKey, r.UserID, r.ID, "api_key")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加密交易所 API Key 失败: %s (%s): %w", r.ID, r.UserID, err)
|
||||||
|
}
|
||||||
|
newAPIKey = enc
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if r.SecretKey != "" && !cryptoService.IsEncryptedStorageValue(r.SecretKey) {
|
||||||
|
enc, err := cryptoService.EncryptForStorage(r.SecretKey, r.UserID, r.ID, "secret_key")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加密交易所 Secret Key 失败: %s (%s): %w", r.ID, r.UserID, err)
|
||||||
|
}
|
||||||
|
newSecretKey = enc
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if r.HyperliquidWallet != "" && !cryptoService.IsEncryptedStorageValue(r.HyperliquidWallet) {
|
||||||
|
enc, err := cryptoService.EncryptForStorage(r.HyperliquidWallet, r.UserID, r.ID, "hyperliquid_wallet_addr")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加密 Hyperliquid 地址失败: %s (%s): %w", r.ID, r.UserID, err)
|
||||||
|
}
|
||||||
|
newHyper = enc
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if r.AsterUser != "" && !cryptoService.IsEncryptedStorageValue(r.AsterUser) {
|
||||||
|
enc, err := cryptoService.EncryptForStorage(r.AsterUser, r.UserID, r.ID, "aster_user")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加密 Aster 用户失败: %s (%s): %w", r.ID, r.UserID, err)
|
||||||
|
}
|
||||||
|
newAsterUser = enc
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if r.AsterSigner != "" && !cryptoService.IsEncryptedStorageValue(r.AsterSigner) {
|
||||||
|
enc, err := cryptoService.EncryptForStorage(r.AsterSigner, r.UserID, r.ID, "aster_signer")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加密 Aster Signer 失败: %s (%s): %w", r.ID, r.UserID, err)
|
||||||
|
}
|
||||||
|
newAsterSigner = enc
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if r.AsterPrivateKey != "" && !cryptoService.IsEncryptedStorageValue(r.AsterPrivateKey) {
|
||||||
|
enc, err := cryptoService.EncryptForStorage(r.AsterPrivateKey, r.UserID, r.ID, "aster_private_key")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("加密 Aster 私钥失败: %s (%s): %w", r.ID, r.UserID, err)
|
||||||
|
}
|
||||||
|
newAsterPrivate = enc
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !changed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
updated++
|
||||||
|
if dryRun {
|
||||||
|
log.Printf("[DRY-RUN] 交易所 %s (%s) 将被加密", r.ID, r.UserID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
UPDATE exchanges
|
||||||
|
SET api_key = $1,
|
||||||
|
secret_key = $2,
|
||||||
|
hyperliquid_wallet_addr = $3,
|
||||||
|
aster_user = $4,
|
||||||
|
aster_signer = $5,
|
||||||
|
aster_private_key = $6,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $7 AND user_id = $8
|
||||||
|
`, newAPIKey, newSecretKey, newHyper, newAsterUser, newAsterSigner, newAsterPrivate, r.ID, r.UserID); err != nil {
|
||||||
|
return fmt.Errorf("更新交易所 %s (%s) 失败: %w", r.ID, r.UserID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("交易所处理完成,需更新 %d 条记录", updated)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(key, fallback string) string {
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user