fix(database): blank boolean-era verifyPeerCertByName values on hosts

A host's verify_peer_cert_by_name must only hold a server name an
operator typed. The column started life as a boolean, and the migration
that was meant to discard those values runs after AutoMigrate, which has
already cast the column to text: SQLite's table rebuild stores 0/1 as
"0"/"1", and Postgres' USING col::text stores "false"/"true". The
migration then sees a text column and leaves the values alone.

Since 3.10.0 the JSON subscription lifts host TLS verification fields to
the level xray reads, so that leftover "0" is now sent as the cert name to
verify and clients such as Happ refuse the server certificate.

A one-shot HostVerifyPeerCertByNameBoolFix seeder blanks exactly the four
cast values on existing databases; real names are untouched. The
Postgres variant joins the CI schema job, since that cast only happens
there.
This commit is contained in:
MHSanaei
2026-10-12 18:16:22 +02:00
parent 6843583706
commit 74c3cc3882
3 changed files with 127 additions and 4 deletions
+20 -1
View File
@@ -431,6 +431,19 @@ func migrateHostVerifyPeerCertByNameColumn() error {
return db.Exec(`UPDATE hosts SET verify_peer_cert_by_name = '' WHERE verify_peer_cert_by_name IS NULL OR typeof(verify_peer_cert_by_name) <> 'text'`).Error
}
// AutoMigrate casts the boolean-era column to text before the migration above runs,
// so a saved false/true reads back as the cert name "0"/"false"/"1"/"true".
func blankLegacyBoolVerifyPeerCertByName() error {
return db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&model.Host{}).
Where("verify_peer_cert_by_name IN ?", []string{"0", "1", "false", "true"}).
Update("verify_peer_cert_by_name", "").Error; err != nil {
return err
}
return tx.Create(&model.HistoryOfSeeders{SeederName: "HostVerifyPeerCertByNameBoolFix"}).Error
})
}
func seedHostsFromExternalProxy() error {
var history []string
if err := db.Model(&model.HistoryOfSeeders{}).Pluck("seeder_name", &history).Error; err != nil {
@@ -1349,7 +1362,7 @@ func runSeeders(isUsersEmpty bool) error {
}
if empty && isUsersEmpty {
seeders := []string{"UserPasswordHash", "ClientsTable", "InboundClientsArrayFix", "InboundClientTgIdFix2", "InboundClientSubIdFix", "FreedomFinalRulesReverseFix", "FreedomFinalRulesPrivateEgressBlock", "UppercaseFreedomFinalRulesFix", "InboundRealityFinalmaskTcpStrip", "ApiTokensHash", "LegacyProxySettingsCleanup", "OutboundRemovedKeysFix", "FreedomDomainStrategyFix", "DNSOutboundLegacyKeysFix", "DNSOutboundQTypeZeroFix", "WireguardDomainStrategyFix", "XdnsFinalmaskNamesAddrsFix", "UDPHopClientMaskFix", "WireguardPeersToClients", "MtprotoSecretsToClients", "NodeInboundsAdopted", "ResetIpLimitNoFail2ban"}
seeders := []string{"UserPasswordHash", "ClientsTable", "InboundClientsArrayFix", "InboundClientTgIdFix2", "InboundClientSubIdFix", "FreedomFinalRulesReverseFix", "FreedomFinalRulesPrivateEgressBlock", "UppercaseFreedomFinalRulesFix", "InboundRealityFinalmaskTcpStrip", "ApiTokensHash", "LegacyProxySettingsCleanup", "OutboundRemovedKeysFix", "FreedomDomainStrategyFix", "DNSOutboundLegacyKeysFix", "DNSOutboundQTypeZeroFix", "WireguardDomainStrategyFix", "XdnsFinalmaskNamesAddrsFix", "UDPHopClientMaskFix", "HostVerifyPeerCertByNameBoolFix", "WireguardPeersToClients", "MtprotoSecretsToClients", "NodeInboundsAdopted", "ResetIpLimitNoFail2ban"}
for _, name := range seeders {
if err := db.Create(&model.HistoryOfSeeders{SeederName: name}).Error; err != nil {
return err
@@ -1502,6 +1515,12 @@ func runSeeders(isUsersEmpty bool) error {
}
}
if !slices.Contains(seedersHistory, "HostVerifyPeerCertByNameBoolFix") {
if err := blankLegacyBoolVerifyPeerCertByName(); err != nil {
return err
}
}
if !slices.Contains(seedersHistory, "NodeInboundsAdopted") {
if err := seedNodeInboundsAdopted(); err != nil {
return err