// Command reencrypt_secrets 在轮换 DATA_ENCRYPTION_KEY 时, // 把存量以密文落库的 API Client / 回调密钥从旧密钥重新加密为新密钥。 // // 典型用法: // // OLD_DATA_ENCRYPTION_KEY=<旧密钥> go run ./cmd/reencrypt_secrets # 预演 // OLD_DATA_ENCRYPTION_KEY=<旧密钥> go run ./cmd/reencrypt_secrets -apply # 真正写库 // // 详见 docs/密钥轮换说明.md package main import ( "flag" "fmt" "log" "os" "affiliate_dash/internal/config" "affiliate_dash/internal/service" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) func main() { apply := flag.Bool("apply", false, "真正写库;不加该参数时只做预演(dry-run),不修改任何数据") flag.Parse() cfg := config.Load() newKey := cfg.DataEncryptionKey // 兼容两种历史配置:显式配置过 DATA_ENCRYPTION_KEY,或曾经回退到 JWT_SECRET。 oldKey := os.Getenv("OLD_DATA_ENCRYPTION_KEY") if oldKey == "" { oldKey = os.Getenv("OLD_JWT_SECRET") } if oldKey == "" { log.Fatal("缺少旧密钥:请用 OLD_DATA_ENCRYPTION_KEY(或旧 JWT_SECRET)环境变量提供轮换前的密钥") } if oldKey == newKey { log.Fatal("新旧密钥相同,无需轮换") } oldCodec, err := service.NewSecretCodec(oldKey) if err != nil { log.Fatalf("初始化旧密钥编解码器失败: %v", err) } newCodec, err := service.NewSecretCodec(newKey) if err != nil { log.Fatalf("初始化新密钥编解码器失败: %v", err) } db, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { log.Fatalf("连接数据库失败: %v", err) } sqlDB, err := db.DB() if err != nil { log.Fatalf("获取数据库连接失败: %v", err) } defer sqlDB.Close() mode := "预演(dry-run)" if *apply { mode = "写入(apply)" } fmt.Printf("密钥轮换模式: %s\n\n", mode) results, err := service.ReencryptSecrets(db, oldCodec, newCodec, *apply) if err != nil { log.Fatalf("重加密失败: %v", err) } for _, r := range results { fmt.Printf("%-24s 扫描=%d 重加密=%d 已是新密钥=%d 失败=%d\n", r.Table, r.Report.Scanned, r.Report.Reencrypted, r.Report.AlreadyNewKey, r.Report.Failed) } if failed := service.TotalFailed(results); failed > 0 { log.Fatalf("有 %d 行新旧密钥都无法解密,请先排查数据来源,未做任何破坏性操作", failed) } if !*apply { fmt.Println("\n预演完成,未修改任何数据。确认无误后加 -apply 执行写入。") return } fmt.Println("\n重加密完成。请确认业务正常后再移除旧密钥。") }