Просмотр исходного кода

fix(migrations): use SHA-256 instead of MD5 for the enclosures unique index

PostgreSQL 18 disables MD5 when running in FIPS mode, which made
md5(url) unusable. This broke enclosure creation with:

    store: unable to create enclosure: pq: could not compute MD5
    hash: unsupported (XX000)

Replace md5(url) with encode(sha256(url::bytea), 'hex') everywhere:

- The historical migrations that created the enclosures index are
  changed to sha256 so fresh installs no longer fail while replaying
  them on FIPS-mode PostgreSQL 18.
- A new migration rebuilds enclosures_user_entry_url_unique_idx with
  sha256 to convert existing installs.
- The ON CONFLICT clause in createEnclosure is updated to match the
  new expression index.

According to `openssl speed -bytes 256 md5 sha256`, this is a performance
improvement as well :D

Finally, the PostgreSQL minimum version was bumped from 9.5 to 11, the lowest
version to support SHA256.

Fixes #4350
jvoisin 1 неделя назад
Родитель
Сommit
f22c08832a
3 измененных файлов с 24 добавлено и 4 удалено
  1. 1 1
      .github/workflows/tests.yml
  2. 22 2
      internal/database/migrations.go
  3. 1 1
      internal/storage/enclosure.go

+ 1 - 1
.github/workflows/tests.yml

@@ -34,7 +34,7 @@ jobs:
     runs-on: ubuntu-latest
     services:
       postgres:
-        image: postgres:9.5
+        image: postgres:11
         env:
           POSTGRES_USER: postgres
           POSTGRES_PASSWORD: postgres

+ 22 - 2
internal/database/migrations.go

@@ -353,7 +353,11 @@ var migrations = [...]func(tx *sql.Tx) error{
 		return err
 	},
 	func(tx *sql.Tx) (err error) {
-		sql := `CREATE INDEX enclosures_user_entry_url_idx ON enclosures(user_id, entry_id, md5(url))`
+		// This migration originally used md5(url), but it was changed to
+		// sha256 because PostgreSQL 18 disables MD5 in FIPS mode, which made
+		// fresh installs fail while replaying this migration. Existing
+		// installs that already ran it are migrated later on.
+		sql := `CREATE INDEX enclosures_user_entry_url_idx ON enclosures(user_id, entry_id, encode(sha256(url::bytea), 'hex'))`
 		_, err = tx.Exec(sql)
 		return err
 	},
@@ -724,7 +728,12 @@ var migrations = [...]func(tx *sql.Tx) error{
 		}
 
 		// Create unique index
-		_, err = tx.Exec(`CREATE UNIQUE INDEX enclosures_user_entry_url_unique_idx ON enclosures(user_id, entry_id, md5(url))`)
+		//
+		// This originally used md5(url), but it was changed to sha256 because
+		// PostgreSQL 18 disables MD5 in FIPS mode, which made fresh installs
+		// fail while replaying this migration. Existing installs that already
+		// ran it are migrated later on.
+		_, err = tx.Exec(`CREATE UNIQUE INDEX enclosures_user_entry_url_unique_idx ON enclosures(user_id, entry_id, encode(sha256(url::bytea), 'hex'))`)
 		if err != nil {
 			return err
 		}
@@ -1525,4 +1534,15 @@ var migrations = [...]func(tx *sql.Tx) error{
 		`)
 		return err
 	},
+	func(tx *sql.Tx) (err error) {
+		// PostgreSQL 18 disables MD5 when running in FIPS mode, which makes
+		// the unique index on enclosures relying on md5(url) unusable.
+		// Replace it with a SHA-256 based expression index.
+		_, err = tx.Exec(`
+			DROP INDEX IF EXISTS enclosures_user_entry_url_unique_idx;
+			CREATE UNIQUE INDEX enclosures_user_entry_url_unique_idx
+				ON enclosures (user_id, entry_id, encode(sha256(url::bytea), 'hex'));
+		`)
+		return err
+	},
 }

+ 1 - 1
internal/storage/enclosure.go

@@ -155,7 +155,7 @@ func (s *Storage) createEnclosure(tx *sql.Tx, enclosure *model.Enclosure) error
 			(url, size, mime_type, entry_id, user_id, media_progression)
 		VALUES
 			($1, $2, $3, $4, $5, $6)
-		ON CONFLICT (user_id, entry_id, md5(url)) DO NOTHING
+		ON CONFLICT (user_id, entry_id, encode(sha256(url::bytea), 'hex')) DO NOTHING
 		RETURNING
 			id
 	`