mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-27 00:59:07 +02:00
[management] Expire unvalidated custom domain registrations (#7497)
Prevent unvalidated registrations from reserving domain names indefinitely. Give pending registrations a 48-hour validation window and clean up expired entries at startup and every 60 minutes. Emit CustomDomainValidationExpired for each deletion and preserve registrations referenced by services. Reject validation after expiry and prevent concurrent validation from recreating deleted registrations. Normalize domain names with the shared parser before registration. Migrate existing pending registrations to receive a fresh 48-hour validation window.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/domain"
|
||||
)
|
||||
|
||||
// MigrateCustomDomainValidationExpiry gives existing pending registrations a validation window.
|
||||
func MigrateCustomDomainValidationExpiry(ctx context.Context, db *gorm.DB) error {
|
||||
result := db.WithContext(ctx).Model(&domain.Domain{}).
|
||||
Where("validated = ? AND validation_expires_at IS NULL", false).
|
||||
Update("validation_expires_at", time.Now().UTC().Add(domain.ValidationTTL))
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("backfill custom domain validation expiry: %w", result.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package migration_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/domain"
|
||||
"github.com/netbirdio/netbird/management/server/migration"
|
||||
)
|
||||
|
||||
func TestMigrateCustomDomainValidationExpiry(t *testing.T) {
|
||||
db := setupDatabase(t)
|
||||
require.NoError(t, db.AutoMigrate(&domain.Domain{}))
|
||||
t.Cleanup(func() { require.NoError(t, db.Migrator().DropTable(&domain.Domain{})) })
|
||||
ctx := context.Background()
|
||||
existingDeadline := time.Now().UTC().Add(time.Hour).Truncate(time.Second)
|
||||
rows := []domain.Domain{
|
||||
{ID: "legacy", Domain: "legacy.example.com"},
|
||||
{ID: "validated", Domain: "validated.example.com", Validated: true},
|
||||
{ID: "pending", Domain: "pending.example.com", ValidationExpiresAt: &existingDeadline},
|
||||
}
|
||||
require.NoError(t, db.Create(&rows).Error)
|
||||
before := time.Now().UTC()
|
||||
require.NoError(t, migration.MigrateCustomDomainValidationExpiry(ctx, db))
|
||||
after := time.Now().UTC()
|
||||
var migrated domain.Domain
|
||||
require.NoError(t, db.First(&migrated, "id = ?", "legacy").Error)
|
||||
require.NotNil(t, migrated.ValidationExpiresAt)
|
||||
assert.WithinRange(t, *migrated.ValidationExpiresAt, before.Truncate(time.Millisecond).Add(48*time.Hour), after.Add(48*time.Hour+time.Millisecond), "legacy pending registrations get a full window")
|
||||
deadline := *migrated.ValidationExpiresAt
|
||||
require.NoError(t, migration.MigrateCustomDomainValidationExpiry(ctx, db))
|
||||
require.NoError(t, db.First(&migrated, "id = ?", "legacy").Error)
|
||||
assert.Equal(t, deadline, *migrated.ValidationExpiresAt, "repeated migration must not extend the deadline")
|
||||
var validated, pending domain.Domain
|
||||
require.NoError(t, db.First(&validated, "id = ?", "validated").Error)
|
||||
require.NoError(t, db.First(&pending, "id = ?", "pending").Error)
|
||||
assert.Nil(t, validated.ValidationExpiresAt, "validated domains do not acquire an expiry")
|
||||
require.NotNil(t, pending.ValidationExpiresAt)
|
||||
assert.WithinDuration(t, existingDeadline, *pending.ValidationExpiresAt, 0, "existing deadlines must be preserved")
|
||||
}
|
||||
Reference in New Issue
Block a user