Skip to content

Commit 4d886d6

Browse files
committed
pg_rewind: Handle unchanged/truncated encrypted files
Encrypted files might remain unchanged or truncated by rewind on the target. That leaves them encrypted with the target's key wich vanishes after the pg_tde replace. So we need to ensure keys for such files. Meaning if this is an encrypted relation, we save its target key into the source's keys.
1 parent 07d91d0 commit 4d886d6

5 files changed

Lines changed: 94 additions & 7 deletions

File tree

fetools/pg18/pg_rewind/filemap.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ action_to_str(file_action_t action)
487487
return "CREATE";
488488
case FILE_ACTION_REMOVE:
489489
return "REMOVE";
490+
case FILE_ACTION_ENSURE_TDE_KEY:
491+
return "ENSURE_KEY";
490492

491493
default:
492494
return "unknown";
@@ -591,7 +593,7 @@ isRelDataFile(const char *path)
591593
/*
592594
* Sets rlocator and segNo based on given path. Returns false if didn't find
593595
* a match.
594-
*
596+
*
595597
* Only concerned with files belonging to the main fork.
596598
*/
597599
bool
@@ -719,13 +721,11 @@ decide_file_action(file_entry_t *entry)
719721
if (strstr(path, ".DS_Store") != NULL)
720722
return FILE_ACTION_NONE;
721723

722-
/*
724+
/*
723725
* Skip pg_tde key data but WAL-related stuff as WAL being replaced by
724726
* source's. We will handle the rest while re-encrypting data.
725727
*/
726-
if (strstr(path, "pg_tde/") != NULL &&
727-
strstr(path, "pg_tde/wal_keys") == NULL &&
728-
strstr(path, "pg_tde/1664_providers") == NULL)
728+
if (strstr(path, "pg_tde/") != NULL)
729729
return FILE_ACTION_NONE;
730730

731731
/*
@@ -847,14 +847,15 @@ decide_file_action(file_entry_t *entry)
847847
* in the target will be copied based on parsing the target
848848
* system's WAL, and any blocks modified in the source will be
849849
* updated after rewinding, when the source system's WAL is
850-
* replayed.
850+
* replayed. But we still have to sync source/target keys in
851+
* case it is encrypted.
851852
*/
852853
if (entry->target_size < entry->source_size)
853854
return FILE_ACTION_COPY_TAIL;
854855
else if (entry->target_size > entry->source_size)
855856
return FILE_ACTION_TRUNCATE;
856857
else
857-
return FILE_ACTION_NONE;
858+
return FILE_ACTION_ENSURE_TDE_KEY;
858859
}
859860
break;
860861

fetools/pg18/pg_rewind/filemap.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ typedef enum
2525
* blocks based on the parsed WAL) */
2626
FILE_ACTION_TRUNCATE, /* truncate local file to 'newsize' bytes */
2727
FILE_ACTION_REMOVE, /* remove local file / directory / symlink */
28+
FILE_ACTION_ENSURE_TDE_KEY, /* data file with no action, but we to check
29+
* if it is encrypted and sync source/target
30+
* keys */
2831
} file_action_t;
2932

3033
typedef enum

fetools/pg18/pg_rewind/pg_rewind.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "pg_rewind.h"
3232
#include "rewind_source.h"
3333
#include "storage/bufpage.h"
34+
#include "tde_ops.h"
3435

3536
#include "pg_tde.h"
3637
#include "access/pg_tde_fe_init.h"
@@ -612,12 +613,28 @@ perform_rewind(filemap_t *filemap, rewind_source *source,
612613
/* nothing else to do */
613614
break;
614615

616+
case FILE_ACTION_ENSURE_TDE_KEY:
617+
618+
/*
619+
* Partial rewrites will ensure the keys on their own.
620+
* Moreover, some partial updates, when the source is libpq,
621+
* may happen in the last turn, when source->finish_fetch() is
622+
* called. So running ensure_tde_keys for such files
623+
* prematurely will make them unreadable since the source key
624+
* would be updated before we use it to decrypt source data.
625+
*/
626+
if (entry->target_pages_to_overwrite.bitmapsize == 0)
627+
ensure_tde_keys(entry->path);
628+
break;
629+
615630
case FILE_ACTION_COPY:
616631
source->queue_fetch_file(source, entry->path, entry->source_size);
617632
break;
618633

619634
case FILE_ACTION_TRUNCATE:
620635
truncate_target_file(entry->path, entry->source_size);
636+
if (entry->target_pages_to_overwrite.bitmapsize == 0)
637+
ensure_tde_keys(entry->path);
621638
break;
622639

623640
case FILE_ACTION_COPY_TAIL:

meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ tap_tests = [
320320
't/pg_rewind_basic.pl',
321321
't/pg_rewind_databases.pl',
322322
't/pg_rewind_enc_copy_blocks.pl',
323+
't/pg_rewind_enc_unchanged_rel.pl',
323324
't/pg_rewind_extrafiles.pl',
324325
't/pg_rewind_growing_files.pl',
325326
't/pg_rewind_keep_recycled_wals.pl',

t/pg_rewind_enc_unchanged_rel.pl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
2+
# Copyright (c) 2021-2024, PostgreSQL Global Development Group
3+
4+
use strict;
5+
use warnings FATAL => 'all';
6+
use PostgreSQL::Test::Utils;
7+
use Test::More;
8+
9+
use FindBin;
10+
use lib $FindBin::RealBin;
11+
12+
use RewindTest;
13+
14+
sub run_test
15+
{
16+
my $test_mode = shift;
17+
my $extra_name = shift;
18+
my $extra_conf = shift;
19+
20+
my $cluster_name = $test_mode;
21+
22+
$cluster_name = $cluster_name . $extra_name if defined $extra_name;
23+
24+
RewindTest::setup_cluster($cluster_name, [], $extra_conf);
25+
RewindTest::start_primary();
26+
RewindTest::create_standby($cluster_name);
27+
28+
primary_psql(
29+
"CREATE TABLE tbl (id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, f1 TEXT) USING tde_heap"
30+
);
31+
primary_psql(
32+
"INSERT INTO tbl (f1) SELECT repeat('abcdeF', 1000) FROM generate_series(1, 1000)"
33+
);
34+
primary_psql("CHECKPOINT");
35+
36+
RewindTest::promote_standby();
37+
38+
39+
# Makes an index relation to remain unchanged on target. So test that we
40+
# preserve a target's internal key for this rel
41+
standby_psql("CHECKPOINT");
42+
43+
44+
RewindTest::run_pg_rewind($test_mode);
45+
46+
check_query(
47+
'SELECT count(*) FROM tbl',
48+
qq(1000
49+
),
50+
'read-unchanged');
51+
52+
53+
RewindTest::clean_rewind_test();
54+
return;
55+
}
56+
57+
# Run the test in both modes
58+
run_test('local');
59+
run_test('remote');
60+
run_test('archive');
61+
62+
my @conf_params = ("pg_tde.cipher = 'aes_256'");
63+
run_test('local', "_aes_256", \@conf_params);
64+
65+
done_testing();

0 commit comments

Comments
 (0)