0009-Add-kmgr-plugin-test-module-kmgr_file.patch

text/x-patch

Filename: 0009-Add-kmgr-plugin-test-module-kmgr_file.patch
Type: text/x-patch
Part: 8
Message: Re: [Proposal] Table-level Transparent Data Encryption (TDE) and Key Management Service (KMS)

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: format-patch
Series: patch 0009
Subject: Add kmgr plugin test module: kmgr_file.
File+
contrib/kmgr_file/kmgr_file.c 311 0
contrib/kmgr_file/Makefile 20 0
contrib/kmgr_file/t/001_basic.pl 123 0
contrib/Makefile 2 1
From 4197b11bb91d9442777637dd2dfbadbb1deb9db1 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Fri, 28 Jun 2019 18:12:18 +0900
Subject: [PATCH 9/9] Add kmgr plugin test module: kmgr_file.

---
 contrib/Makefile                 |   3 +-
 contrib/kmgr_file/Makefile       |  20 +++
 contrib/kmgr_file/kmgr_file.c    | 311 +++++++++++++++++++++++++++++++++++++++
 contrib/kmgr_file/t/001_basic.pl | 123 ++++++++++++++++
 4 files changed, 456 insertions(+), 1 deletion(-)
 create mode 100644 contrib/kmgr_file/Makefile
 create mode 100644 contrib/kmgr_file/kmgr_file.c
 create mode 100644 contrib/kmgr_file/t/001_basic.pl

diff --git a/contrib/Makefile b/contrib/Makefile
index 92184ed..1e70603 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -48,7 +48,8 @@ SUBDIRS = \
 		tsm_system_rows \
 		tsm_system_time \
 		unaccent	\
-		vacuumlo
+		vacuumlo	\
+		kmgr_file
 
 ifeq ($(with_openssl),yes)
 SUBDIRS += sslinfo
diff --git a/contrib/kmgr_file/Makefile b/contrib/kmgr_file/Makefile
new file mode 100644
index 0000000..7430445
--- /dev/null
+++ b/contrib/kmgr_file/Makefile
@@ -0,0 +1,20 @@
+# contrib/kmgr_file/Makefile
+
+MODULES = kmgr_file
+PGFILEDESC = "kmgr_file - example of a kmgr plugin"
+
+OBJS = kmgr_file.o
+
+PREGRESS = kmgr_test
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/kmgr_file
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/kmgr_file/kmgr_file.c b/contrib/kmgr_file/kmgr_file.c
new file mode 100644
index 0000000..8465556
--- /dev/null
+++ b/contrib/kmgr_file/kmgr_file.c
@@ -0,0 +1,311 @@
+/*-------------------------------------------------------------------------
+ *
+ * keyring_file.c
+ *	  Test module for Key ring plugin
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  contrib/test_keyringfile/keyring_file.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "fmgr.h"
+#include "utils/hsearch.h"
+#include "storage/fd.h"
+#include "storage/kmgr.h"
+#include "storage/kmgr_api.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+#include "utils/hashutils.h"
+#include "utils/guc.h"
+#include "miscadmin.h"
+
+#define KEYRING_MAX_KEYS 128
+#define TEST_MASTERKEY_FILENAME "kmgr_test"
+
+
+PG_MODULE_MAGIC;
+
+typedef char KeyId[MASTER_KEY_ID_LEN];
+
+typedef struct MyKey
+{
+	char	id[MASTER_KEY_ID_LEN];
+	char	key[ENCRYPTION_KEY_SIZE];
+} MyKey;
+
+static HTAB *MyKeys;
+static LWLock *lock;
+static char *masterkey_filepath;
+
+/* function prototypes */
+extern void _PG_kmgr_init(KmgrPluginCallbacks *cb);
+static void test_startup(void);
+static char *test_getkey(const char *keyid);
+static void test_generatekey(const char *keyid);
+static void test_removekey(const char *keyid);
+static bool test_isexistkey(const char *keyid);
+static void load_all_keys(void);
+static void save_all_keys(void);
+static Size test_memsize(void);
+
+/*
+ * Specify output plugin callbacks
+ */
+void
+_PG_kmgr_init(KmgrPluginCallbacks *cb)
+{
+	AssertVariableIsOfType(&_PG_kmgr_init, KmgrPluginInit);
+
+	cb->startup_cb = test_startup;
+	cb->getkey_cb = test_getkey;
+	cb->generatekey_cb = test_generatekey;
+	cb->isexistkey_cb = test_isexistkey;
+	cb->removekey_cb = test_removekey;
+
+	DefineCustomStringVariable("kmgr_file.masterkey_filepath",
+							   "file location of key file",
+							   NULL,
+							   &masterkey_filepath,
+							   "global/",
+							   PGC_POSTMASTER,
+							   0,
+							   NULL, NULL, NULL);
+
+	RequestAddinShmemSpace(test_memsize());
+	RequestNamedLWLockTranche("kmgr_test", 1);
+}
+
+static Size
+test_memsize(void)
+{
+	Size size;
+
+	size = MAXALIGN(sizeof(lock));
+	size = add_size(size, hash_estimate_size(KEYRING_MAX_KEYS, sizeof(MyKey)));
+
+	return size;
+}
+
+/*
+ * Read all keys in my keyfile(kmgr_test.kr) and build hash map for keys.
+ */
+static void
+load_all_keys(void)
+{
+	char path[MAXPGPATH];
+	int fd;
+
+	LWLockAcquire(lock, LW_EXCLUSIVE);
+
+	sprintf(path, "%s/%s", masterkey_filepath, TEST_MASTERKEY_FILENAME);
+
+	fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
+
+	if (fd < 0)
+	{
+		if (errno == ENOENT)
+		{
+			LWLockRelease(lock);
+			return;
+		}
+
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not open file \"%s\": %m", path)));
+	}
+
+	for (;;)
+	{
+		MyKey *key = palloc(sizeof(MyKey));
+		MyKey *keycache;
+		int read_len;
+
+		read_len = read(fd, key, sizeof(MyKey));
+
+		if (read_len < 0)
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 (errmsg("could not read from file \"%s\": %m", path))));
+		else if (read_len == 0) /* EOF */
+			break;
+		else if (read_len != sizeof(MyKey))
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not read from file \"%s\": read %d instead of %d bytes",
+							path, read_len, (int32) sizeof(MyKey))));
+
+		keycache = hash_search(MyKeys, (void *) key->id, HASH_ENTER, NULL);
+
+		memcpy(keycache->key, key->key, ENCRYPTION_KEY_SIZE);
+	}
+
+	CloseTransientFile(fd);
+	LWLockRelease(lock);
+}
+
+/*
+ * Persistent all keys in my key file(kmgr_test).
+ */
+static void
+save_all_keys(void)
+{
+	HASH_SEQ_STATUS status;
+	char path[MAXPGPATH];
+	MyKey *key;
+	FILE *fpout;
+	int	rc;
+
+	sprintf(path, "%s/%s", masterkey_filepath, TEST_MASTERKEY_FILENAME);
+	fpout = AllocateFile(path, PG_BINARY_W);
+	if (fpout == NULL)
+	{
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not open temporary keyring file \"%s\": %m",
+						path)));
+		return;
+	}
+
+	/* Iterate all keys while writing them to the file */
+	hash_seq_init(&status, MyKeys);
+	while ((key = (MyKey *) hash_seq_search(&status)) != NULL)
+	{
+		rc = fwrite(key, sizeof(MyKey), 1, fpout);
+		(void) rc;
+	}
+
+	if (ferror(fpout))
+	{
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not write temporary keyring file \"%s\": %m",
+						path)));
+	}
+	else if (FreeFile(fpout) < 0)
+	{
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not close temporary keyring file \"%s\": %m",
+						path)));
+	}
+}
+
+static void
+test_startup(void)
+{
+	HASHCTL ctl;
+
+	ereport(LOG, (errmsg("keyring_file: starting up")));
+
+	//LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+
+	MemSet(&ctl, 0, sizeof(ctl));
+	ctl.keysize = MASTER_KEY_ID_LEN;
+	ctl.entrysize = sizeof(MyKey);
+
+	MyKeys = ShmemInitHash("test_kmgr key shared hash map",
+						   KEYRING_MAX_KEYS, KEYRING_MAX_KEYS,
+						   &ctl,
+						   HASH_ELEM | HASH_BLOBS);
+
+	lock = &(GetNamedLWLockTranche("kmgr_test"))->lock;
+
+	//LWLockRelease(AddinShmemInitLock);
+
+	load_all_keys();
+}
+
+static char *
+test_getkey(const char *keyid)
+{
+	char *key;
+	MyKey *mykey;
+	bool found;
+
+	LWLockAcquire(lock, LW_SHARED);
+
+	mykey = hash_search(MyKeys, (void *) keyid, HASH_FIND,	&found);
+
+	if (!found)
+		ereport(ERROR,
+				(errmsg("kmgr_file: could not get master key \"%s\"",
+						keyid)));
+
+	/* Set master key */
+	key = palloc0(ENCRYPTION_KEY_SIZE);
+	memcpy(key, mykey->key, ENCRYPTION_KEY_SIZE);
+
+	LWLockRelease(lock);
+
+	return key;
+}
+
+static bool
+test_isexistkey(const char *keyid)
+{
+	bool found;
+
+	Assert(keyid != NULL);
+
+	LWLockAcquire(lock, LW_SHARED);
+
+	(void *) hash_search(MyKeys, (void *) keyid, HASH_FIND,	&found);
+
+	LWLockRelease(lock);
+
+	return found;
+}
+
+static void
+test_generatekey(const char *keyid)
+{
+	MyKey *mykey;
+	bool found;
+	char *newkey;
+	int ret;
+
+	Assert(keyid);
+
+	LWLockAcquire(lock, LW_EXCLUSIVE);
+
+	/* Set master key */
+	newkey = (char *) palloc0(ENCRYPTION_KEY_SIZE);
+	ret = pg_strong_random(newkey, ENCRYPTION_KEY_SIZE);
+	if (!ret)
+		ereport(ERROR,
+				(errmsg("failed to generate tablespace encryption key")));
+
+	/* Duplication check */
+	mykey = hash_search(MyKeys, (void *) keyid, HASH_FIND,	&found);
+
+	if (found)
+		ereport(ERROR,
+				(errmsg("key \"%s\" is already registered", keyid)));
+
+	/* Ok, register the key */
+	mykey = hash_search(MyKeys, (void *) keyid,	HASH_ENTER,	&found);
+	Assert(!found);
+
+	memcpy(mykey->key, newkey, ENCRYPTION_KEY_SIZE);
+
+	/* update key file */
+	save_all_keys();
+
+	LWLockRelease(lock);
+}
+
+static void
+test_removekey(const char *keyid)
+{
+	/* Not implemented yet */
+	return;
+}
diff --git a/contrib/kmgr_file/t/001_basic.pl b/contrib/kmgr_file/t/001_basic.pl
new file mode 100644
index 0000000..677b7cb
--- /dev/null
+++ b/contrib/kmgr_file/t/001_basic.pl
@@ -0,0 +1,123 @@
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 9;
+
+my $keyword = "secret keyword";
+my $node = get_new_node('test');
+$node->init();
+$node->append_conf('postgresql.conf',
+				   qq(kmgr_plugin_library = 'kmgr_file'));
+$node->start;
+
+# Check is the given relation file is encrypted
+sub is_encrypted
+{
+	my $node = shift;
+	my $filepath = shift;
+	my $expected = shift;
+	my $testname = shift;
+	my $pgdata = $node->data_dir;
+
+	open my $file, '<' , "$pgdata/$filepath";
+	sysread $file, my $buffer, 8192;
+
+	my $ret = $buffer !~ /$keyword/ ? 1 : 0;
+
+	is($ret, $expected, $testname);
+
+	close $file;
+}
+
+# Prepare both encrypted and non-encrypted tablespaces
+my $enctblspc = $node->data_dir() . '_enctblspc';
+my $plntblspc = $node->data_dir() . '_plntblspc';
+mkdir $enctblspc or die;
+mkdir $plntblspc or die;
+$node->safe_psql('postgres',
+				 qq(
+				 CREATE TABLESPACE enctblspc LOCATION '$enctblspc' WITH (encryption = on);
+				 CREATE TABLESPACE plntblspc LOCATION '$plntblspc' WITH (encryption = off);
+				 ));
+
+# populate tables
+$node->safe_psql('postgres',
+				 qq(
+				 CREATE TABLE enc (a text) TABLESPACE enctblspc;
+				 CREATE TABLE pln (a text) TABLESPACE plntblspc;
+				 ));
+$node->safe_psql('postgres',
+				 qq(
+				 INSERT INTO enc VALUES ('$keyword');
+				 INSERT INTO pln SELECT ('$keyword');
+				 ));
+
+my $enc_filepath = $node->safe_psql('postgres', "SELECT pg_relation_filepath('enc')");
+my $pln_filepath = $node->safe_psql('postgres', "SELECT pg_relation_filepath('pln')");
+
+# Read encrypted table
+my $ret = $node->safe_psql('postgres', 'SELECT a FROM enc');
+is($ret, "$keyword", 'Read encrypted table');
+
+# Sync to disk
+$node->safe_psql('postgres', 'CHECKPOINT');
+
+# Encrypted table must be encrypted
+is_encrypted($node, $enc_filepath, 1, 'encrypted table is encrypted');
+
+# Plain table must not be encrypted
+is_encrypted($node, $pln_filepath, 0, 'non-encrypted table is not encrypted');
+
+# Read encrypted table from disk while decryption
+$node->restart;
+$ret = $node->safe_psql('postgres', 'SELECT a FROM enc');
+is($ret, "$keyword", 'Read encrypted table');
+
+# Rotate enryption keys, and read encrypted table
+$node->psql('postgres',
+			'SELECT pg_rotate_encryption_key()');
+$ret = $node->safe_psql('postgres', 'SELECT a FROM enc');
+is($ret, "$keyword", 'Read encrypted table');
+
+
+# Create an encrypted database, and create table
+$node->psql('postgres',
+			'CREATE DATABASE encdb TABLESPACE enctblspc');
+
+# System catalog on the encrypted database must be encrypted.
+my $pgclass_filepath = $node->safe_psql('encdb',
+									   "SELECT pg_relation_filepath('pg_class')");
+is_encrypted($node, $pgclass_filepath, 1, 'system catalog is encrypted');
+
+# Change encryption properly by moving tablespace
+$node->psql('postgres',
+			qq(
+			ALTER TABLE enc SET TABLESPACE plntblspc;
+			ALTER TABLE pln SET TABLESPACE enctblspc;
+			));
+
+# Now, the result of is_encryption is reverse
+$enc_filepath = $node->safe_psql('postgres', "SELECT pg_relation_filepath('enc')");
+$pln_filepath = $node->safe_psql('postgres', "SELECT pg_relation_filepath('pln')");
+$node->psql('postgres', 'CHECKPOINT');
+is_encrypted($node, $enc_filepath, 0, 'encrypted table gets plain');
+is_encrypted($node, $pln_filepath, 1, 'plain tble gets encrypted');
+
+# Create another encrypted tablespace and table
+my $enctblspc2 = $node->data_dir() . '_enctblspc2';
+mkdir $enctblspc2 or die;
+$node->safe_psql('postgres',
+				 qq(CREATE TABLESPACE enctblspc2 LOCATION '$enctblspc2' WITH (encryption = on);));
+$node->safe_psql('postgres',
+				 qq(
+				 CREATE TABLE enc2 (a text) TABLESPACE enctblspc2;
+				 INSERT INTO enc2 VALUES ('$keyword')));
+
+# We create the encrypted tablespace and table during recovery
+$node->teardown_node;
+$node->start;
+
+# Check the table is recovered successfully
+$ret = $node->safe_psql('postgres', 'SELECT a FROM enc2');
+is($ret, "$keyword", 'Read encrypted table');
-- 
1.8.3.1