Skip to content

Commit 481030d

Browse files
committed
pg_basebackup: encrypt streamed WAL with new key
Before, pg_basebackup would encrypt streamed WAL according to the keys in pg_tde/wal_keys in the destination dir. This commit introduces the number of changes: pg_basebackup encrypts WAL only if the "-E --encrypt-wal" flag is provided. In such a case, it would extract the principal key, truncate pg_tde/wal_keys and encrypt WAL with a newly generated WAL key. We still expect pg_tde/wal_keys and pg_tde/1664_providers in the destination dir. In case these files are not provided, but "-E" is specified, it fails with an error. We also throw a warning if pg_basebackup runs w/o -E, but there is wal_keys on the source as WAL might be compromised, and the backup is broken For PG-1603, PG-1857
1 parent 6df714b commit 481030d

File tree

11 files changed

+165
-20
lines changed

11 files changed

+165
-20
lines changed

contrib/pg_tde/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ tap_tests = [
109109
't/key_rotate_tablespace.pl',
110110
't/key_validation.pl',
111111
't/multiple_extensions.pl',
112+
't/pg_basebackup.pl',
112113
't/pg_tde_change_key_provider.pl',
113114
't/pg_rewind_basic.pl',
114115
't/pg_rewind_databases.pl',

contrib/pg_tde/src/access/pg_tde_xlog_keys.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,6 @@ pg_tde_save_server_key_redo(const TDESignedPrincipalKeyInfo *signed_key_info)
766766
}
767767
#endif
768768

769-
#ifndef FRONTEND
770769
/*
771770
* Creates the key file and saves the principal key information.
772771
*
@@ -788,17 +787,18 @@ pg_tde_save_server_key(const TDEPrincipalKey *principal_key, bool write_xlog)
788787

789788
pg_tde_sign_principal_key_info(&signed_key_Info, principal_key);
790789

790+
#ifndef FRONTEND
791791
if (write_xlog)
792792
{
793793
XLogBeginInsert();
794794
XLogRegisterData((char *) &signed_key_Info, sizeof(TDESignedPrincipalKeyInfo));
795795
XLogInsert(RM_TDERMGR_ID, XLOG_TDE_ADD_PRINCIPAL_KEY);
796796
}
797+
#endif
797798

798799
fd = pg_tde_open_wal_key_file_write(get_wal_key_file_path(), &signed_key_Info, true, &curr_pos);
799800
CloseTransientFile(fd);
800801
}
801-
#endif
802802

803803
/*
804804
* Get the principal key from the key file. The caller must hold

contrib/pg_tde/src/access/pg_tde_xlog_smgr.c

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -355,29 +355,25 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset,
355355
return pg_pwrite(fd, enc_buff, count, offset);
356356
}
357357

358-
static ssize_t
359-
tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset,
360-
TimeLineID tli, XLogSegNo segno, int segSize)
358+
/*
359+
* Set the last (most recent) key's start location if not set.
360+
*/
361+
bool
362+
tde_ensure_xlog_key_location(WalLocation loc)
361363
{
362364
bool lastKeyUsable;
363365
bool afterWriteKey;
366+
WalLocation writeKeyLoc;
364367
#ifdef FRONTEND
365368
bool crashRecovery = false;
366369
#else
367370
bool crashRecovery = GetRecoveryState() == RECOVERY_STATE_CRASH;
368371
#endif
369372

370-
WalLocation loc = {.tli = tli};
371-
WalLocation writeKeyLoc;
372-
373-
XLogSegNoOffsetToRecPtr(segno, offset, segSize, loc.lsn);
374-
375373
/*
376-
* Set the last (most recent) key's start LSN if not set.
377-
*
378-
* This func called with WALWriteLock held, so no need in any extra sync.
374+
* On backend this called with WALWriteLock held, so no need in any extra
375+
* sync.
379376
*/
380-
381377
writeKeyLoc.lsn = TDEXLogGetEncKeyLsn();
382378
pg_read_barrier();
383379
writeKeyLoc.tli = TDEXLogGetEncKeyTli();
@@ -398,7 +394,20 @@ tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset,
398394
}
399395
}
400396

401-
if ((!afterWriteKey || !lastKeyUsable) && EncryptionKey.type != WAL_KEY_TYPE_INVALID)
397+
return lastKeyUsable && afterWriteKey;
398+
}
399+
400+
static ssize_t
401+
tdeheap_xlog_seg_write(int fd, const void *buf, size_t count, off_t offset,
402+
TimeLineID tli, XLogSegNo segno, int segSize)
403+
{
404+
bool lastKeyUsable;
405+
WalLocation loc = {.tli = tli};
406+
407+
XLogSegNoOffsetToRecPtr(segno, offset, segSize, loc.lsn);
408+
lastKeyUsable = tde_ensure_xlog_key_location(loc);
409+
410+
if (!lastKeyUsable && EncryptionKey.type != WAL_KEY_TYPE_INVALID)
402411
{
403412
return TDEXLogWriteEncryptedPagesOldKeys(fd, buf, count, offset, tli, segno, segSize);
404413
}

contrib/pg_tde/src/include/access/pg_tde_xlog_smgr.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include "postgres.h"
99

10+
#include "access/pg_tde_xlog_keys.h"
11+
1012
extern Size TDEXLogEncryptStateSize(void);
1113
extern void TDEXLogShmemInit(void);
1214
extern void TDEXLogSmgrInit(void);
@@ -16,4 +18,6 @@ extern void TDEXLogSmgrInitWriteOldKeys(void);
1618
extern void TDEXLogCryptBuffer(const void *buf, void *out_buf, size_t count, off_t offset,
1719
TimeLineID tli, XLogSegNo segno, int segSize);
1820

21+
extern bool tde_ensure_xlog_key_location(WalLocation loc);
22+
1923
#endif /* PG_TDE_XLOGSMGR_H */

contrib/pg_tde/t/pg_basebackup.pl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use strict;
2+
use warnings FATAL => 'all';
3+
use Config;
4+
use PostgreSQL::Test::Cluster;
5+
use PostgreSQL::Test::Utils;
6+
use Test::More;
7+
8+
program_help_ok('pg_basebackup');
9+
program_version_ok('pg_basebackup');
10+
program_options_handling_ok('pg_basebackup');
11+
12+
my $tempdir = PostgreSQL::Test::Utils::tempdir;
13+
14+
my $node = PostgreSQL::Test::Cluster->new('main');
15+
16+
# Initialize node without replication settings
17+
$node->init(
18+
allows_streaming => 1,
19+
extra => ['--data-checksums'],
20+
auth_extra => [ '--create-role', 'backupuser' ]);
21+
$node->start;
22+
23+
# Sanity checks for options with WAL encryption
24+
$node->command_fails_like(
25+
[ 'pg_basebackup', '-D', "$tempdir/backup", '-E', '-Ft' ],
26+
qr/can not encrypt WAL in tar mode/,
27+
'encryption in tar mode');
28+
29+
$node->command_fails_like(
30+
[ 'pg_basebackup', '-D', "$tempdir/backup", '-E', '-X', 'fetch' ],
31+
qr/WAL encryption can only be used with WAL streaming/,
32+
'encryption with WAL fetch');
33+
34+
$node->command_fails_like(
35+
[ 'pg_basebackup', '-D', "$tempdir/backup", '-E', '-X', 'none' ],
36+
qr/WAL encryption can only be used with WAL streaming/,
37+
'encryption with WAL none');
38+
39+
$node->command_fails_like(
40+
[ 'pg_basebackup', '-D', "$tempdir/backup", '-E' ],
41+
qr/could not find server principal key/,
42+
'encryption with no pg_tde dir');
43+
44+
$node->command_fails_like(
45+
[ 'pg_basebackup', '-D', "$tempdir/backup", '--encrypt-wal' ],
46+
qr/could not find server principal key/,
47+
'encryption with no pg_tde dir long flag');
48+
49+
done_testing();

contrib/pg_tde/t/pgtde.pm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ sub backup
119119
PostgreSQL::Test::RecursiveCopy::copypath($node->data_dir . '/pg_tde',
120120
$backup_dir . '/pg_tde');
121121

122+
push @{ $params{backup_options} }, "-E";
123+
122124
$node->backup($backup_name, %params);
123125
}
124126

src/bin/pg_basebackup/bbstreamer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ extern bbstreamer *bbstreamer_gzip_writer_new(char *pathname, FILE *file,
204204
pg_compress_specification *compress);
205205
extern bbstreamer *bbstreamer_extractor_new(const char *basepath,
206206
const char *(*link_map) (const char *),
207-
void (*report_output_file) (const char *));
207+
void (*report_output_file) (const char *),
208+
bool encrypted_wal);
208209

209210
extern bbstreamer *bbstreamer_gzip_decompressor_new(bbstreamer *next);
210211
extern bbstreamer *bbstreamer_lz4_compressor_new(bbstreamer *next,

src/bin/pg_basebackup/bbstreamer_file.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ typedef struct bbstreamer_extractor
3838
void (*report_output_file) (const char *);
3939
char filename[MAXPGPATH];
4040
FILE *file;
41+
bool encryped_wal;
4142
} bbstreamer_extractor;
4243

4344
static void bbstreamer_plain_writer_content(bbstreamer *streamer,
@@ -186,7 +187,8 @@ bbstreamer_plain_writer_free(bbstreamer *streamer)
186187
bbstreamer *
187188
bbstreamer_extractor_new(const char *basepath,
188189
const char *(*link_map) (const char *),
189-
void (*report_output_file) (const char *))
190+
void (*report_output_file) (const char *),
191+
bool encrypted_wal)
190192
{
191193
bbstreamer_extractor *streamer;
192194

@@ -196,6 +198,7 @@ bbstreamer_extractor_new(const char *basepath,
196198
streamer->basepath = pstrdup(basepath);
197199
streamer->link_map = link_map;
198200
streamer->report_output_file = report_output_file;
201+
streamer->encryped_wal = encrypted_wal;
199202

200203
return &streamer->base;
201204
}
@@ -240,9 +243,29 @@ bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member,
240243
extract_link(mystreamer->filename, linktarget);
241244
}
242245
else
246+
{
247+
#ifdef PERCONA_EXT
248+
/*
249+
* A streamed WAL is encrypted with the newly generated WAL key,
250+
* hence we have to prevent these files from rewriting.
251+
*/
252+
if (mystreamer->encryped_wal)
253+
{
254+
if (strcmp(member->pathname, "pg_tde/wal_keys") == 0 ||
255+
strcmp(member->pathname, "pg_tde/1664_providers") == 0)
256+
break;
257+
}
258+
else if (strcmp(member->pathname, "pg_tde/wal_keys") == 0)
259+
{
260+
pg_log_warning("the source has WAL keys, but no WAL encryption configured for the target backups");
261+
pg_log_warning_detail("This may lead to exposed data and broken backup.");
262+
pg_log_warning_hint("Run pg_basebackup with -E to encrypt streamed WAL.");
263+
}
264+
#endif
243265
mystreamer->file =
244266
create_file_for_extract(mystreamer->filename,
245267
member->mode);
268+
}
246269

247270
/* Report output file change. */
248271
if (mystreamer->report_output_file)

src/bin/pg_basebackup/pg_basebackup.c

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,12 @@
4141
#ifdef PERCONA_EXT
4242
#include "access/pg_tde_fe_init.h"
4343
#include "access/pg_tde_xlog_smgr.h"
44+
#include "access/pg_tde_xlog_keys.h"
4445
#include "access/xlog_smgr.h"
46+
#include "catalog/tde_principal_key.h"
4547
#include "pg_tde.h"
48+
49+
#define GLOBAL_DATA_TDE_OID 1664
4650
#endif
4751

4852
#define ERRCODE_DATA_CORRUPTED_BCP "XX001"
@@ -145,6 +149,7 @@ static bool showprogress = false;
145149
static bool estimatesize = true;
146150
static int verbose = 0;
147151
static IncludeWal includewal = STREAM_WAL;
152+
static bool encrypt_wal = false;
148153
static bool fastcheckpoint = false;
149154
static bool writerecoveryconf = false;
150155
static bool do_sync = true;
@@ -416,6 +421,9 @@ usage(void)
416421
printf(_(" --waldir=WALDIR location for the write-ahead log directory\n"));
417422
printf(_(" -X, --wal-method=none|fetch|stream\n"
418423
" include required WAL files with specified method\n"));
424+
#ifdef PERCONA_EXT
425+
printf(_(" -E, --encrypt-wal encrypt streamed WAL\n"));
426+
#endif
419427
printf(_(" -z, --gzip compress tar output\n"));
420428
printf(_(" -Z, --compress=[{client|server}-]METHOD[:DETAIL]\n"
421429
" compress on client or server as specified\n"));
@@ -568,6 +576,7 @@ LogStreamerMain(logstreamer_param *param)
568576
stream.synchronous = false;
569577
/* fsync happens at the end of pg_basebackup for all data */
570578
stream.do_sync = false;
579+
stream.encrypt = encrypt_wal;
571580
stream.mark_done = true;
572581
stream.partial_suffix = NULL;
573582
stream.replication_slot = replication_slot;
@@ -662,12 +671,23 @@ StartLogStreamer(char *startpos, uint32 timeline, char *sysidentifier,
662671
"pg_xlog" : "pg_wal");
663672

664673
#ifdef PERCONA_EXT
665-
{
674+
if (encrypt_wal) {
666675
char tdedir[MAXPGPATH];
676+
TDEPrincipalKey *principalKey;
667677

668678
snprintf(tdedir, sizeof(tdedir), "%s/%s", basedir, PG_TDE_DATA_DIR);
669679
pg_tde_fe_init(tdedir);
670680
TDEXLogSmgrInit();
681+
682+
principalKey = GetPrincipalKey(GLOBAL_DATA_TDE_OID, NULL);
683+
if (!principalKey)
684+
{
685+
pg_log_error("could not find server principal key");
686+
pg_log_error_hint("Copy PGDATA/pg_tde from the source to the backup destination dir.");
687+
exit(1);
688+
}
689+
pg_tde_save_server_key(principalKey, false);
690+
TDEXLogSmgrInitWrite(true);
671691
}
672692
#endif
673693

@@ -1187,7 +1207,8 @@ CreateBackupStreamer(char *archive_name, char *spclocation,
11871207
directory = get_tablespace_mapping(spclocation);
11881208
streamer = bbstreamer_extractor_new(directory,
11891209
get_tablespace_mapping,
1190-
progress_update_filename);
1210+
progress_update_filename,
1211+
encrypt_wal);
11911212
}
11921213
else
11931214
{
@@ -2393,6 +2414,9 @@ main(int argc, char **argv)
23932414
{"target", required_argument, NULL, 't'},
23942415
{"tablespace-mapping", required_argument, NULL, 'T'},
23952416
{"wal-method", required_argument, NULL, 'X'},
2417+
#ifdef PERCONA_EXT
2418+
{"encrypt-wal", no_argument, NULL, 'E'},
2419+
#endif
23962420
{"gzip", no_argument, NULL, 'z'},
23972421
{"compress", required_argument, NULL, 'Z'},
23982422
{"label", required_argument, NULL, 'l'},
@@ -2447,7 +2471,7 @@ main(int argc, char **argv)
24472471

24482472
atexit(cleanup_directories_atexit);
24492473

2450-
while ((c = getopt_long(argc, argv, "c:Cd:D:F:h:i:l:nNp:Pr:Rs:S:t:T:U:vwWX:zZ:",
2474+
while ((c = getopt_long(argc, argv, "c:Cd:D:EF:h:i:l:nNp:Pr:Rs:S:t:T:U:vwWX:zZ:",
24512475
long_options, &option_index)) != -1)
24522476
{
24532477
switch (c)
@@ -2560,6 +2584,11 @@ main(int argc, char **argv)
25602584
pg_fatal("invalid wal-method option \"%s\", must be \"fetch\", \"stream\", or \"none\"",
25612585
optarg);
25622586
break;
2587+
#ifdef PERCONA_EXT
2588+
case 'E':
2589+
encrypt_wal = true;
2590+
break;
2591+
#endif
25632592
case 'z':
25642593
compression_algorithm = "gzip";
25652594
compression_detail = NULL;
@@ -2738,6 +2767,26 @@ main(int argc, char **argv)
27382767
exit(1);
27392768
}
27402769

2770+
/*
2771+
* Sanity checks for WAL encryption.
2772+
*/
2773+
if (encrypt_wal)
2774+
{
2775+
if (includewal != STREAM_WAL)
2776+
{
2777+
pg_log_error("WAL encryption can only be used with WAL streaming");
2778+
pg_log_error_hint("Use -X stream with -E.");
2779+
exit(1);
2780+
}
2781+
2782+
if (format != 'p')
2783+
{
2784+
pg_log_error("can not encrypt WAL in tar mode");
2785+
pg_log_error_hint("Use -Fp with -E.");
2786+
exit(1);
2787+
}
2788+
}
2789+
27412790
/*
27422791
* Sanity checks for replication slot options.
27432792
*/

src/bin/pg_basebackup/receivelog.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#ifdef PERCONA_EXT
2929
#include "access/pg_tde_fe_init.h"
3030
#include "access/pg_tde_xlog_smgr.h"
31+
#include "access/xlog_smgr.h"
3132
#include "catalog/tde_global_space.h"
3233
#endif
3334

@@ -1131,8 +1132,13 @@ ProcessXLogDataMsg(PGconn *conn, StreamCtl *stream, char *copybuf, int len,
11311132
}
11321133

11331134
#ifdef PERCONA_EXT
1135+
if (stream->encrypt)
11341136
{
11351137
void* enc_buf = copybuf + hdr_len + bytes_written;
1138+
WalLocation loc = {.tli = stream->timeline};
1139+
1140+
XLogSegNoOffsetToRecPtr(segno, xlogoff, WalSegSz, loc.lsn);
1141+
tde_ensure_xlog_key_location(loc);
11361142
TDEXLogCryptBuffer(enc_buf, enc_buf, bytes_to_write,
11371143
xlogoff, stream->timeline, segno, WalSegSz);
11381144
}

0 commit comments

Comments
 (0)