From be4642372f5baf8ccbbfbee8a181ac7abf3b6b13 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Fri, 28 Jun 2019 17:45:25 +0900 Subject: [PATCH 3/9] Add key management module for transparent data encryption. This commit introduce new module that is responsible for encryption key management for transparent data encryption. This module manages only tablespace keys but not the master key. The tablespace keys are persisted to the disk after encrypted with the master key. --- src/backend/postmaster/postmaster.c | 17 + src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/kmgr/Makefile | 17 + src/backend/storage/kmgr/kmgr.c | 679 +++++++++++++++++++++++++++++++ src/backend/storage/lmgr/lwlocknames.txt | 2 + src/backend/utils/misc/guc.c | 24 ++ src/include/catalog/pg_proc.dat | 4 +- src/include/storage/kmgr.h | 61 +++ 8 files changed, 806 insertions(+), 1 deletion(-) create mode 100644 src/backend/storage/kmgr/Makefile create mode 100644 src/backend/storage/kmgr/kmgr.c create mode 100644 src/include/storage/kmgr.h diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 688ad43..c744fe0 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -119,6 +119,7 @@ #include "replication/walsender.h" #include "storage/fd.h" #include "storage/ipc.h" +#include "storage/kmgr.h" #include "storage/pg_shmem.h" #include "storage/pmsignal.h" #include "storage/proc.h" @@ -988,6 +989,11 @@ PostmasterMain(int argc, char *argv[]) process_shared_preload_libraries(); /* + * Load and invoke startup callback of keyring plugin. + */ + processKmgrPlugin(); + + /* * Now that loadable modules have had their chance to register background * workers, calculate MaxBackends. */ @@ -1313,6 +1319,12 @@ PostmasterMain(int argc, char *argv[]) autovac_init(); /* + * Get the master encrption key via kmgr plugin and store into the + * shared memory. + */ + InitializeKmgr(); + + /* * Load configuration files for client authentication. */ if (!load_hba()) @@ -4901,6 +4913,11 @@ SubPostmasterMain(int argc, char *argv[]) */ process_shared_preload_libraries(); + /* + * Load and invoke startup callback of keyring plugin. + */ + processKmgrPlugin(); + /* Run backend or appropriate child */ if (strcmp(argv[1], "--forkbackend") == 0) { diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index d7d7335..f8b3e76 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -36,6 +36,7 @@ #include "storage/bufmgr.h" #include "storage/dsm.h" #include "storage/ipc.h" +#include "storage/kmgr.h" #include "storage/pg_shmem.h" #include "storage/pmsignal.h" #include "storage/predicate.h" @@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(int port) size = add_size(size, BTreeShmemSize()); size = add_size(size, SyncScanShmemSize()); size = add_size(size, AsyncShmemSize()); + size = add_size(size, KmgrShmemSize()); #ifdef EXEC_BACKEND size = add_size(size, ShmemBackendArraySize()); #endif @@ -263,6 +265,7 @@ CreateSharedMemoryAndSemaphores(int port) BTreeShmemInit(); SyncScanShmemInit(); AsyncShmemInit(); + KmgrShmemInit(); #ifdef EXEC_BACKEND diff --git a/src/backend/storage/kmgr/Makefile b/src/backend/storage/kmgr/Makefile new file mode 100644 index 0000000..e695dcf --- /dev/null +++ b/src/backend/storage/kmgr/Makefile @@ -0,0 +1,17 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for storage/kmgr +# +# IDENTIFICATION +# src/backend/storage/kmgr/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/storage/kmgr +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = kmgr.o plugin.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/storage/kmgr/kmgr.c b/src/backend/storage/kmgr/kmgr.c new file mode 100644 index 0000000..e071f0c --- /dev/null +++ b/src/backend/storage/kmgr/kmgr.c @@ -0,0 +1,679 @@ +/*------------------------------------------------------------------------- + * + * kmgr.c + * Encryption key management module. + * + * This module manages both all tablespace keys and the master key identifer, + * for transparent data encryption. TDE uses two types of keys: master key and + * tablespace key. We have tablespace key for each tablespace whose encryption + * is enabled and encrypt relation data and WAL with the corresponding + * tablespace key. The bunch of tablespace keys are called 'keyring'. The + * keyring is loaded to shared memory from the keyring file located on + * global/pg_tblspc.kr at postmaster startup, and is modified each time when + * encrypted tablespace is either created or dropped. The master key is used to + * encrypt tablespace keys. On the shared memory we have non-encrypted + * tablespace keys in the keyring but when we persist them to the disk we must + * encrypt each of them with the master key. + * + * We also write the master key identifier that used to encrypt the persisted + * tablespace keys. The master key identifier is passed to kmgr plugin callbacks. + * The master key identifer consists of the system identifer and the master key + * sequence number, which is unsigned integer starting from + * FIRST_MASTER_KEY_SEQNO. The sequence number is incremented whenever key + * rotation. When key rotation, we generates a new master key and then update + * keyring file while reencrypting all tablespace keys with the new master key. + * We don't need to reencrypt relation data itself. + * + * LOCKING: + * After loaded the keyring to the shared memory, we have the hash table which + * maps between tablespace oid and encryption key, and have the master key + * identifer and sequence number. These fields are protected by + * KeyringControlLock. Also when updating the keyring file we have to be holding + * KeyringControlLock in exclusive mode to prevent the keyring file from being + * updated by concurrent processes. The process who want to rotate the master key + * needs to hold MasterKeyRotationLock in exclusive mode until end of all + * operation as well as KeyringControlLock during updating the keyring file. + * + * Copyright (c) 2019, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/storage/kmgr/kmgr.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include + +#include "funcapi.h" +#include "miscadmin.h" + +#include "access/xlog.h" +#include "storage/encryption.h" +#include "storage/fd.h" +#include "storage/kmgr.h" +#include "storage/kmgr_plugin.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" +#include "utils/builtins.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "utils/inval.h" +#include "utils/syscache.h" + +/* Filename of tablespace keyring */ +#define KEYRING_TBLSPC_FILE "pg_tblspc.kr" + +/* + * Since we have encryption keys per tablspace, we expect this value is enough + * for most usecase. + */ +#define KMGR_KEYRING_SIZE 128 + +/* Struct for one tablespace key, and hash entry */ +typedef struct TblspcKeyData +{ + Oid spcoid; /* hash key; must be first */ + char tblspckey[ENCRYPTION_KEY_SIZE]; /* non-encrypted key */ +} TblspcKeyData; + +/* + * Shared struct to save master key information written in the keyring file. + * KmgrCtl and TblspcKeyring are protected by KeyringControlFile lightweight + * lock. If we want to increment master key sequence number when key rotation, + * we need to acquire MasterKeyRotationLock in addition. + */ +typedef struct KmgrCtlData +{ + char masterKeyId[MASTER_KEY_ID_LEN]; + MasterKeySeqNo masterKeySeqNo; +} KmgrCtlData; +static KmgrCtlData *KmgrCtl; +static HTAB *TblspcKeyring; + +/* GUC variable */ +char *kmgr_plugin_library = NULL; + +PG_FUNCTION_INFO_V1(pg_rotate_encryption_key); +PG_FUNCTION_INFO_V1(pg_get_tablespace_keys); + +static bool load_keyring_file(void); +static char *read_keyring_file(List **keylist_p); +static void update_keyring_file(void); +static void update_keyring_file_extended(const char *masterkey_id, + const char *masterkey); +static void key_encryption_tweak(char *tweak, Oid spcoid); +static void encrypt_tblspc_key(Oid spcoid, char *tblspckey, const char *masterkey); +static void decrypt_tblspc_key(Oid spcoid, char *tblspckey, const char *masterkey); +static MasterKeySeqNo get_seqno_from_master_key_id(const char *masterkeyid); +static void create_master_key_id(char *id, MasterKeySeqNo seqno); + +Size +KmgrShmemSize(void) +{ + Size size; + + size = MAXALIGN(sizeof(KmgrCtlData)); + size = add_size(size, + hash_estimate_size(KMGR_KEYRING_SIZE, sizeof(TblspcKeyData))); + + return size; +} + +void +KmgrShmemInit(void) +{ + HASHCTL hash_ctl; + bool found; + + KmgrCtl = ShmemInitStruct("KmgrCtl shared", + KmgrShmemSize(), + &found); + + if (!found) + MemSet(KmgrCtl, 0, sizeof(KmgrCtlData)); + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(TblspcKeyData); + + TblspcKeyring = ShmemInitHash("kmgr keyring hash", + KMGR_KEYRING_SIZE, KMGR_KEYRING_SIZE, + &hash_ctl, + HASH_ELEM | HASH_BLOBS); +} + +/* + * Initialize Kmgr and kmgr plugin. We load the keyring file and set up both + * KmgrCtl and TblspcKeyring hash table on shared memory. When first time to + * access the keyring file, ie the keyring file does not exist, we create it + * with the initial master key id. If the keyring file exists, we load it to + * the shared structs. This function must be called by postmaster at startup + * time. + */ +void +InitializeKmgr(void) +{ + char *key = NULL; + + if (!TransparentEncryptionEnabled()) + return; + + /* + * Invoke kmgr plugin startup callback. Since we could get the master key + * during loading the keyring file we have to startup the plugin beforehand. + */ + ereport(DEBUG1, (errmsg("invoking kmgr plugin startup callback"))); + KmgrPluginStartup(); + + /* Load keyring file and update shmem structs */ + if (!load_keyring_file()) + { + /* + * If the keyring doesn't exist this is the first time to access the + * keyring file. We create it with the initial master key id. + */ + create_master_key_id(KmgrCtl->masterKeyId, FIRST_MASTER_KEY_SEQNO); + KmgrCtl->masterKeySeqNo = FIRST_MASTER_KEY_SEQNO; + + /* Create keyring file only with only master key id */ + LWLockAcquire(KeyringControlLock, LW_EXCLUSIVE); + update_keyring_file_extended(KmgrCtl->masterKeyId, NULL); + LWLockRelease(KeyringControlLock); + + ereport(DEBUG3, (errmsg("create initial keyring file with master key id"))); + } + + /* Create the master key if not exists */ + if (!KmgrPluginIsExist(KmgrCtl->masterKeyId)) + KmgrPluginGenerateKey(KmgrCtl->masterKeyId); + + /* Get the master key */ + key = KmgrPluginGetKey(KmgrCtl->masterKeyId); + + Assert(key != NULL); +} + +/* process and load kmgr plugin given by kmgr_plugin_library */ +void +processKmgrPlugin(void) +{ + process_shared_preload_libraries_in_progress = true; + startupKmgrPlugin(kmgr_plugin_library); + process_shared_preload_libraries_in_progress = false; +} + +/* Set the encryption key of the given tablespace to *key */ +void +KeyringGetKey(Oid spcOid, char *key) +{ + TblspcKeyData *tskey; + bool found; + + Assert(OidIsValid(spcOid)); + + LWLockAcquire(KeyringControlLock, LW_SHARED); + + tskey = hash_search(TblspcKeyring, (void *) &spcOid, + HASH_FIND, &found); + + LWLockRelease(KeyringControlLock); + + if (!found) + ereport(ERROR, (errmsg("could not find encryption key for tablespace %u", + spcOid))); + + /* Set encryption key */ + memcpy(key, tskey->tblspckey, ENCRYPTION_KEY_SIZE); +} + +/* + * Check the tablespace key is exists. Since encrypted tablespaces has its + * encryption key this function can be used to check if the tablespace is + * encrypted. + */ +bool +KeyringKeyExists(Oid spcOid) +{ + bool found; + + Assert(OidIsValid(spcOid)); + + LWLockAcquire(KeyringControlLock, LW_SHARED); + + (void *) hash_search(TblspcKeyring, (void *) &spcOid, HASH_FIND, &found); + + LWLockRelease(KeyringControlLock); + + return found; +} + +/* + * Generate new tablespace key and update the keyring file. Return encrypted + * key of new tablespace key. + */ +char * +KeyringCreateKey(Oid spcOid) +{ + TblspcKeyData *key; + char *masterkey; + char *retkey; + bool found; + bool ret; + + LWLockAcquire(KeyringControlLock, LW_EXCLUSIVE); + + key = hash_search(TblspcKeyring, (void *) &spcOid, + HASH_ENTER, &found); + + if (found) + elog(ERROR, "found duplicate tablespace encryption key for tablespace %u", + spcOid); + + /* Generate a random tablespace key */ + retkey = (char *) palloc0(ENCRYPTION_KEY_SIZE); + ret = pg_strong_random(retkey, ENCRYPTION_KEY_SIZE); + if (!ret) + ereport(ERROR, + (errmsg("failed to generate tablespace encryption key"))); + memcpy(key->tblspckey, retkey, ENCRYPTION_KEY_SIZE); + + /* Update tablespace key file */ + update_keyring_file(); + + LWLockRelease(KeyringControlLock); + + /* The returned key must be encrypted */ + masterkey = KmgrPluginGetKey(KmgrCtl->masterKeyId); + encrypt_tblspc_key(spcOid, retkey, masterkey); + + return retkey; +} + +/* + * Drop one tablespace key from the keyring hash table and update the keyring + * file. + */ +void +KeyringDropKey(Oid spcOid) +{ + bool found; + + LWLockAcquire(KeyringControlLock, LW_EXCLUSIVE); + + hash_search(TblspcKeyring, (void *) &spcOid, HASH_REMOVE, &found); + + if (!found) + elog(ERROR, "could not find tablespace encryption key for tablespace %u", + spcOid); + + LWLockRelease(KeyringControlLock); + + /* Update tablespace key file */ + update_keyring_file(); +} + +/* + * Add a tablespace key of given tablespace to the keyring hash table. + * *encrrypted_key is encrypted with the encryption key identified by + * masterkeyid. If the encryption key of the tablespace already exists, + * we check if these keys are the same. + */ +void +KeyringAddKey(Oid spcOid, char *encrypted_key, const char *masterkeyid) +{ + TblspcKeyData *key; + char buf[ENCRYPTION_KEY_SIZE]; + char *masterkey; + bool found; + + /* Copy to work buffer */ + memcpy(buf, encrypted_key, ENCRYPTION_KEY_SIZE); + + /* Get the master key */ + masterkey = KmgrPluginGetKey(masterkeyid); + + /* Decrypt tablespace key with the master key */ + decrypt_tblspc_key(spcOid, buf, masterkey); + + LWLockAcquire(KeyringControlLock, LW_EXCLUSIVE); + + key = hash_search(TblspcKeyring, (void *) &spcOid, HASH_ENTER, + &found); + + if (found) + { + LWLockRelease(KeyringControlLock); + + if (strncmp(key->tblspckey, buf, ENCRYPTION_KEY_SIZE) != 0) + elog(ERROR, "adding encryption key for tablespace %u does not match the exsiting one", + spcOid); + + /* The existing key is the same, return */ + return; + } + + /* Store the raw key to the hash */ + memcpy(key->tblspckey, buf, ENCRYPTION_KEY_SIZE); + + /* Update keyring file */ + update_keyring_file(); + + LWLockRelease(KeyringControlLock); +} + +/* + * Load the keyring file and update the shared variables. This function is + * intended to be used by postmaster at startup time , so lockings are not + * needed. + */ +static bool +load_keyring_file(void) +{ + List *keylist = NIL; + ListCell *lc; + char *masterkeyid; + char *masterkey; + + /* Read keyring file */ + masterkeyid = read_keyring_file(&keylist); + + /* There is no keyring file */ + if (masterkeyid == NULL) + { + /* We must not get any keys as well */ + Assert(keylist == NIL); + return false; + } + + /* We got keyring file. Update the master key id and sequence number */ + memcpy(KmgrCtl->masterKeyId, masterkeyid, MASTER_KEY_ID_LEN); + KmgrCtl->masterKeySeqNo = get_seqno_from_master_key_id(masterkeyid); + + /* Get the master key */ + masterkey = KmgrPluginGetKey(masterkeyid); + + /* Loading tablespace keys to shared keyring hash table */ + foreach (lc, keylist) + { + TblspcKeyData *key_infile = (TblspcKeyData *) lfirst(lc); + TblspcKeyData *key; + + key = hash_search(TblspcKeyring, (void *) &key_infile->spcoid, + HASH_ENTER, NULL); + + /* Decrypt tablespace key by the master key */ + decrypt_tblspc_key(key->spcoid, key_infile->tblspckey, masterkey); + + /* Set unencrypted tablespace key to the keyring hash table */ + memcpy(key->tblspckey, key_infile->tblspckey, ENCRYPTION_KEY_SIZE); + } + + list_free_deep(keylist); + + return true; +} + +/* + * Read the keyring file and return the list of tablespace keys. + */ +static char * +read_keyring_file(List **keylist_p) +{ + char *path = "global/"KEYRING_TBLSPC_FILE; + char *mkeyid; + int read_len; + int fd; + + fd = OpenTransientFile(path, O_RDONLY | PG_BINARY); + + if (fd < 0) + { + if (errno == ENOENT) + return NULL; + + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", path))); + } + + /* Read the master key id */ + mkeyid = palloc0(MASTER_KEY_ID_LEN); + if ((read_len = read(fd, mkeyid, MASTER_KEY_ID_LEN)) < 0) + ereport(ERROR, + (errcode_for_file_access(), + (errmsg("could not read from file \"%s\": %m", path)))); + + /* Tablespace keys follows */ + for (;;) + { + TblspcKeyData *key = palloc(sizeof(TblspcKeyData)); + + read_len = read(fd, key, sizeof(TblspcKeyData)); + + 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(TblspcKeyData)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from file \"%s\": read %d instead of %d bytes", + path, read_len, (int32) sizeof(TblspcKeyData)))); + + *keylist_p = lappend(*keylist_p, key); + } + + CloseTransientFile(fd); + + return mkeyid; +} + +/* + * Update the keyring file with the current master key */ +static void +update_keyring_file(void) +{ + update_keyring_file_extended(KmgrCtl->masterKeyId, + KmgrPluginGetKey(KmgrCtl->masterKeyId)); +} + +/* + * Update the keyring file with the specified key and id. *masterkey_id will + * be written to the beginning of keyring file and tablespace keys encrypted + * with *masterkey follows. The caller must hold KeyringControlLock in + * exclusive mode to prevent keyring file from concurrent update. + */ +static void +update_keyring_file_extended(const char *masterkey_id, const char *masterkey) +{ + HASH_SEQ_STATUS status; + TblspcKeyData *key; + char path[MAXPGPATH]; + char tmppath[MAXPGPATH]; + FILE *fpout; + int rc; + + Assert(LWLockHeldByMeInMode(KeyringControlLock, LW_EXCLUSIVE)); + + sprintf(path, "global/"KEYRING_TBLSPC_FILE); + sprintf(tmppath, "global/"KEYRING_TBLSPC_FILE".tmp"); + + fpout = AllocateFile(tmppath, PG_BINARY_W); + if (fpout == NULL) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open temporary keyring file \"%s\": %m", + tmppath))); + return; + } + + /* Write the master key id first */ + rc = fwrite(masterkey_id, MASTER_KEY_ID_LEN, 1, fpout); + + /* If we have any tablespace keys, write them to the file. */ + if (hash_get_num_entries(TblspcKeyring) > 0) + { + /* Write tablespace key to the file */ + hash_seq_init(&status, TblspcKeyring); + while ((key = (TblspcKeyData *) hash_seq_search(&status)) != NULL) + { + TblspcKeyData k; + + /* Copy to work buffer */ + memcpy(&k, key, sizeof(TblspcKeyData)); + + /* Prepare tablespace key and master key to write */ + encrypt_tblspc_key(key->spcoid, (char *) &k.tblspckey, masterkey); + + rc = fwrite(&k, sizeof(TblspcKeyData), 1, fpout); + (void) rc; /* will check for error with ferror */ + } + } + + if (ferror(fpout)) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write temporary keyring file \"%s\": %m", + tmppath))); + FreeFile(fpout); + unlink(tmppath); + } + else if (FreeFile(fpout) < 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not close temporary keyring file \"%s\": %m", + tmppath))); + unlink(tmppath); + } + else if (durable_rename(tmppath, path, ERROR) < 0) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not rename temporary keyring file \"%s\" to \"%s\": %m", + tmppath, path))); + unlink(tmppath); + } +} + +/* + * Encrypt and decrypt routine for tablespace key + */ +static void +encrypt_tblspc_key(Oid spcoid, char *tblspckey, const char *masterkey) +{ + char tweak[ENCRYPTION_TWEAK_SIZE]; + + /* And encrypt tablespace key before writing */ + key_encryption_tweak(tweak, spcoid); + encrypt_block(tblspckey, tblspckey, ENCRYPTION_KEY_SIZE, + masterkey, tweak, false); + +} + +static void +decrypt_tblspc_key(Oid spcoid, char *tblspckey, const char *masterkey) +{ + char tweak[ENCRYPTION_TWEAK_SIZE]; + + /* And encrypt tablespace key before writing */ + key_encryption_tweak(tweak, spcoid); + decrypt_block(tblspckey, tblspckey, ENCRYPTION_KEY_SIZE, + masterkey, tweak, false); + +} + +static void +key_encryption_tweak(char *tweak, Oid spcoid) +{ + memset(tweak, 0, ENCRYPTION_TWEAK_SIZE); + memcpy(tweak, &spcoid, sizeof(Oid)); +} + +/* + * Craft the master key identifier using the given sequence number and system + * identifier. + */ +static void +create_master_key_id(char *id, MasterKeySeqNo seqno) +{ + char sysid[32]; + + Assert(id != NULL); + snprintf(sysid, sizeof(sysid), UINT64_FORMAT, GetSystemIdentifier()); + snprintf(id, MASTER_KEY_ID_LEN, MASTER_KEY_ID_FORMAT, + sysid, seqno); +} + +/* Extract the sequence number from *masterkeyid */ +static MasterKeySeqNo +get_seqno_from_master_key_id(const char *masterkeyid) +{ + MasterKeySeqNo seqno; + uint32 dummy; + + /* Get the sequence number */ + sscanf(masterkeyid, MASTER_KEY_ID_FORMAT_SCAN, &dummy, &seqno); + Assert(seqno >= 0); + + return seqno; +} + +/* + * Rotate the master key. This function generate new master key id and + * require the kmgr plugin to generate the corresponding key. And then, using + * the new master key we update keyring file while encrypt all tablespace keys. + */ +Datum +pg_rotate_encryption_key(PG_FUNCTION_ARGS) +{ + char newid[MASTER_KEY_ID_LEN] = {0}; + char retid[MASTER_KEY_ID_LEN + 1]; + char *newkey; + MasterKeySeqNo current_seqno; + + /* Protect sequence number increment from concurrent processes */ + LWLockAcquire(MasterKeyRotationLock, LW_EXCLUSIVE); + + /* Get the current sequence number */ + LWLockAcquire(KeyringControlLock, LW_SHARED); + current_seqno = KmgrCtl->masterKeySeqNo; + LWLockRelease(KeyringControlLock); + + /* Craft the new master key id */ + create_master_key_id(newid, current_seqno + 1); + + /* Generate the new master key by the new id */ + KmgrPluginGenerateKey(newid); + newkey = KmgrPluginGetKey(newid); + Assert(newkey); + + /* + * Update share memory information with the next id and sequence number. + */ + LWLockAcquire(KeyringControlLock, LW_EXCLUSIVE); + + KmgrCtl->masterKeySeqNo = current_seqno + 1; + memcpy(KmgrCtl->masterKeyId, newid, MASTER_KEY_ID_LEN); + + /* + * Reencrypt all tablespace keys with the new master key, and update + * the keyring file. + */ + update_keyring_file_extended(newid, newkey); + + LWLockRelease(KeyringControlLock); + LWLockRelease(MasterKeyRotationLock); + + /* Craft key id for the return value */ + memcpy(retid, newid, MASTER_KEY_ID_LEN); + retid[MASTER_KEY_ID_LEN] = '\0'; + + PG_RETURN_TEXT_P(cstring_to_text(retid)); +} diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt index db47843..2a19642 100644 --- a/src/backend/storage/lmgr/lwlocknames.txt +++ b/src/backend/storage/lmgr/lwlocknames.txt @@ -49,3 +49,5 @@ MultiXactTruncationLock 41 OldSnapshotTimeMapLock 42 LogicalRepWorkerLock 43 CLogTruncationLock 44 +KeyringControlLock 45 +MasterKeyRotationLock 46 diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 1208eb9..bb4788b 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -75,6 +75,7 @@ #include "storage/standby.h" #include "storage/fd.h" #include "storage/large_object.h" +#include "storage/kmgr.h" #include "storage/pg_shmem.h" #include "storage/proc.h" #include "storage/predicate.h" @@ -215,6 +216,7 @@ static bool check_recovery_target_lsn(char **newval, void **extra, GucSource sou static void assign_recovery_target_lsn(const char *newval, void *extra); static bool check_primary_slot_name(char **newval, void **extra, GucSource source); static bool check_default_with_oids(bool *newval, void **extra, GucSource source); +static bool check_kmgr_plugin_library(char **newval, void **extra, GucSource source); /* Private functions in guc-file.l that need to be called from guc.c */ static ConfigVariable *ProcessConfigFileInternal(GucContext context, @@ -4213,6 +4215,17 @@ static struct config_string ConfigureNamesString[] = NULL, NULL, NULL }, + { + {"kmgr_plugin_library", PGC_POSTMASTER, CLIENT_CONN_PRELOAD, + gettext_noop("Keyring library to use."), + NULL, + GUC_SUPERUSER_ONLY, + }, + &kmgr_plugin_library, + "", + check_kmgr_plugin_library, NULL, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL @@ -11710,6 +11723,17 @@ check_recovery_target_lsn(char **newval, void **extra, GucSource source) return true; } +static bool +check_kmgr_plugin_library(char **newval, void **extra, GucSource source) +{ +#ifndef USE_OPENSSL + GUC_check_errdetail("database encryption requies openssl library"); + return false; +#endif + return true; +} + + static void assign_recovery_target_lsn(const char *newval, void *extra) { diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 8733524..0de5eb1 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10677,4 +10677,6 @@ proname => 'pg_partition_root', prorettype => 'regclass', proargtypes => 'regclass', prosrc => 'pg_partition_root' }, -] +{ oid => '4218', descr => 'rotate encryption master key', + proname => 'pg_rotate_encryption_key', prorettype => 'text', + proargtypes => '', prosrc => 'pg_rotate_encryption_key' }, diff --git a/src/include/storage/kmgr.h b/src/include/storage/kmgr.h new file mode 100644 index 0000000..c2a805c --- /dev/null +++ b/src/include/storage/kmgr.h @@ -0,0 +1,61 @@ +/*------------------------------------------------------------------------- + * + * kmgr.h + * Key management + * + * Portions Copyright (c) 2019, PostgreSQL Global Development Group + * + * src/include/storage/kmgr.h + * + *------------------------------------------------------------------------- + */ +#ifndef KMGR_H +#define KMGR_H + +#include "postgres.h" + +#include "storage/encryption.h" +#include "storage/bufpage.h" + +#define TransparentEncryptionEnabled() \ + (kmgr_plugin_library != NULL && kmgr_plugin_library[0] != '\0') + +/* + * The master key format is "pg_master_key--". + * The maximum length of database system identifer is + * 20 (=18446744073709551615) as it is an uint64 value and the maximum + * string length of seqno is 10 (=4294967295). + */ +#define MASTER_KEY_ID_FORMAT "pg_master_key-%.7s-%04u" +#define MASTER_KEY_ID_FORMAT_SCAN "pg_master_key-%u-%u" +#define MASTER_KEY_ID_LEN (16 + 7 + 4 + 1) + +/* The initial master key sequence number */ +#define FIRST_MASTER_KEY_SEQNO 0 + +/* + * masterKeySeqno is the sequence number starting from FIRST_MASTER_KEY_SEQNO + * and get incremented every time key rotation. + */ +typedef uint32 MasterKeySeqNo; + +/* GUC variable */ +extern char *kmgr_plugin_library; + +/* kmgr.c */ +extern Size KmgrShmemSize(void); +extern void KmgrShmemInit(void); +extern void processKmgrPlugin(void); +extern void InitializeKmgr(void); +extern void KeyringSetup(void); +extern char *KeyringCreateKey(Oid tablespaceoid); +extern void KeyringGetKey(Oid spcOid, char *key); +extern void KeyringDropKey(Oid tablespaceoid); +extern bool KeyringKeyExists(Oid spcOid); +extern void KeyringAddKey(Oid spcOid, char *encrypted_key, + const char *masterkeyid); + +/* tempkey.c */ +extern char * GetBackendKey(void); + +#endif /* KMGR_H */ -- 1.8.3.1