kms_v4_fe.patch
application/octet-stream
Filename: kms_v4_fe.patch
Type: application/octet-stream
Part: 0
Message:
Re: Internal key management system
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v4
| File | + | − |
|---|---|---|
| src/bin/pg_waldump/pg_waldump.c | 66 | 1 |
| src/bin/pg_waldump/t/002_tde.pl | 83 | 0 |
| src/common/kmgr_utils.c | 143 | 0 |
| src/include/common/kmgr_utils.h | 7 | 0 |
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 */