diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index 83202b5b87..38308d2160 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -24,11 +24,16 @@ #include "common/logging.h" #include "getopt_long.h" #include "rmgrdesc.h" +#include "common/kmgr_utils.h" +#include "common/cipher.h" static const char *progname; static int WalSegSz; +pg_cipher_ctx *encrypt_cipher_ctx = NULL; +pg_cipher_ctx *decrypt_cipher_ctx = NULL; + typedef struct XLogDumpPrivate { TimeLineID timeline; @@ -714,6 +719,9 @@ usage(void) printf(_(" %s [OPTION]... [STARTSEG [ENDSEG]]\n"), progname); printf(_("\nOptions:\n")); printf(_(" -b, --bkp-details output detailed information about backup blocks\n")); + printf(_(" -c --cluster-passphrase-command=COMMAND\n" + " set command to obtain passphrase for data encryption key\n")); + printf(_(" [-D, --pgdata=]DATADIR data directory to get the Data encryption cipher\n")); printf(_(" -e, --end=RECPTR stop reading at WAL location RECPTR\n")); printf(_(" -f, --follow keep retrying after reaching end of WAL\n")); printf(_(" -n, --limit=N number of records to display\n")); @@ -746,9 +754,14 @@ main(int argc, char **argv) XLogRecPtr first_record; char *waldir = NULL; char *errormsg; + char *pgdataDir = NULL; + char *cluster_passphrase = NULL; + bool ret = false; static struct option long_options[] = { {"bkp-details", no_argument, NULL, 'b'}, + {"pgdata", required_argument, NULL, 'D'}, + {"cluster-passphrase-command", required_argument, NULL, 'c'}, {"end", required_argument, NULL, 'e'}, {"follow", no_argument, NULL, 'f'}, {"help", no_argument, NULL, '?'}, @@ -809,7 +822,7 @@ main(int argc, char **argv) goto bad_argument; } - while ((option = getopt_long(argc, argv, "be:fn:p:r:s:t:x:z", + while ((option = getopt_long(argc, argv, "D:c:be:fn:p:r:s:t:x:z", long_options, &optindex)) != -1) { switch (option) @@ -817,6 +830,12 @@ main(int argc, char **argv) case 'b': config.bkp_details = true; break; + case 'c': + cluster_passphrase = pg_strdup(optarg); + break; + case 'D': + pgdataDir = pg_strdup(optarg); + break; case 'e': if (sscanf(optarg, "%X/%X", &xlogid, &xrecoff) != 2) { @@ -912,6 +931,52 @@ main(int argc, char **argv) } } + if (!pgdataDir) + pgdataDir = getenv("PGDATA"); + + if (pgdataDir && cluster_passphrase) + { + uint8 wrapped_key[KMGR_WRAPPED_KEY_LEN]; + uint32 cipherMethod = 0; + + /* get wrapped key and cipher method from control file */ + ret = kmgr_get_cipher_info(pgdataDir, &wrapped_key[0], &cipherMethod); + if (!ret) + { + pg_log_error("failed to get cipher info from control data file in cluster %s", pgdataDir); + goto bad_argument; + } + + if (!cipherMethod) + { + pg_log_error("encryption is not enabled in cluster %s", pgdataDir); + goto bad_argument; + } + + /* + * Based on the passphrase, derive cipher contexts for encryption and + * decryption which can be used to encrypt or decrypt WAL when TDE is + * enabled. + */ + ret = kmgr_derive_cipher_ctx_from_passphrase_cmd(cluster_passphrase, + &wrapped_key[0], &encrypt_cipher_ctx, &decrypt_cipher_ctx); + if (!ret) + { + pg_log_error("failed to derive cipher context for cluster encryption"); + goto bad_argument; + } + else + { + pg_log_info("cipher context for cluster encryption derived successfully"); + } + + /* free the cipher contexts when not in used. */ + if(encrypt_cipher_ctx) + pg_cipher_ctx_free(encrypt_cipher_ctx); + if(decrypt_cipher_ctx) + pg_cipher_ctx_free(decrypt_cipher_ctx); + } + if ((optind + 2) < argc) { pg_log_error("too many command-line arguments (first is \"%s\")", diff --git a/src/bin/pg_waldump/t/002_tde.pl b/src/bin/pg_waldump/t/002_tde.pl new file mode 100644 index 0000000000..aa93719e97 --- /dev/null +++ b/src/bin/pg_waldump/t/002_tde.pl @@ -0,0 +1,83 @@ +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 4; + +my $tempdir = TestLib::tempdir; +my $tde_params_ok = + "echo 1234567890123456789012345678901234567890123456789012345678901234"; +my $tde_params_ko = + "echo 7777777766666666555555554444444433333333222222221111111100000000"; + +my $node; +my $startlsn; +my $endlsn; +my $xlogdir; +my $datadir; + +sub init_and_start_tde_cluster +{ + $node = get_new_node('main'); + $node->init(enable_kms => 1); + + $xlogdir = $node->data_dir . "/pg_wal"; + $datadir = $node->data_dir; + + $node->start; + $node->psql('postgres', 'CREATE TABLE test_table(x integer)'); + $node->psql('postgres', + 'INSERT INTO test_table(x) SELECT y FROM generate_series(1, 10) a(y);' + ); + $startlsn = + $node->safe_psql('postgres', 'SELECT pg_current_wal_insert_lsn()'); + + $node->psql('postgres', + 'INSERT INTO test_table(x) SELECT y FROM generate_series(1, 5000) a(y);' + ); + + $endlsn = + $node->safe_psql('postgres', 'SELECT pg_current_wal_insert_lsn()'); +} + +init_and_start_tde_cluster(); + +command_ok( + [ + 'pg_waldump', '-c', $tde_params_ok, '-p', $xlogdir, '-D', + $datadir, '-s', $startlsn, '-e', $endlsn, '-n', + '100' + ], + 'pg_waldump with correct cluster passphrase'); + +command_fails( + [ + 'pg_waldump', '-c', $tde_params_ko, '-p', $xlogdir, '-D', + $datadir, '-s', $startlsn, '-e', $endlsn, '-n', + '100' + ], + 'pg_waldump with incorrect cluster passphrase'); + +$node->append_conf('postgresql.conf', + qq[cluster_passphrase_command = '$tde_params_ko']); +$node->reload; +$node->psql('postgres', 'select pg_rotate_encryption_key();'); + +command_ok( + [ + 'pg_waldump', '-c', $tde_params_ko, '-p', $xlogdir, '-D', + $datadir, '-s', $startlsn, '-e', $endlsn, '-n', + '100' + ], + 'pg_waldump with correct cluster passphrase after key rotation'); + +command_fails( + [ + 'pg_waldump', '-c', $tde_params_ok, '-p', $xlogdir, '-D', + $datadir, '-s', $startlsn, '-e', $endlsn, '-n', + '100' + ], + 'pg_waldump with incorrect cluster passphrase after key rotation'); + +$node->stop; +$node->teardown_node; diff --git a/src/common/kmgr_utils.c b/src/common/kmgr_utils.c index e1936ab529..069259714e 100644 --- a/src/common/kmgr_utils.c +++ b/src/common/kmgr_utils.c @@ -22,6 +22,7 @@ #endif #include "common/kmgr_utils.h" #include "common/sha2.h" +#include "common/controldata_utils.h" #include "crypto/kmgr.h" #include "utils/elog.h" #include "storage/fd.h" @@ -416,3 +417,145 @@ close_pipe_stream(FILE *file) #endif /* WIN32 */ } #endif /* FRONTEND */ + +/* + * Obtain cipher information from control data file. Return true and set + * *wrapped_key_out and *cipher_method_out accordingly. *wrapped_key_out must + * be pre-allocated to hold at least KMGR_WRAPPED_KEY_LEN bytes long. Return + * false if control data file fails to be accessed. + */ +bool +kmgr_get_cipher_info(char *pgdata_dir, + uint8 *wrapped_key_out, + uint32 *cipher_method_out) +{ + ControlFileData *control_file = NULL; + bool crc_ok = false; + + Assert(pgdata_dir && wrapped_key_out && cipher_method_out); + + control_file = get_controlfile(pgdata_dir, &crc_ok); + if (!control_file) + { +#ifdef FRONTEND + pg_log_error("failed to get control_file from cluster %s", pgdata_dir); +#else + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to get control_file from cluster %s", pgdata_dir))); +#endif + return false; + } + + if (!crc_ok) + { +#ifdef FRONTEND + pg_log_error("control file failed CRC verification"); +#else + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("control file failed CRC verification"))); +#endif + return false; + } + + *cipher_method_out = control_file->key_management_enabled; + memcpy(wrapped_key_out, control_file->masterkey, KMGR_WRAPPED_KEY_LEN); + return true; +} + +/* + * Evaluate the passphrase command and derive the raw master key if passphrase + * is correct. Return true if key derivation is successful and **encrypt_ctx_out + * and **decrypt_ctx_out will be allocated for subsequent encryption and decryption + * operations. Return false if passphrase is incorrect or key derivation is not + * successful. Caller is responsible for freeing **encrypt_ctx_out and **decrypt_ctx_out. + */ +bool +kmgr_derive_cipher_ctx_from_passphrase_cmd(char *cluster_passphrase_command, + uint8 *wrapped_key_in, + pg_cipher_ctx * *encrypt_ctx_out, + pg_cipher_ctx * *decrypt_ctx_out) +{ + int len; + bool ret = false; + char passphrase[KMGR_MAX_PASSPHRASE_LEN]; + uint8 rawkey[KMGR_KEY_AND_HMACKEY_LEN]; + + Assert(cluster_passphrase_command && wrapped_key_in); + + /* derive passphrase from cluster_passphrase_cmd */ + len = kmgr_run_cluster_passphrase_command(cluster_passphrase_command, + &passphrase[0], + KMGR_MAX_PASSPHRASE_LEN); + + /* verify passphrase and obtain rawkey */ + ret = kmgr_verify_passphrase(passphrase, len, + wrapped_key_in, + &rawkey[0]); + + if (!ret) + { +#ifdef FRONTEND + pg_log_error("encryption passphrase verification failed"); +#else + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("encryption passphrase verification failed"))); +#endif + return false; + } + + *encrypt_ctx_out = pg_cipher_ctx_create(); + if (!(*encrypt_ctx_out)) + { +#ifdef FRONTEND + pg_log_error("failed to allocate cipher context for encryption"); +#else + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to allocate cipher context for encryption"))); +#endif + return false; + } + + *decrypt_ctx_out = pg_cipher_ctx_create(); + if (!(*decrypt_ctx_out)) + { +#ifdef FRONTEND + pg_log_error("failed to allocate cipher context for decryption"); +#else + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to allocate cipher context for decryption"))); +#endif + return false; + } + + ret = pg_aes256_encrypt_init(*encrypt_ctx_out, &rawkey[0]); + if (!ret) + { +#ifdef FRONTEND + pg_log_error("failed to initialize encryption cipher context with master key"); +#else + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to initialize encryption cipher context with master key"))); +#endif + return false; + } + + ret = pg_aes256_decrypt_init(*decrypt_ctx_out, &rawkey[0]); + if (!ret) + { +#ifdef FRONTEND + pg_log_error("failed to initialize decryption cipher context with master key"); +#else + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("failed to initialize decryption cipher context with master key"))); +#endif + return false; + } + return true; +} diff --git a/src/include/common/kmgr_utils.h b/src/include/common/kmgr_utils.h index 0d075bc2bb..3a152782a6 100644 --- a/src/include/common/kmgr_utils.h +++ b/src/include/common/kmgr_utils.h @@ -80,5 +80,12 @@ extern bool kmgr_compute_HMAC(KeyWrapCtx *ctx, const uint8 *in, int inlen, uint8 *out); extern int kmgr_run_cluster_passphrase_command(char *passphrase_command, char *buf, int size); +extern bool kmgr_get_cipher_info(char *pgdata_dir, + uint8 *wrapped_key_out, + uint32 *cipher_method_out); +extern bool kmgr_derive_cipher_ctx_from_passphrase_cmd(char *cluster_passphrase_command, + uint8 *wrapped_key_in, + pg_cipher_ctx * *encrypt_ctx_out, + pg_cipher_ctx * *decrypt_ctx_out); #endif /* KMGR_UTILS_H */