1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

Add key management system

This adds a key management system that stores (currently) two data
encryption keys of length 128, 192, or 256 bits.  The data keys are
AES256 encrypted using a key encryption key, and validated via GCM
cipher mode.  A command to obtain the key encryption key must be
specified at initdb time, and will be run at every database server
start.  New parameters allow a file descriptor open to the terminal to
be passed.  pg_upgrade support has also been added.

Discussion: https://postgr.es/m/CA+fd4k7q5o6Nc_AaX6BcYM9yqTbC6_pnH-6nSD=54Zp6NBQTCQ@mail.gmail.com
Discussion: https://postgr.es/m/20201202213814.GG20285@momjian.us

Author: Masahiko Sawada, me, Stephen Frost
This commit is contained in:
Bruce Momjian
2020-12-25 10:19:44 -05:00
parent 5c31afc49d
commit 978f869b99
49 changed files with 2091 additions and 35 deletions

View File

@ -21,7 +21,7 @@ SUBDIRS = access bootstrap catalog parser commands executor foreign lib libpq \
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
jit crypto
include $(srcdir)/common.mk

View File

@ -44,11 +44,13 @@
#include "commands/tablespace.h"
#include "common/controldata_utils.h"
#include "executor/instrument.h"
#include "crypto/kmgr.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "port/atomics.h"
#include "postmaster/bgwriter.h"
#include "postmaster/postmaster.h"
#include "postmaster/startup.h"
#include "postmaster/walwriter.h"
#include "replication/basebackup.h"
@ -81,6 +83,7 @@
#include "utils/timestamp.h"
extern uint32 bootstrap_data_checksum_version;
extern int bootstrap_file_encryption_keylen;
/* Unsupported old recovery command file names (relative to $PGDATA) */
#define RECOVERY_COMMAND_FILE "recovery.conf"
@ -4618,6 +4621,7 @@ InitControlFile(uint64 sysidentifier)
ControlFile->wal_log_hints = wal_log_hints;
ControlFile->track_commit_timestamp = track_commit_timestamp;
ControlFile->data_checksum_version = bootstrap_data_checksum_version;
ControlFile->file_encryption_keylen = bootstrap_file_encryption_keylen;
}
static void
@ -4717,6 +4721,7 @@ ReadControlFile(void)
pg_crc32c crc;
int fd;
static char wal_segsz_str[20];
static char file_encryption_keylen_str[20];
int r;
/*
@ -4905,6 +4910,12 @@ ReadControlFile(void)
/* Make the initdb settings visible as GUC variables, too */
SetConfigOption("data_checksums", DataChecksumsEnabled() ? "yes" : "no",
PGC_INTERNAL, PGC_S_OVERRIDE);
Assert(ControlFile != NULL);
snprintf(file_encryption_keylen_str, sizeof(file_encryption_keylen_str), "%d",
ControlFile->file_encryption_keylen);
SetConfigOption("file_encryption_keylen", file_encryption_keylen_str, PGC_INTERNAL,
PGC_S_OVERRIDE);
}
/*
@ -5354,6 +5365,16 @@ BootStrapXLOG(void)
/* some additional ControlFile fields are set in WriteControlFile() */
WriteControlFile();
/* Enable file encryption if required */
if (ControlFile->file_encryption_keylen > 0)
BootStrapKmgr();
if (terminal_fd != -1)
{
close(terminal_fd);
terminal_fd = -1;
}
/* Bootstrap the commit log, too */
BootStrapCLOG();
BootStrapCommitTs();

View File

@ -28,12 +28,14 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "common/link-canary.h"
#include "crypto/kmgr.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pg_getopt.h"
#include "pgstat.h"
#include "postmaster/bgwriter.h"
#include "postmaster/postmaster.h"
#include "postmaster/startup.h"
#include "postmaster/walwriter.h"
#include "replication/walreceiver.h"
@ -51,6 +53,8 @@
#include "utils/relmapper.h"
uint32 bootstrap_data_checksum_version = 0; /* No checksum */
int bootstrap_file_encryption_keylen = 0; /* disabled */
char *bootstrap_old_key_datadir = NULL; /* disabled */
static void CheckerModeMain(void);
@ -224,7 +228,7 @@ AuxiliaryProcessMain(int argc, char *argv[])
/* If no -x argument, we are a CheckerProcess */
MyAuxProcType = CheckerProcess;
while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:x:X:-:")) != -1)
while ((flag = getopt(argc, argv, "B:c:d:D:FkK:r:R:u:x:X:-:")) != -1)
{
switch (flag)
{
@ -253,9 +257,18 @@ AuxiliaryProcessMain(int argc, char *argv[])
case 'k':
bootstrap_data_checksum_version = PG_DATA_CHECKSUM_VERSION;
break;
case 'K':
bootstrap_file_encryption_keylen = atoi(optarg);
break;
case 'u':
bootstrap_old_key_datadir = pstrdup(optarg);
break;
case 'r':
strlcpy(OutputFileName, optarg, MAXPGPATH);
break;
case 'R':
terminal_fd = atoi(optarg);
break;
case 'x':
MyAuxProcType = atoi(optarg);
break;
@ -312,6 +325,12 @@ AuxiliaryProcessMain(int argc, char *argv[])
proc_exit(1);
}
if (bootstrap_file_encryption_keylen != 0 &&
bootstrap_file_encryption_keylen != 128 &&
bootstrap_file_encryption_keylen != 192 &&
bootstrap_file_encryption_keylen != 256)
elog(PANIC, "unrecognized file encryption length: %d", bootstrap_file_encryption_keylen);
switch (MyAuxProcType)
{
case StartupProcess:

View File

@ -0,0 +1,18 @@
#-------------------------------------------------------------------------
#
# Makefile
# Makefile for src/backend/crypto
#
# IDENTIFICATION
# src/backend/crypto/Makefile
#
#-------------------------------------------------------------------------
subdir = src/backend/crypto
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = \
kmgr.o
include $(top_srcdir)/src/backend/common.mk

372
src/backend/crypto/kmgr.c Normal file
View File

@ -0,0 +1,372 @@
/*-------------------------------------------------------------------------
*
* kmgr.c
* Cluster file encryption routines
*
* Cluster file encryption is enabled if user requests it during initdb.
* During bootstrap, we generate data encryption keys, wrap them with the
* cluster-level key, and store them into each file located at KMGR_DIR.
* Once generated, these are not changed. During startup, we decrypt all
* internal keys and load them to the shared memory space. Internal keys
* on the shared memory are read-only. All wrapping and unwrapping key
* routines require the OpenSSL library.
*
* Copyright (c) 2020, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/crypto/kmgr.c
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <sys/stat.h>
#include <unistd.h>
#include "funcapi.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "common/file_perm.h"
#include "common/hex_decode.h"
#include "common/kmgr_utils.h"
#include "common/sha2.h"
#include "access/xlog.h"
#include "crypto/kmgr.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/shmem.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
/* Struct stores file encryption keys in plaintext format */
typedef struct KmgrShmemData
{
CryptoKey intlKeys[KMGR_MAX_INTERNAL_KEYS];
} KmgrShmemData;
static KmgrShmemData *KmgrShmem;
/* GUC variables */
char *cluster_key_command = NULL;
int file_encryption_keylen = 0;
CryptoKey bootstrap_keys[KMGR_MAX_INTERNAL_KEYS];
extern char *bootstrap_old_key_datadir;
extern int bootstrap_file_encryption_keylen;
static void bzeroKmgrKeys(int status, Datum arg);
static void KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys);
static CryptoKey *generate_crypto_key(int len);
/*
* This function must be called ONCE during initdb.
*/
void
BootStrapKmgr(void)
{
char live_path[MAXPGPATH];
CryptoKey *keys_wrap;
int nkeys;
char cluster_key_hex[ALLOC_KMGR_CLUSTER_KEY_LEN];
int cluster_key_hex_len;
unsigned char cluster_key[KMGR_CLUSTER_KEY_LEN];
#ifndef USE_OPENSSL
ereport(ERROR,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
(errmsg("cluster file encryption is not supported because OpenSSL is not supported by this build"),
errhint("Compile with --with-openssl to use this feature."))));
#endif
snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
/* copy cluster file encryption keys from an old cluster? */
if (bootstrap_old_key_datadir != NULL)
{
char old_key_dir[MAXPGPATH];
snprintf(old_key_dir, sizeof(old_key_dir), "%s/%s",
bootstrap_old_key_datadir, LIVE_KMGR_DIR);
copydir(old_key_dir, LIVE_KMGR_DIR, true);
}
/* create empty directory */
else
{
if (mkdir(LIVE_KMGR_DIR, pg_dir_create_mode) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create cluster file encryption directory \"%s\": %m",
LIVE_KMGR_DIR)));
}
/*
* Get key encryption key from the cluster_key command. The cluster_key
* command might want to check for the existance of files in the
* live directory, so run this _after_ copying the directory in place.
*/
cluster_key_hex_len = kmgr_run_cluster_key_command(cluster_key_command,
cluster_key_hex,
ALLOC_KMGR_CLUSTER_KEY_LEN,
live_path);
if (hex_decode(cluster_key_hex, cluster_key_hex_len, (char*) cluster_key) !=
KMGR_CLUSTER_KEY_LEN)
ereport(ERROR,
(errmsg("cluster key must be %d hexadecimal characters",
KMGR_CLUSTER_KEY_LEN * 2)));
/* generate new cluster file encryption keys */
if (bootstrap_old_key_datadir == NULL)
{
CryptoKey bootstrap_keys_wrap[KMGR_MAX_INTERNAL_KEYS];
PgCipherCtx *cluster_key_ctx;
/* Create KEK encryption context */
cluster_key_ctx = pg_cipher_ctx_create(PG_CIPHER_AES_GCM, cluster_key,
KMGR_CLUSTER_KEY_LEN, true);
if (!cluster_key_ctx)
elog(ERROR, "could not initialize encryption context");
/* Wrap all data encryption keys by key encryption key */
for (int id = 0; id < KMGR_MAX_INTERNAL_KEYS; id++)
{
CryptoKey *key;
/* generate a data encryption key */
key = generate_crypto_key(bootstrap_file_encryption_keylen);
/* Set this key's ID */
key->pgkey_id = id;
if (!kmgr_wrap_key(cluster_key_ctx, key, &(bootstrap_keys_wrap[id])))
{
pg_cipher_ctx_free(cluster_key_ctx);
elog(ERROR, "failed to wrap data encryption key");
}
explicit_bzero(key, sizeof(CryptoKey));
}
/* Save data encryption keys to the disk */
KmgrSaveCryptoKeys(LIVE_KMGR_DIR, bootstrap_keys_wrap);
explicit_bzero(bootstrap_keys_wrap, sizeof(bootstrap_keys_wrap));
pg_cipher_ctx_free(cluster_key_ctx);
}
/*
* We are either decrypting keys we copied from an old cluster, or
* decrypting keys we just wrote above --- either way, we decrypt
* them here and store them in a file-scoped variable for use in
* later encrypting during bootstrap mode.
*/
/* Get the crypto keys from the file */
keys_wrap = kmgr_get_cryptokeys(LIVE_KMGR_DIR, &nkeys);
Assert(nkeys == KMGR_MAX_INTERNAL_KEYS);
if (!kmgr_verify_cluster_key(cluster_key, keys_wrap, bootstrap_keys,
KMGR_MAX_INTERNAL_KEYS))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("supplied cluster key does not match expected cluster_key")));
/* bzero keys on exit */
on_proc_exit(bzeroKmgrKeys, 0);
explicit_bzero(cluster_key_hex, cluster_key_hex_len);
explicit_bzero(cluster_key, KMGR_CLUSTER_KEY_LEN);
}
/* Report shared-memory space needed by KmgrShmem */
Size
KmgrShmemSize(void)
{
if (!file_encryption_keylen)
return 0;
return MAXALIGN(sizeof(KmgrShmemData));
}
/* Allocate and initialize key manager memory */
void
KmgrShmemInit(void)
{
bool found;
if (!file_encryption_keylen)
return;
KmgrShmem = (KmgrShmemData *) ShmemInitStruct("File encryption key manager",
KmgrShmemSize(), &found);
on_shmem_exit(bzeroKmgrKeys, 0);
}
/*
* Get cluster key and verify it, then get the data encryption keys.
* This function is called by postmaster at startup time.
*/
void
InitializeKmgr(void)
{
CryptoKey *keys_wrap;
int nkeys;
char cluster_key_hex[ALLOC_KMGR_CLUSTER_KEY_LEN];
int cluster_key_hex_len;
struct stat buffer;
char live_path[MAXPGPATH];
unsigned char cluster_key[KMGR_CLUSTER_KEY_LEN];
if (!file_encryption_keylen)
return;
elog(DEBUG1, "starting up cluster file encryption manager");
if (stat(KMGR_DIR, &buffer) != 0 || !S_ISDIR(buffer.st_mode))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster file encryption directory %s is missing", KMGR_DIR))));
if (stat(KMGR_DIR_PID, &buffer) == 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster had a pg_alterckey failure that needs repair or pg_alterckey is running"),
errhint("Run pg_alterckey --repair or wait for it to complete."))));
/*
* We want OLD deleted since it allows access to the data encryption
* keys using the old cluster key. If NEW exists, it means either
* NEW is partly written, or NEW wasn't renamed to LIVE --- in either
* case, it needs to be repaired.
*/
if (stat(OLD_KMGR_DIR, &buffer) == 0 || stat(NEW_KMGR_DIR, &buffer) == 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster had a pg_alterckey failure that needs repair"),
errhint("Run pg_alterckey --repair."))));
/* If OLD, NEW, and LIVE do not exist, there is a serious problem. */
if (stat(LIVE_KMGR_DIR, &buffer) != 0 || !S_ISDIR(buffer.st_mode))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
(errmsg("cluster has no data encryption keys"))));
/* Get cluster key */
snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR);
cluster_key_hex_len = kmgr_run_cluster_key_command(cluster_key_command,
cluster_key_hex,
ALLOC_KMGR_CLUSTER_KEY_LEN,
live_path);
if (hex_decode(cluster_key_hex, cluster_key_hex_len, (char*) cluster_key) !=
KMGR_CLUSTER_KEY_LEN)
ereport(ERROR,
(errmsg("cluster key must be %d hexadecimal characters",
KMGR_CLUSTER_KEY_LEN * 2)));
/* Get the crypto keys from the file */
keys_wrap = kmgr_get_cryptokeys(LIVE_KMGR_DIR, &nkeys);
Assert(nkeys == KMGR_MAX_INTERNAL_KEYS);
/*
* Verify cluster key and prepare a data encryption key in plaintext in shared memory.
*/
if (!kmgr_verify_cluster_key(cluster_key, keys_wrap, KmgrShmem->intlKeys,
KMGR_MAX_INTERNAL_KEYS))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("supplied cluster key does not match expected cluster key")));
explicit_bzero(cluster_key_hex, cluster_key_hex_len);
explicit_bzero(cluster_key, KMGR_CLUSTER_KEY_LEN);
}
static void
bzeroKmgrKeys(int status, Datum arg)
{
if (IsBootstrapProcessingMode())
explicit_bzero(bootstrap_keys, sizeof(bootstrap_keys));
else
explicit_bzero(KmgrShmem->intlKeys, sizeof(KmgrShmem->intlKeys));
}
const CryptoKey *
KmgrGetKey(int id)
{
Assert(id < KMGR_MAX_INTERNAL_KEYS);
return (const CryptoKey *) (IsBootstrapProcessingMode() ?
&(bootstrap_keys[id]) : &(KmgrShmem->intlKeys[id]));
}
/* Generate an empty CryptoKey */
static CryptoKey *
generate_crypto_key(int len)
{
CryptoKey *newkey;
Assert(len <= KMGR_MAX_KEY_LEN);
newkey = (CryptoKey *) palloc0(sizeof(CryptoKey));
/* We store the key as length + key into 'encrypted_key' */
memcpy(newkey->encrypted_key, &len, sizeof(len));
if (!pg_strong_random(newkey->encrypted_key + sizeof(len), len))
elog(ERROR, "failed to generate new file encryption key");
return newkey;
}
/*
* Save the given file encryption keys to the disk.
*/
static void
KmgrSaveCryptoKeys(const char *dir, CryptoKey *keys)
{
elog(DEBUG2, "saving all cryptographic keys");
for (int i = 0; i < KMGR_MAX_INTERNAL_KEYS; i++)
{
int fd;
char path[MAXPGPATH];
CryptoKeyFilePath(path, dir, i);
if ((fd = BasicOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY)) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m",
path)));
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_WRITE);
if (write(fd, &(keys[i]), sizeof(CryptoKey)) != sizeof(CryptoKey))
{
/* if write didn't set errno, assume problem is no disk space */
if (errno == 0)
errno = ENOSPC;
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write file \"%s\": %m",
path)));
}
pgstat_report_wait_end();
pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_SYNC);
if (pg_fsync(fd) != 0)
ereport(PANIC,
(errcode_for_file_access(),
errmsg("could not fsync file \"%s\": %m",
path)));
pgstat_report_wait_end();
if (close(fd) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not close file \"%s\": %m",
path)));
}
}

View File

@ -324,6 +324,7 @@ help(const char *progname)
#endif
printf(_(" -N MAX-CONNECT maximum number of allowed connections\n"));
printf(_(" -p PORT port number to listen on\n"));
printf(_(" -R fd prompt for the cluster key\n"));
printf(_(" -s show statistics after each query\n"));
printf(_(" -S WORK-MEM set amount of memory for sorts (in kB)\n"));
printf(_(" -V, --version output version information, then exit\n"));
@ -351,7 +352,9 @@ help(const char *progname)
printf(_("\nOptions for bootstrapping mode:\n"));
printf(_(" --boot selects bootstrapping mode (must be first argument)\n"));
printf(_(" DBNAME database name (mandatory argument in bootstrapping mode)\n"));
printf(_(" -K LEN enable cluster file encryption with specified key length\n"));
printf(_(" -r FILENAME send stdout and stderr to given file\n"));
printf(_(" -u DATADIR copy encryption keys from datadir\n"));
printf(_(" -x NUM internal use\n"));
printf(_("\nPlease read the documentation for the complete list of run-time\n"

View File

@ -4152,6 +4152,15 @@ pgstat_get_wait_io(WaitEventIO w)
case WAIT_EVENT_DSM_FILL_ZERO_WRITE:
event_name = "DSMFillZeroWrite";
break;
case WAIT_EVENT_KEY_FILE_READ:
event_name = "KeyFileRead";
break;
case WAIT_EVENT_KEY_FILE_WRITE:
event_name = "KeyFileWrite";
break;
case WAIT_EVENT_KEY_FILE_SYNC:
event_name = "KeyFileSync";
break;
case WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ:
event_name = "LockFileAddToDataDirRead";
break;

View File

@ -100,6 +100,7 @@
#include "common/file_perm.h"
#include "common/ip.h"
#include "common/string.h"
#include "crypto/kmgr.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
#include "libpq/libpq.h"
@ -231,6 +232,7 @@ static int SendStop = false;
/* still more option variables */
bool EnableSSL = false;
int terminal_fd = -1;
int PreAuthDelay = 0;
int AuthenticationTimeout = 60;
@ -687,7 +689,7 @@ PostmasterMain(int argc, char *argv[])
* tcop/postgres.c (the option sets should not conflict) and with the
* common help() function in main/main.c.
*/
while ((opt = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:W:-:")) != -1)
while ((opt = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:R:S:sTt:W:-:")) != -1)
{
switch (opt)
{
@ -778,6 +780,10 @@ PostmasterMain(int argc, char *argv[])
/* only used by single-user backend */
break;
case 'R':
terminal_fd = atoi(optarg);
break;
case 'S':
SetConfigOption("work_mem", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
@ -1326,6 +1332,11 @@ PostmasterMain(int argc, char *argv[])
*/
RemovePgTempFiles();
InitializeKmgr();
if (terminal_fd != -1)
close(terminal_fd);
/*
* Initialize stats collection subsystem (this does NOT start the
* collector process!)

View File

@ -18,6 +18,7 @@
#include "access/xlog_internal.h" /* for pg_start/stop_backup */
#include "catalog/pg_type.h"
#include "common/kmgr_utils.h"
#include "common/file_perm.h"
#include "commands/progress.h"
#include "lib/stringinfo.h"
@ -152,6 +153,10 @@ struct exclude_list_item
*/
static const char *const excludeDirContents[] =
{
/* Skip temporary crypto key directories */
NEW_KMGR_DIR,
OLD_KMGR_DIR,
/*
* Skip temporary statistics files. PG_STAT_TMP_DIR must be skipped even
* when stats_temp_directory is set because PGSS_TEXT_FILE is always

View File

@ -23,6 +23,7 @@
#include "access/syncscan.h"
#include "access/twophase.h"
#include "commands/async.h"
#include "crypto/kmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
@ -149,6 +150,7 @@ CreateSharedMemoryAndSemaphores(void)
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
@ -267,6 +269,7 @@ CreateSharedMemoryAndSemaphores(void)
BTreeShmemInit();
SyncScanShmemInit();
AsyncShmemInit();
KmgrShmemInit();
#ifdef EXEC_BACKEND

View File

@ -53,3 +53,4 @@ XactTruncationLock 44
# 45 was XactTruncationLock until removal of BackendRandomLock
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
KmgrFileLock 48

View File

@ -42,6 +42,7 @@
#include "catalog/pg_type.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "crypto/kmgr.h"
#include "executor/spi.h"
#include "jit/jit.h"
#include "libpq/libpq.h"
@ -3578,7 +3579,7 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx,
* postmaster/postmaster.c (the option sets should not conflict) and with
* the common help() function in main/main.c.
*/
while ((flag = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:v:W:-:")) != -1)
while ((flag = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:R:S:sTt:v:W:-:")) != -1)
{
switch (flag)
{
@ -3670,6 +3671,16 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx,
strlcpy(OutputFileName, optarg, MAXPGPATH);
break;
case 'R':
terminal_fd = atoi(optarg);
if (terminal_fd == -1)
/*
* Allow file descriptor closing to be bypassed via -1.
* We just dup sterr. This is useful for single-user mode.
*/
terminal_fd = dup(2);
break;
case 'S':
SetConfigOption("work_mem", optarg, ctx, gucsource);
break;
@ -3921,6 +3932,18 @@ PostgresMain(int argc, char *argv[],
/* Early initialization */
BaseInit();
/*
* Initialize kmgr for cluster encryption. Since kmgr needs to attach to
* shared memory the initialization must be called after BaseInit().
*/
if (!IsUnderPostmaster)
{
InitializeKmgr();
if (terminal_fd != -1)
close(terminal_fd);
}
/*
* Create a per-backend PGPROC struct in shared memory, except in the
* EXEC_BACKEND case where this was done in SubPostmasterMain. We must do

View File

@ -47,6 +47,7 @@
#include "commands/vacuum.h"
#include "commands/variable.h"
#include "common/string.h"
#include "crypto/kmgr.h"
#include "funcapi.h"
#include "jit/jit.h"
#include "libpq/auth.h"
@ -745,6 +746,8 @@ const char *const config_group_names[] =
gettext_noop("Statistics / Monitoring"),
/* STATS_COLLECTOR */
gettext_noop("Statistics / Query and Index Statistics Collector"),
/* ENCRYPTION */
gettext_noop("Encryption"),
/* AUTOVACUUM */
gettext_noop("Autovacuum"),
/* CLIENT_CONN */
@ -3389,6 +3392,17 @@ static struct config_int ConfigureNamesInt[] =
check_huge_page_size, NULL, NULL
},
{
{"file_encryption_keylen", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the bit length of the file encryption key."),
NULL,
GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
},
&file_encryption_keylen,
0, 0, 256,
NULL, NULL, NULL
},
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
@ -4383,6 +4397,16 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
{
{"cluster_key_command", PGC_SIGHUP, ENCRYPTION,
gettext_noop("Command to obtain cluster key for cluster file encryption."),
NULL
},
&cluster_key_command,
"",
NULL, NULL, NULL
},
{
{"application_name", PGC_USERSET, LOGGING_WHAT,
gettext_noop("Sets the application name to be reported in statistics and logs."),

View File

@ -263,8 +263,8 @@ pg_control_recovery(PG_FUNCTION_ARGS)
Datum
pg_control_init(PG_FUNCTION_ARGS)
{
Datum values[11];
bool nulls[11];
Datum values[12];
bool nulls[12];
TupleDesc tupdesc;
HeapTuple htup;
ControlFileData *ControlFile;
@ -274,7 +274,7 @@ pg_control_init(PG_FUNCTION_ARGS)
* Construct a tuple descriptor for the result row. This must match this
* function's pg_proc entry!
*/
tupdesc = CreateTemplateTupleDesc(11);
tupdesc = CreateTemplateTupleDesc(12);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "max_data_alignment",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database_block_size",
@ -297,6 +297,8 @@ pg_control_init(PG_FUNCTION_ARGS)
BOOLOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 11, "data_page_checksum_version",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 12, "file_encryption_keylen",
INT4OID, -1, 0);
tupdesc = BlessTupleDesc(tupdesc);
/* read the control file */
@ -338,6 +340,9 @@ pg_control_init(PG_FUNCTION_ARGS)
values[10] = Int32GetDatum(ControlFile->data_checksum_version);
nulls[10] = false;
values[11] = Int32GetDatum(ControlFile->file_encryption_keylen);
nulls[11] = false;
htup = heap_form_tuple(tupdesc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(htup));

View File

@ -632,6 +632,11 @@
# autovacuum, -1 means use
# vacuum_cost_limit
#------------------------------------------------------------------------------
# ENCRYPTION
#------------------------------------------------------------------------------
#cluster_key_command = ''
#------------------------------------------------------------------------------
# CLIENT CONNECTION DEFAULTS

View File

@ -141,11 +141,16 @@ static bool debug = false;
static bool noclean = false;
static bool do_sync = true;
static bool sync_only = false;
static bool pass_terminal_fd = false;
static char *term_fd_opt = NULL;
static int file_encryption_keylen = 0;
static bool show_setting = false;
static bool data_checksums = false;
static char *xlog_dir = NULL;
static char *str_wal_segment_size_mb = NULL;
static int wal_segment_size_mb;
static char *cluster_key_cmd = NULL;
static char *old_key_datadir = NULL;
/* internal vars */
@ -203,6 +208,7 @@ static const char *const subdirs[] = {
"global",
"pg_wal/archive_status",
"pg_commit_ts",
"pg_cryptokeys",
"pg_dynshmem",
"pg_notify",
"pg_serial",
@ -954,12 +960,13 @@ test_config_settings(void)
test_buffs = MIN_BUFS_FOR_CONNS(test_conns);
snprintf(cmd, sizeof(cmd),
"\"%s\" --boot -x0 %s "
"\"%s\" --boot -x0 %s %s "
"-c max_connections=%d "
"-c shared_buffers=%d "
"-c dynamic_shared_memory_type=%s "
"< \"%s\" > \"%s\" 2>&1",
backend_exec, boot_options,
term_fd_opt ? term_fd_opt : "",
test_conns, test_buffs,
dynamic_shared_memory_type,
DEVNULL, DEVNULL);
@ -990,12 +997,13 @@ test_config_settings(void)
}
snprintf(cmd, sizeof(cmd),
"\"%s\" --boot -x0 %s "
"\"%s\" --boot -x0 %s %s "
"-c max_connections=%d "
"-c shared_buffers=%d "
"-c dynamic_shared_memory_type=%s "
"< \"%s\" > \"%s\" 2>&1",
backend_exec, boot_options,
term_fd_opt ? term_fd_opt : "",
n_connections, test_buffs,
dynamic_shared_memory_type,
DEVNULL, DEVNULL);
@ -1185,6 +1193,13 @@ setup_config(void)
"password_encryption = md5");
}
if (cluster_key_cmd)
{
snprintf(repltok, sizeof(repltok), "cluster_key_command = '%s'",
escape_quotes(cluster_key_cmd));
conflines = replace_token(conflines, "#cluster_key_command = ''", repltok);
}
/*
* If group access has been enabled for the cluster then it makes sense to
* ensure that the log files also allow group access. Otherwise a backup
@ -1394,13 +1409,22 @@ bootstrap_template1(void)
/* Also ensure backend isn't confused by this environment var: */
unsetenv("PGCLIENTENCODING");
if (file_encryption_keylen != 0)
sprintf(buf, "%d", file_encryption_keylen);
else
buf[0] = '\0';
snprintf(cmd, sizeof(cmd),
"\"%s\" --boot -x1 -X %u %s %s %s",
"\"%s\" --boot -x1 -X %u %s %s %s %s %s %s %s %s",
backend_exec,
wal_segment_size_mb * (1024 * 1024),
data_checksums ? "-k" : "",
cluster_key_cmd ? "-K" : "", buf,
old_key_datadir ? "-u" : "",
old_key_datadir ? old_key_datadir : "",
boot_options,
debug ? "-d 5" : "");
debug ? "-d 5" : "",
term_fd_opt ? term_fd_opt : "");
PG_CMD_OPEN;
@ -2281,19 +2305,25 @@ usage(const char *progname)
" set default locale in the respective category for\n"
" new databases (default taken from environment)\n"));
printf(_(" --no-locale equivalent to --locale=C\n"));
printf(_(" --pwfile=FILE read password for the new superuser from file\n"));
printf(_(" --pwfile=FILE read the new superuser password from file\n"));
printf(_(" -T, --text-search-config=CFG\n"
" default text search configuration\n"));
printf(_(" -U, --username=NAME database superuser name\n"));
printf(_(" -W, --pwprompt prompt for a password for the new superuser\n"));
printf(_(" -W, --pwprompt prompt for the new superuser password\n"));
printf(_(" -X, --waldir=WALDIR location for the write-ahead log directory\n"));
printf(_(" --wal-segsize=SIZE size of WAL segments, in megabytes\n"));
printf(_("\nLess commonly used options:\n"));
printf(_(" -c --cluster-key-command=COMMAND\n"
" enable cluster file encryption and set command\n"
" to obtain the cluster key\n"));
printf(_(" -d, --debug generate lots of debugging output\n"));
printf(_(" -k, --data-checksums use data page checksums\n"));
printf(_(" -K, --file-encryption-keylen\n"
" bit length of the file encryption key\n"));
printf(_(" -L DIRECTORY where to find the input files\n"));
printf(_(" -n, --no-clean do not clean up after errors\n"));
printf(_(" -N, --no-sync do not wait for changes to be written safely to disk\n"));
printf(_(" -R, --authprompt prompt for a passphrase or PIN\n"));
printf(_(" -s, --show show internal settings\n"));
printf(_(" -S, --sync-only only sync data directory\n"));
printf(_("\nOther options:\n"));
@ -2860,6 +2890,23 @@ initialize_data_directory(void)
/* Top level PG_VERSION is checked by bootstrapper, so make it first */
write_version_file(NULL);
if (pass_terminal_fd)
{
#ifndef WIN32
int terminal_fd = open("/dev/tty", O_RDWR, 0);
#else
int terminal_fd = open("CONOUT$", O_RDWR, 0);
#endif
if (terminal_fd < 0)
{
pg_log_error(_("%s: could not open terminal: %s\n"),
progname, strerror(errno));
exit(1);
}
term_fd_opt = psprintf("-R %d", terminal_fd);
}
/* Select suitable configuration settings */
set_null_conf();
test_config_settings();
@ -2883,8 +2930,9 @@ initialize_data_directory(void)
fflush(stdout);
snprintf(cmd, sizeof(cmd),
"\"%s\" %s template1 >%s",
"\"%s\" %s %s template1 >%s",
backend_exec, backend_options,
term_fd_opt ? term_fd_opt : "",
DEVNULL);
PG_CMD_OPEN;
@ -2957,7 +3005,11 @@ main(int argc, char *argv[])
{"waldir", required_argument, NULL, 'X'},
{"wal-segsize", required_argument, NULL, 12},
{"data-checksums", no_argument, NULL, 'k'},
{"authprompt", no_argument, NULL, 'R'},
{"file-encryption-keylen", no_argument, NULL, 'K'},
{"allow-group-access", no_argument, NULL, 'g'},
{"cluster-key-command", required_argument, NULL, 'c'},
{"copy-encryption-keys", required_argument, NULL, 'u'},
{NULL, 0, NULL, 0}
};
@ -2999,7 +3051,7 @@ main(int argc, char *argv[])
/* process command-line options */
while ((c = getopt_long(argc, argv, "A:dD:E:gkL:nNsST:U:WX:", long_options, &option_index)) != -1)
while ((c = getopt_long(argc, argv, "A:c:dD:E:gkK:L:nNRsST:u:U:WX:", long_options, &option_index)) != -1)
{
switch (c)
{
@ -3045,6 +3097,12 @@ main(int argc, char *argv[])
case 'N':
do_sync = false;
break;
case 'R':
pass_terminal_fd = true;
break;
case 'K':
file_encryption_keylen = atoi(optarg);
break;
case 'S':
sync_only = true;
break;
@ -3081,6 +3139,12 @@ main(int argc, char *argv[])
case 9:
pwfilename = pg_strdup(optarg);
break;
case 'c':
cluster_key_cmd = pg_strdup(optarg);
break;
case 'u':
old_key_datadir = pg_strdup(optarg);
break;
case 's':
show_setting = true;
break;
@ -3151,6 +3215,37 @@ main(int argc, char *argv[])
exit(1);
}
#ifndef USE_OPENSSL
if (cluster_key_cmd)
{
pg_log_error("cluster file encryption is not supported because OpenSSL is not supported by this build");
exit(1);
}
#endif
if (old_key_datadir != NULL && cluster_key_cmd == NULL)
{
pg_log_error("copying encryption keys requires the cluster key command to be specified");
exit(1);
}
if (file_encryption_keylen != 0 && cluster_key_cmd == NULL)
{
pg_log_error("a non-zero file encryption key length requires the cluster key command to be specified");
exit(1);
}
if (file_encryption_keylen != 0 && file_encryption_keylen != 128 &&
file_encryption_keylen != 192 && file_encryption_keylen != 256)
{
pg_log_error("invalid file encrypt key length; supported values are 0 (disabled), 128, 192, and 256");
exit(1);
}
/* set the default */
if (file_encryption_keylen == 0 && cluster_key_cmd != NULL)
file_encryption_keylen = 128;
check_authmethod_unspecified(&authmethodlocal);
check_authmethod_unspecified(&authmethodhost);
@ -3218,6 +3313,11 @@ main(int argc, char *argv[])
else
printf(_("Data page checksums are disabled.\n"));
if (cluster_key_cmd)
printf(_("Cluster file encryption is enabled.\n"));
else
printf(_("Cluster file encryption is disabled.\n"));
if (pwprompt || pwfilename)
get_su_pwd();

View File

@ -25,6 +25,7 @@
#include "access/xlog_internal.h"
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
#include "common/kmgr_utils.h"
#include "common/logging.h"
#include "getopt_long.h"
#include "pg_getopt.h"
@ -334,5 +335,7 @@ main(int argc, char *argv[])
ControlFile->data_checksum_version);
printf(_("Mock authentication nonce: %s\n"),
mock_auth_nonce_str);
printf(_("File encryption key length: %d\n"),
ControlFile->file_encryption_keylen);
return 0;
}

View File

@ -79,6 +79,7 @@ typedef enum
static bool do_wait = true;
static int wait_seconds = DEFAULT_WAIT;
static bool wait_seconds_arg = false;
static bool pass_terminal_fd = false;
static bool silent_mode = false;
static ShutdownMode shutdown_mode = FAST_MODE;
static int sig = SIGINT; /* default */
@ -442,7 +443,7 @@ free_readfile(char **optlines)
static pgpid_t
start_postmaster(void)
{
char cmd[MAXPGPATH];
char cmd[MAXPGPATH], *term_fd_opt = NULL;
#ifndef WIN32
pgpid_t pm_pid;
@ -467,6 +468,19 @@ start_postmaster(void)
/* fork succeeded, in child */
if (pass_terminal_fd)
{
int terminal_fd = open("/dev/tty", O_RDWR, 0);
if (terminal_fd < 0)
{
write_stderr(_("%s: could not open terminal: %s\n"),
progname, strerror(errno));
exit(1);
}
term_fd_opt = psprintf(" -R %d", terminal_fd);
}
/*
* If possible, detach the postmaster process from the launching process
* group and make it a group leader, so that it doesn't get signaled along
@ -487,12 +501,14 @@ start_postmaster(void)
* has the same PID as the current child process.
*/
if (log_file != NULL)
snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s < \"%s\" >> \"%s\" 2>&1",
snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s%s < \"%s\" >> \"%s\" 2>&1",
exec_path, pgdata_opt, post_opts,
term_fd_opt ? term_fd_opt : "",
DEVNULL, log_file);
else
snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s < \"%s\" 2>&1",
exec_path, pgdata_opt, post_opts, DEVNULL);
snprintf(cmd, MAXPGPATH, "exec \"%s\" %s%s%s < \"%s\" 2>&1",
exec_path, pgdata_opt, post_opts,
term_fd_opt ? term_fd_opt : "", DEVNULL);
(void) execl("/bin/sh", "/bin/sh", "-c", cmd, (char *) NULL);
@ -513,6 +529,21 @@ start_postmaster(void)
PROCESS_INFORMATION pi;
const char *comspec;
if (pass_terminal_fd)
{
/* Hopefully we can read and write CONOUT, see simple_prompt() XXX */
/* Do CreateRestrictedProcess() children even inherit open file descriptors? XXX */
int terminal_fd = open("CONOUT$", O_RDWR, 0);
if (terminal_fd < 0)
{
write_stderr(_("%s: could not open terminal: %s\n"),
progname, strerror(errno));
exit(1);
}
term_fd_opt = psprintf(" -R %d", terminal_fd);
}
/* Find CMD.EXE location using COMSPEC, if it's set */
comspec = getenv("COMSPEC");
if (comspec == NULL)
@ -553,12 +584,14 @@ start_postmaster(void)
else
close(fd);
snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts, DEVNULL, log_file);
snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s%s < \"%s\" >> \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts,
term_fd_opt ? term_fd_opt : "", DEVNULL, log_file);
}
else
snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s < \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts, DEVNULL);
snprintf(cmd, MAXPGPATH, "\"%s\" /C \"\"%s\" %s%s%s < \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts,
term_fd_opt ? term_fd_opt : "", DEVNULL);
if (!CreateRestrictedProcess(cmd, &pi, false))
{
@ -689,7 +722,8 @@ wait_for_postmaster(pgpid_t pm_pid, bool do_checkpoint)
}
else
#endif
print_msg(".");
if (!pass_terminal_fd)
print_msg(".");
}
pg_usleep(USEC_PER_SEC / WAITS_PER_SEC);
@ -2066,6 +2100,7 @@ do_help(void)
printf(_(" -o, --options=OPTIONS command line options to pass to postgres\n"
" (PostgreSQL server executable) or initdb\n"));
printf(_(" -p PATH-TO-POSTGRES normally not necessary\n"));
printf(_(" -R, --authprompt prompt for a paasphrase or PIN\n"));
printf(_("\nOptions for stop or restart:\n"));
printf(_(" -m, --mode=MODE MODE can be \"smart\", \"fast\", or \"immediate\"\n"));
@ -2260,6 +2295,7 @@ main(int argc, char **argv)
{"mode", required_argument, NULL, 'm'},
{"pgdata", required_argument, NULL, 'D'},
{"options", required_argument, NULL, 'o'},
{"authprompt", no_argument, NULL, 'R'},
{"silent", no_argument, NULL, 's'},
{"timeout", required_argument, NULL, 't'},
{"core-files", no_argument, NULL, 'c'},
@ -2332,7 +2368,7 @@ main(int argc, char **argv)
/* process command-line options */
while (optind < argc)
{
while ((c = getopt_long(argc, argv, "cD:e:l:m:N:o:p:P:sS:t:U:wW",
while ((c = getopt_long(argc, argv, "cD:e:l:m:N:o:p:P:RsS:t:U:wW",
long_options, &option_index)) != -1)
{
switch (c)
@ -2385,6 +2421,9 @@ main(int argc, char **argv)
case 'P':
register_password = pg_strdup(optarg);
break;
case 'R':
pass_terminal_fd = true;
break;
case 's':
silent_mode = true;
break;

View File

@ -804,6 +804,8 @@ PrintControlValues(bool guessed)
(ControlFile.float8ByVal ? _("by value") : _("by reference")));
printf(_("Data page checksum version: %u\n"),
ControlFile.data_checksum_version);
printf(_("File encryption key length: %d\n"),
ControlFile.file_encryption_keylen);
}

View File

@ -28,6 +28,7 @@
#include "catalog/pg_tablespace_d.h"
#include "common/hashfn.h"
#include "common/kmgr_utils.h"
#include "common/string.h"
#include "datapagemap.h"
#include "filemap.h"
@ -107,6 +108,13 @@ static const char *excludeDirContents[] =
/* Contents removed on startup, see AsyncShmemInit(). */
"pg_notify",
/*
* Skip cryptographic keys. It's generally not a good idea to copy the
* cryptographic keys from source database because these might use
* different cluster key.
*/
KMGR_DIR,
/*
* Old contents are loaded for possible debugging but are not required for
* normal operation, see SerialInit().

View File

@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include "catalog/pg_authid_d.h"
#include "common/kmgr_utils.h"
#include "fe_utils/string_utils.h"
#include "mb/pg_wchar.h"
#include "pg_upgrade.h"
@ -27,6 +28,7 @@ static void check_for_tables_with_oids(ClusterInfo *cluster);
static void check_for_reg_data_type_usage(ClusterInfo *cluster);
static void check_for_jsonb_9_4_usage(ClusterInfo *cluster);
static void check_for_pg_role_prefix(ClusterInfo *cluster);
static void check_for_cluster_key_failure(ClusterInfo *cluster);
static void check_for_new_tablespace_dir(ClusterInfo *new_cluster);
static char *get_canonical_locale_name(int category, const char *locale);
@ -139,6 +141,9 @@ check_and_dump_old_cluster(bool live_check)
if (GET_MAJOR_VERSION(old_cluster.major_version) <= 905)
check_for_pg_role_prefix(&old_cluster);
if (GET_MAJOR_VERSION(old_cluster.major_version) >= 1400)
check_for_cluster_key_failure(&old_cluster);
if (GET_MAJOR_VERSION(old_cluster.major_version) == 904 &&
old_cluster.controldata.cat_ver < JSONB_FORMAT_CHANGE_CAT_VER)
check_for_jsonb_9_4_usage(&old_cluster);
@ -173,6 +178,9 @@ check_new_cluster(void)
check_loadable_libraries();
if (GET_MAJOR_VERSION(old_cluster.major_version) >= 1400)
check_for_cluster_key_failure(&new_cluster);
switch (user_opts.transfer_mode)
{
case TRANSFER_MODE_CLONE:
@ -1269,6 +1277,32 @@ check_for_pg_role_prefix(ClusterInfo *cluster)
}
/*
* check_for_cluster_key_failure()
*
* Make sure there was no unrepaired pg_alterckey failure
*/
static void
check_for_cluster_key_failure(ClusterInfo *cluster)
{
struct stat buffer;
if (stat (KMGR_DIR_PID, &buffer) == 0)
{
if (cluster == &old_cluster)
pg_fatal("The source cluster had a pg_alterckey failure that needs repair or\n"
"pg_alterckey is running. Run pg_alterckey --repair or wait for it\n"
"to complete.\n");
else
pg_fatal("The target cluster had a pg_alterckey failure that needs repair or\n"
"pg_alterckey is running. Run pg_alterckey --repair or wait for it\n"
"to complete.\n");
}
check_ok();
}
/*
* get_canonical_locale_name
*

View File

@ -9,10 +9,16 @@
#include "postgres_fe.h"
#include <dirent.h>
#include <ctype.h>
#include "pg_upgrade.h"
#include "access/xlog_internal.h"
#include "common/controldata_utils.h"
#include "common/file_utils.h"
#include "common/kmgr_utils.h"
/*
* get_control_data()
*
@ -59,6 +65,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
bool got_date_is_int = false;
bool got_data_checksum_version = false;
bool got_cluster_state = false;
int got_file_encryption_keylen = 0;
char *lc_collate = NULL;
char *lc_ctype = NULL;
char *lc_monetary = NULL;
@ -202,6 +209,13 @@ get_control_data(ClusterInfo *cluster, bool live_check)
got_data_checksum_version = true;
}
/* Only in <= 14 */
if (GET_MAJOR_VERSION(cluster->major_version) <= 1400)
{
cluster->controldata.file_encryption_keylen = 0;
got_file_encryption_keylen = true;
}
/* we have the result of cmd in "output". so parse it line by line now */
while (fgets(bufin, sizeof(bufin), output))
{
@ -485,6 +499,18 @@ get_control_data(ClusterInfo *cluster, bool live_check)
cluster->controldata.data_checksum_version = str2uint(p);
got_data_checksum_version = true;
}
else if ((p = strstr(bufin, "File encryption key length:")) != NULL)
{
p = strchr(p, ':');
if (p == NULL || strlen(p) <= 1)
pg_fatal("%d: controldata retrieval problem\n", __LINE__);
p++; /* remove ':' char */
/* used later for contrib check */
cluster->controldata.file_encryption_keylen = atoi(p);
got_file_encryption_keylen = true;
}
}
pclose(output);
@ -539,7 +565,8 @@ get_control_data(ClusterInfo *cluster, bool live_check)
!got_index || !got_toast ||
(!got_large_object &&
cluster->controldata.ctrl_ver >= LARGE_OBJECT_SIZE_PG_CONTROL_VER) ||
!got_date_is_int || !got_data_checksum_version)
!got_date_is_int || !got_data_checksum_version ||
!got_file_encryption_keylen)
{
if (cluster == &old_cluster)
pg_log(PG_REPORT,
@ -605,6 +632,10 @@ get_control_data(ClusterInfo *cluster, bool live_check)
if (!got_data_checksum_version)
pg_log(PG_REPORT, " data checksum version\n");
/* value added in Postgres 14 */
if (!got_file_encryption_keylen)
pg_log(PG_REPORT, " file encryption key length\n");
pg_fatal("Cannot continue without required control information, terminating\n");
}
}
@ -669,6 +700,15 @@ check_control_data(ControlData *oldctrl,
pg_fatal("old cluster uses data checksums but the new one does not\n");
else if (oldctrl->data_checksum_version != newctrl->data_checksum_version)
pg_fatal("old and new cluster pg_controldata checksum versions do not match\n");
/*
* We cannot upgrade if the old cluster file encryption key length
* doesn't match the new one.
*/
if (oldctrl->file_encryption_keylen != newctrl->file_encryption_keylen)
pg_fatal("old and new clusters use different file encryption key lengths or\n"
"one cluster uses encryption and the other does not");
}

View File

@ -11,6 +11,7 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#ifdef HAVE_COPYFILE_H
#include <copyfile.h>
#endif
@ -21,6 +22,7 @@
#include "access/visibilitymap.h"
#include "common/file_perm.h"
#include "common/file_utils.h"
#include "pg_upgrade.h"
#include "storage/bufpage.h"
#include "storage/checksum.h"

View File

@ -52,6 +52,7 @@ parseCommandLine(int argc, char *argv[])
{"check", no_argument, NULL, 'c'},
{"link", no_argument, NULL, 'k'},
{"retain", no_argument, NULL, 'r'},
{"authprompt", no_argument, NULL, 'R'},
{"jobs", required_argument, NULL, 'j'},
{"socketdir", required_argument, NULL, 's'},
{"verbose", no_argument, NULL, 'v'},
@ -102,7 +103,7 @@ parseCommandLine(int argc, char *argv[])
if (os_user_effective_id == 0)
pg_fatal("%s: cannot be run as root\n", os_info.progname);
while ((option = getopt_long(argc, argv, "d:D:b:B:cj:ko:O:p:P:rs:U:v",
while ((option = getopt_long(argc, argv, "d:D:b:B:cj:ko:O:p:P:rRs:U:v",
long_options, &optindex)) != -1)
{
switch (option)
@ -180,6 +181,10 @@ parseCommandLine(int argc, char *argv[])
log_opts.retain = true;
break;
case 'R':
user_opts.pass_terminal_fd = true;
break;
case 's':
user_opts.socketdir = pg_strdup(optarg);
break;

View File

@ -11,6 +11,7 @@
#include <sys/time.h>
#include "libpq-fe.h"
#include "common/kmgr_utils.h"
/* Use port in the private/dynamic port number range */
#define DEF_PGUPORT 50432
@ -219,6 +220,7 @@ typedef struct
bool date_is_int;
bool float8_pass_by_value;
bool data_checksum_version;
int file_encryption_keylen;
} ControlData;
/*
@ -293,6 +295,7 @@ typedef struct
int jobs; /* number of processes/threads to use */
char *socketdir; /* directory to use for Unix sockets */
bool ind_coll_unknown; /* mark unknown index collation versions */
bool pass_terminal_fd; /* pass -R to pg_ctl? */
} UserOpts;
typedef struct

View File

@ -244,8 +244,9 @@ start_postmaster(ClusterInfo *cluster, bool report_and_exit_on_error)
* vacuumdb --freeze actually freezes the tuples.
*/
snprintf(cmd, sizeof(cmd),
"\"%s/pg_ctl\" -w -l \"%s\" -D \"%s\" -o \"-p %d%s%s %s%s\" start",
cluster->bindir, SERVER_LOG_FILE, cluster->pgconfig, cluster->port,
"\"%s/pg_ctl\" -w%s -l \"%s\" -D \"%s\" -o \"-p %d%s%s %s%s\" start",
cluster->bindir, user_opts.pass_terminal_fd ? " -R" : "",
SERVER_LOG_FILE, cluster->pgconfig, cluster->port,
(cluster->controldata.cat_ver >=
BINARY_UPGRADE_SERVER_FLAG_CAT_VER) ? " -b" :
" -c autovacuum=off -c autovacuum_freeze_max_age=2000000000",

View File

@ -62,6 +62,7 @@ OBJS_COMMON = \
ip.o \
jsonapi.o \
keywords.o \
kmgr_utils.o \
kwlookup.o \
link-canary.o \
md5_common.o \
@ -82,10 +83,12 @@ OBJS_COMMON = \
ifeq ($(with_openssl),yes)
OBJS_COMMON += \
cipher_openssl.o \
protocol_openssl.o \
cryptohash_openssl.o
else
OBJS_COMMON += \
cipher.o \
cryptohash.o \
md5.o \
sha2.o

67
src/common/cipher.c Normal file
View File

@ -0,0 +1,67 @@
/*-------------------------------------------------------------------------
*
* cipher.c
* Shared frontend/backend for cryptographic functions
*
* Copyright (c) 2020, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/common/cipher.c
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include "common/cipher.h"
static cipher_failure(void);
PgCipherCtx *
pg_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
{
cipher_failure();
}
void
pg_cipher_ctx_free(PgCipherCtx *ctx)
{
cipher_failure();
}
bool
pg_cipher_encrypt(PgCipherCtx *ctx, const unsigned char *plaintext,
const int inlen, unsigned char *ciphertext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *outtag, const int taglen)
{
cipher_failure();
}
bool
pg_cipher_decrypt(PgCipherCtx *ctx, const unsigned char *ciphertext,
const int inlen, unsigned char *plaintext, int *outlen,
const unsigned char *iv, const int ivlen,
const unsigned char *intag, const int taglen)
{
cipher_failure();
}
static
cipher_failure(void)
{
#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
(errmsg("cluster file encryption is not supported because OpenSSL is not supported by this build"),
errhint("Compile with --with-openssl to use this feature."))));
#else
fprintf(stderr, _("cluster file encryption is not supported because OpenSSL is not supported by this build"));
exit(1);
#endif
}

268
src/common/cipher_openssl.c Normal file
View File

@ -0,0 +1,268 @@
/*-------------------------------------------------------------------------
* cipher_openssl.c
* Cryptographic function using OpenSSL
*
* This contains the common low-level functions needed in both frontend and
* backend, for implement the database encryption.
*
* Portions Copyright (c) 2020, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/common/cipher_openssl.c
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include "common/cipher.h"
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
/*
* prototype for the EVP functions that return an algorithm, e.g.
* EVP_aes_128_gcm().
*/
typedef const EVP_CIPHER *(*ossl_EVP_cipher_func) (void);
static ossl_EVP_cipher_func get_evp_aes_gcm(int klen);
static EVP_CIPHER_CTX *ossl_cipher_ctx_create(int cipher, uint8 *key, int klen,
bool enc);
/*
* Return a newly created cipher context. 'cipher' specifies cipher algorithm
* by identifer like PG_CIPHER_XXX.
*/
PgCipherCtx *
pg_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
{
PgCipherCtx *ctx = NULL;
if (cipher >= PG_MAX_CIPHER_ID)
return NULL;
ctx = ossl_cipher_ctx_create(cipher, key, klen, enc);
return ctx;
}
void
pg_cipher_ctx_free(PgCipherCtx *ctx)
{
EVP_CIPHER_CTX_free(ctx);
}
/*
* Encryption routine to encrypt data provided.
*
* ctx is the encryption context which must have been created previously.
*
* plaintext is the data we are going to encrypt
* inlen is the length of the data to encrypt
*
* ciphertext is the encrypted result
* outlen is the encrypted length
*
* iv is the IV to use.
* ivlen is the IV length to use.
*
* outtag is the resulting tag.
* taglen is the length of the tag.
*/
bool
pg_cipher_encrypt(PgCipherCtx *ctx,
const unsigned char *plaintext, const int inlen,
unsigned char *ciphertext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *outtag, const int taglen)
{
int len;
int enclen;
Assert(ctx != NULL);
/*
* Here we are setting the IV for the context which was passed
* in. Note that we signal to OpenSSL that we are configuring
* a new value for the context by passing in 'NULL' for the
* 2nd ('type') parameter.
*/
/* Set the IV length first */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivlen, NULL))
return false;
/* Set the IV for this encryption. */
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv))
return false;
/*
* This is the function which is actually performing the
* encryption for us.
*/
if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, inlen))
return false;
enclen = len;
/* Finalize the encryption, which could add more to output. */
if (!EVP_EncryptFinal_ex(ctx, ciphertext + enclen, &len))
return false;
*outlen = enclen + len;
/*
* Once all of the encryption has been completed we grab
* the tag.
*/
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, taglen, outtag))
return false;
return true;
}
/*
* Decryption routine
*
* ctx is the encryption context which must have been created previously.
*
* ciphertext is the data we are going to decrypt
* inlen is the length of the data to decrypt
*
* plaintext is the decrypted result
* outlen is the decrypted length
*
* iv is the IV to use.
* ivlen is the length of the IV.
*
* intag is the tag to use to verify.
* taglen is the length of the tag.
*/
bool
pg_cipher_decrypt(PgCipherCtx *ctx,
const unsigned char *ciphertext, const int inlen,
unsigned char *plaintext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *intag, const int taglen)
{
int declen;
int len;
/*
* Here we are setting the IV for the context which was passed
* in. Note that we signal to OpenSSL that we are configuring
* a new value for the context by passing in 'NULL' for the
* 2nd ('type') parameter.
*/
/* Set the IV length first */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivlen, NULL))
return false;
/* Set the IV for this decryption. */
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv))
return false;
/*
* This is the function which is actually performing the
* decryption for us.
*/
if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, inlen))
return false;
declen = len;
/* Set the expected tag value. */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, taglen, intag))
return false;
/*
* Finalize the decryption, which could add more to output,
* this is also the step which checks the tag and we MUST
* fail if this indicates an invalid tag!
*/
if (!EVP_DecryptFinal_ex(ctx, plaintext + declen, &len))
return false;
*outlen = declen + len;
return true;
}
/*
* Returns the correct cipher functions for OpenSSL based
* on the key length requested.
*/
static ossl_EVP_cipher_func
get_evp_aes_gcm(int klen)
{
switch (klen)
{
case PG_AES128_KEY_LEN:
return EVP_aes_128_gcm;
case PG_AES192_KEY_LEN:
return EVP_aes_192_gcm;
case PG_AES256_KEY_LEN:
return EVP_aes_256_gcm;
default:
return NULL;
}
}
/*
* Initialize and return an EVP_CIPHER_CTX. Returns NULL if the given
* cipher algorithm is not supported or on failure.
*/
static EVP_CIPHER_CTX *
ossl_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
{
EVP_CIPHER_CTX *ctx;
ossl_EVP_cipher_func func;
int ret;
ctx = EVP_CIPHER_CTX_new();
/*
* We currently only support AES GCM but others could be
* added in the future.
*/
switch (cipher)
{
case PG_CIPHER_AES_GCM:
func = get_evp_aes_gcm(klen);
if (!func)
goto failed;
break;
default:
goto failed;
}
/*
* We create the context here based on the cipher requested and the provided
* key. Note that the IV will be provided in the actual encryption call
* through another EVP_EncryptInit_ex call- this is fine as long as 'type'
* is passed in as NULL!
*/
if (enc)
ret = EVP_EncryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
else
ret = EVP_DecryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
if (!ret)
goto failed;
/* Set the key length based on the key length requested. */
if (!EVP_CIPHER_CTX_set_key_length(ctx, klen))
goto failed;
return ctx;
failed:
EVP_CIPHER_CTX_free(ctx);
return NULL;
}

507
src/common/kmgr_utils.c Normal file
View File

@ -0,0 +1,507 @@
/*-------------------------------------------------------------------------
*
* kmgr_utils.c
* Shared frontend/backend for cluster file encryption
*
* Copyright (c) 2020, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/common/kmgr_utils.c
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include <unistd.h>
#include <sys/stat.h>
#ifdef FRONTEND
#include "common/logging.h"
#endif
#include "common/cryptohash.h"
#include "common/file_perm.h"
#include "common/kmgr_utils.h"
#include "common/hex_decode.h"
#include "common/string.h"
#include "crypto/kmgr.h"
#include "lib/stringinfo.h"
#include "postmaster/postmaster.h"
#include "storage/fd.h"
#ifndef FRONTEND
#include "pgstat.h"
#include "storage/fd.h"
#endif
#define KMGR_PROMPT_MSG "Enter authentication needed to generate the cluster key: "
#ifdef FRONTEND
static FILE *open_pipe_stream(const char *command);
static int close_pipe_stream(FILE *file);
#endif
static void read_one_keyfile(const char *dataDir, uint32 id, CryptoKey *key_p);
/*
* Encrypt the given data. Return true and set encrypted data to 'out' if
* success. Otherwise return false. The caller must allocate sufficient space
* for cipher data calculated by using KmgrSizeOfCipherText(). Please note that
* this function modifies 'out' data even on failure case.
*/
bool
kmgr_wrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out)
{
int len, enclen;
unsigned char iv[sizeof(in->pgkey_id) + sizeof(in->counter)];
Assert(ctx && in && out);
/* Get the actual length of the key we are wrapping */
memcpy(&len, in->encrypted_key, sizeof(len));
/* Key ID remains the same */
out->pgkey_id = in->pgkey_id;
/* Increment the counter */
out->counter = in->counter + 1;
/* Construct the IV we are going to use, see kmgr_utils.h */
memcpy(iv, &out->pgkey_id, sizeof(out->pgkey_id));
memcpy(iv + sizeof(out->pgkey_id), &out->counter, sizeof(out->counter));
if (!pg_cipher_encrypt(ctx,
in->encrypted_key, /* Plaintext source, key length + key */
sizeof(in->encrypted_key), /* Full data length */
out->encrypted_key, /* Ciphertext result */
&enclen, /* Resulting length, must match input for us */
iv, /* Generated IV from above */
sizeof(iv), /* Length of the IV */
(unsigned char *) &out->tag, /* Resulting tag */
sizeof(out->tag))) /* Length of our tag */
return false;
Assert(enclen == sizeof(in->encrypted_key));
return true;
}
/*
* Decrypt the given Data. Return true and set plain text data to `out` if
* success. Otherwise return false. The caller must allocate sufficient space
* for cipher data calculated by using KmgrSizeOfPlainText(). Please note that
* this function modifies 'out' data even on failure case.
*/
bool
kmgr_unwrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out)
{
int declen;
unsigned char iv[sizeof(in->pgkey_id) + sizeof(in->counter)];
Assert(ctx && in && out);
out->pgkey_id = in->pgkey_id;
out->counter = in->counter;
out->tag = in->tag;
/* Construct the IV we are going to use, see kmgr_utils.h */
memcpy(iv, &out->pgkey_id, sizeof(out->pgkey_id));
memcpy(iv + sizeof(out->pgkey_id), &out->counter, sizeof(out->counter));
/* Decrypt encrypted data */
if (!pg_cipher_decrypt(ctx,
in->encrypted_key, /* Encrypted source */
sizeof(in->encrypted_key), /* Length of encrypted data */
out->encrypted_key, /* Plaintext result */
&declen, /* Length of plaintext */
iv, /* IV we constructed above */
sizeof(iv), /* Size of our IV */
(unsigned char *) &in->tag, /* Tag which will be verified */
sizeof(in->tag))) /* Size of our tag */
return false;
Assert(declen == sizeof(in->encrypted_key));
return true;
}
/*
* Verify the correctness of the given cluster key by unwrapping the given keys.
* If the given cluster key is correct we set unwrapped keys to out_keys and return
* true. Otherwise return false. Please note that this function changes the
* contents of out_keys even on failure. Both in_keys and out_keys must be the
* same length, nkey.
*/
bool
kmgr_verify_cluster_key(unsigned char *cluster_key,
CryptoKey *in_keys, CryptoKey *out_keys, int nkeys)
{
PgCipherCtx *ctx;
/*
* Create decryption context with cluster KEK.
*/
ctx = pg_cipher_ctx_create(PG_CIPHER_AES_GCM, cluster_key,
KMGR_CLUSTER_KEY_LEN, false);
for (int i = 0; i < nkeys; i++)
{
if (!kmgr_unwrap_key(ctx, &(in_keys[i]), &(out_keys[i])))
{
/* The cluster key is not correct */
pg_cipher_ctx_free(ctx);
return false;
}
explicit_bzero(&(in_keys[i]), sizeof(in_keys[i]));
}
/* The cluster key is correct, free the cipher context */
pg_cipher_ctx_free(ctx);
return true;
}
/*
* Run cluster key command.
*
* prompt will be substituted for %p, file descriptor for %R
*
* The result will be put in buffer buf, which is of size size.
* The return value is the length of the actual result.
*/
int
kmgr_run_cluster_key_command(char *cluster_key_command, char *buf,
int size, char *dir)
{
StringInfoData command;
const char *sp;
FILE *fh;
int pclose_rc;
size_t len = 0;
buf[0] = '\0';
Assert(size > 0);
/*
* Build the command to be executed.
*/
initStringInfo(&command);
for (sp = cluster_key_command; *sp; sp++)
{
if (*sp == '%')
{
switch (sp[1])
{
case 'd':
{
char *nativePath;
sp++;
/*
* This needs to use a placeholder to not modify the
* input with the conversion done via
* make_native_path().
*/
nativePath = pstrdup(dir);
make_native_path(nativePath);
appendStringInfoString(&command, nativePath);
pfree(nativePath);
break;
}
case 'p':
sp++;
appendStringInfoString(&command, KMGR_PROMPT_MSG);
break;
case 'R':
{
char fd_str[20];
if (terminal_fd == -1)
{
#ifdef FRONTEND
pg_log_fatal("cluster key command referenced %%R, but --authprompt not specified");
exit(EXIT_FAILURE);
#else
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("cluster key command referenced %%R, but --authprompt not specified")));
#endif
}
sp++;
snprintf(fd_str, sizeof(fd_str), "%d", terminal_fd);
appendStringInfoString(&command, fd_str);
break;
}
case '%':
/* convert %% to a single % */
sp++;
appendStringInfoChar(&command, *sp);
break;
default:
/* otherwise treat the % as not special */
appendStringInfoChar(&command, *sp);
break;
}
}
else
{
appendStringInfoChar(&command, *sp);
}
}
#ifdef FRONTEND
fh = open_pipe_stream(command.data);
if (fh == NULL)
{
pg_log_fatal("could not execute command \"%s\": %m",
command.data);
exit(EXIT_FAILURE);
}
#else
fh = OpenPipeStream(command.data, "r");
if (fh == NULL)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not execute command \"%s\": %m",
command.data)));
#endif
if (!fgets(buf, size, fh))
{
if (ferror(fh))
{
#ifdef FRONTEND
pg_log_fatal("could not read from command \"%s\": %m",
command.data);
exit(EXIT_FAILURE);
#else
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not read from command \"%s\": %m",
command.data)));
#endif
}
}
#ifdef FRONTEND
pclose_rc = close_pipe_stream(fh);
#else
pclose_rc = ClosePipeStream(fh);
#endif
if (pclose_rc == -1)
{
#ifdef FRONTEND
pg_log_fatal("could not close pipe to external command: %m");
exit(EXIT_FAILURE);
#else
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not close pipe to external command: %m")));
#endif
}
else if (pclose_rc != 0)
{
#ifdef FRONTEND
pg_log_fatal("command \"%s\" failed", command.data);
exit(EXIT_FAILURE);
#else
ereport(ERROR,
(errcode_for_file_access(),
errmsg("command \"%s\" failed",
command.data),
errdetail_internal("%s", wait_result_to_str(pclose_rc))));
#endif
}
/* strip trailing newline and carriage return */
len = pg_strip_crlf(buf);
pfree(command.data);
return len;
}
#ifdef FRONTEND
static FILE *
open_pipe_stream(const char *command)
{
FILE *res;
#ifdef WIN32
size_t cmdlen = strlen(command);
char *buf;
int save_errno;
buf = malloc(cmdlen + 2 + 1);
if (buf == NULL)
{
errno = ENOMEM;
return NULL;
}
buf[0] = '"';
mempcy(&buf[1], command, cmdlen);
buf[cmdlen + 1] = '"';
buf[cmdlen + 2] = '\0';
res = _popen(buf, "r");
save_errno = errno;
free(buf);
errno = save_errno;
#else
res = popen(command, "r");
#endif /* WIN32 */
return res;
}
static int
close_pipe_stream(FILE *file)
{
#ifdef WIN32
return _pclose(file);
#else
return pclose(file);
#endif /* WIN32 */
}
#endif /* FRONTEND */
CryptoKey *
kmgr_get_cryptokeys(const char *path, int *nkeys)
{
struct dirent *de;
DIR *dir;
CryptoKey *keys;
#ifndef FRONTEND
if ((dir = AllocateDir(path)) == NULL)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open directory \"%s\": %m",
path)));
#else
if ((dir = opendir(path)) == NULL)
pg_log_fatal("could not open directory \"%s\": %m", path);
#endif
keys = (CryptoKey *) palloc0(sizeof(CryptoKey) * KMGR_MAX_INTERNAL_KEYS);
*nkeys = 0;
#ifndef FRONTEND
while ((de = ReadDir(dir, LIVE_KMGR_DIR)) != NULL)
#else
while ((de = readdir(dir)) != NULL)
#endif
{
if (strspn(de->d_name, "0123456789") == strlen(de->d_name))
{
uint32 id = strtoul(de->d_name, NULL, 10);
if (id < 0 || id >= KMGR_MAX_INTERNAL_KEYS)
{
#ifndef FRONTEND
elog(ERROR, "invalid cryptographic key identifier %u", id);
#else
pg_log_fatal("invalid cryptographic key identifier %u", id);
#endif
}
if (*nkeys >= KMGR_MAX_INTERNAL_KEYS)
{
#ifndef FRONTEND
elog(ERROR, "too many cryptographic keys");
#else
pg_log_fatal("too many cryptographic keys");
#endif
}
read_one_keyfile(path, id, &(keys[id]));
(*nkeys)++;
}
}
#ifndef FRONTEND
FreeDir(dir);
#else
closedir(dir);
#endif
return keys;
}
static void
read_one_keyfile(const char *cryptoKeyDir, uint32 id, CryptoKey *key_p)
{
char path[MAXPGPATH];
int fd;
int r;
CryptoKeyFilePath(path, cryptoKeyDir, id);
#ifndef FRONTEND
if ((fd = OpenTransientFile(path, O_RDONLY | PG_BINARY)) == -1)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\" for reading: %m",
path)));
#else
if ((fd = open(path, O_RDONLY | PG_BINARY, 0)) == -1)
pg_log_fatal("could not open file \"%s\" for reading: %m",
path);
#endif
#ifndef FRONTEND
pgstat_report_wait_start(WAIT_EVENT_KEY_FILE_READ);
#endif
/* Get key bytes */
r = read(fd, key_p, sizeof(CryptoKey));
if (r != sizeof(CryptoKey))
{
if (r < 0)
{
#ifndef FRONTEND
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not read file \"%s\": %m", path)));
#else
pg_log_fatal("could not read file \"%s\": %m", path);
#endif
}
else
{
#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("could not read file \"%s\": read %d of %zu",
path, r, sizeof(CryptoKey))));
#else
pg_log_fatal("could not read file \"%s\": read %d of %zu",
path, r, sizeof(CryptoKey));
#endif
}
}
#ifndef FRONTEND
pgstat_report_wait_end();
#endif
#ifndef FRONTEND
if (CloseTransientFile(fd) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not close file \"%s\": %m",
path)));
#else
if (close(fd) != 0)
pg_log_fatal("could not close file \"%s\": %m", path);
#endif
}

View File

@ -22,7 +22,7 @@
/* Version identifier for this pg_control format */
#define PG_CONTROL_VERSION 1300
#define PG_CONTROL_VERSION 1400
/* Nonce key length, see below */
#define MOCK_AUTH_NONCE_LEN 32
@ -226,6 +226,9 @@ typedef struct ControlFileData
*/
char mock_authentication_nonce[MOCK_AUTH_NONCE_LEN];
/* File encryption key length. Zero if disabled. */
int file_encryption_keylen;
/* CRC of all above ... MUST BE LAST! */
pg_crc32c crc;
} ControlFileData;

View File

@ -0,0 +1,62 @@
/*-------------------------------------------------------------------------
*
* cipher.h
* Declarations for cryptographic functions
*
* Portions Copyright (c) 2020, PostgreSQL Global Development Group
*
* src/include/common/cipher.h
*
*-------------------------------------------------------------------------
*/
#ifndef PG_CIPHER_H
#define PG_CIPHER_H
#ifdef USE_OPENSSL
#include <openssl/evp.h>
#include <openssl/conf.h>
#include <openssl/err.h>
#endif
/*
* Supported symmetric encryption algorithm. These identifiers are passed
* to pg_cipher_ctx_create() function, and then actual encryption
* implementations need to initialize their context of the given encryption
* algorithm.
*/
#define PG_CIPHER_AES_GCM 0
#define PG_MAX_CIPHER_ID 1
/* AES128/192/256 various length definitions */
#define PG_AES128_KEY_LEN (128 / 8)
#define PG_AES192_KEY_LEN (192 / 8)
#define PG_AES256_KEY_LEN (256 / 8)
/*
* The encrypted data is a series of blocks of size. Initialization
* vector(IV) is the same size of cipher block.
*/
#define PG_AES_BLOCK_SIZE 16
#define PG_AES_IV_SIZE (PG_AES_BLOCK_SIZE)
#ifdef USE_OPENSSL
typedef EVP_CIPHER_CTX PgCipherCtx;
#else
typedef void PgCipherCtx;
#endif
extern PgCipherCtx *pg_cipher_ctx_create(int cipher, uint8 *key, int klen,
bool enc);
extern void pg_cipher_ctx_free(PgCipherCtx *ctx);
extern bool pg_cipher_encrypt(PgCipherCtx *ctx,
const unsigned char *plaintext, const int inlen,
unsigned char *ciphertext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *tag, const int taglen);
extern bool pg_cipher_decrypt(PgCipherCtx *ctx,
const unsigned char *ciphertext, const int inlen,
unsigned char *plaintext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *intag, const int taglen);
#endif /* PG_CIPHER_H */

View File

@ -0,0 +1,98 @@
/*-------------------------------------------------------------------------
*
* kmgr_utils.h
* Declarations for utility function for file encryption key
*
* Portions Copyright (c) 2020, PostgreSQL Global Development Group
*
* src/include/common/kmgr_utils.h
*
*-------------------------------------------------------------------------
*/
#ifndef KMGR_UTILS_H
#define KMGR_UTILS_H
#include "common/cipher.h"
/* Current version number */
#define KMGR_VERSION 1
/*
* Directories where cluster file encryption keys reside within PGDATA.
*/
#define KMGR_DIR "pg_cryptokeys"
#define KMGR_DIR_PID KMGR_DIR"/pg_alterckey.pid"
#define LIVE_KMGR_DIR KMGR_DIR"/live"
/* used during cluster key rotation */
#define NEW_KMGR_DIR KMGR_DIR"/new"
#define OLD_KMGR_DIR KMGR_DIR"/old"
/* CryptoKey file name is keys id */
#define CryptoKeyFilePath(path, dir, id) \
snprintf((path), MAXPGPATH, "%s/%d", (dir), (id))
/*
* Identifiers of internal keys.
*/
#define KMGR_KEY_ID_REL 0
#define KMGR_KEY_ID_WAL 1
#define KMGR_MAX_INTERNAL_KEYS 2
/* We always, today, use a 256-bit AES key. */
#define KMGR_CLUSTER_KEY_LEN PG_AES256_KEY_LEN
/* double for hex format, plus some for spaces, \r,\n, and null byte */
#define ALLOC_KMGR_CLUSTER_KEY_LEN (KMGR_CLUSTER_KEY_LEN * 2 + 10 + 2 + 1)
/* Maximum length of key the key manager can store */
#define KMGR_MAX_KEY_LEN 256
#define KMGR_MAX_KEY_LEN_BYTES KMGR_MAX_KEY_LEN / 8
#define KMGR_MAX_WRAPPED_KEY_LEN KmgrSizeOfCipherText(KMGR_MAX_KEY_LEN)
/*
* Cryptographic key data structure.
*
* This is the structure we use to write out the encrypted keys.
*
* pgkey_id is the identifier for this key (should be same as the
* file name and be one of KMGR_KEY_ID_* from above). This is what
* we consider our 'context' or 'fixed' portion of the deterministic
* IV we create.
*
* counter is updated each time we use the cluster KEK to encrypt a
* new key. This is our the 'invocation' field of the deterministic
* IV we create.
*
* Absolutely essential when using GCM (or CTR) is that the IV is unique,
* for a given key, but a deterministic IV such as this is perfectly
* acceptable and encouraged. If (and only if!) the KEK is changed to a
* new key, then we can re-initialize the counter.
*
* Detailed discussion of deterministic IV creation can be found here:
*
* https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
*
* tag is the GCM tag which is produced and must be validated in order
* to be able to trust the results of our decryption.
*
* encrypted_key is the encrypted key length (as an int) + encrypted key.
*/
typedef struct CryptoKey
{
uint64 pgkey_id; /* Upper half of IV */
uint64 counter; /* Lower half of IV */
uint128 tag; /* GCM tag */
unsigned char encrypted_key[sizeof(int) + KMGR_MAX_KEY_LEN_BYTES];
} CryptoKey;
extern bool kmgr_wrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out);
extern bool kmgr_unwrap_key(PgCipherCtx *ctx, CryptoKey *in, CryptoKey *out);
extern bool kmgr_verify_cluster_key(unsigned char *cluster_key,
CryptoKey *in_keys, CryptoKey *out_keys,
int nkey);
extern int kmgr_run_cluster_key_command(char *cluster_key_command,
char *buf, int size, char *dir);
extern CryptoKey *kmgr_get_cryptokeys(const char *path, int *nkeys);
#endif /* KMGR_UTILS_H */

29
src/include/crypto/kmgr.h Normal file
View File

@ -0,0 +1,29 @@
/*-------------------------------------------------------------------------
*
* kmgr.h
*
* Portions Copyright (c) 2020, PostgreSQL Global Development Group
*
* src/include/crypto/kmgr.h
*
*-------------------------------------------------------------------------
*/
#ifndef KMGR_H
#define KMGR_H
#include "common/cipher.h"
#include "common/kmgr_utils.h"
#include "storage/relfilenode.h"
#include "storage/bufpage.h"
/* GUC parameters */
extern int file_encryption_keylen;
extern char *cluster_key_command;
extern Size KmgrShmemSize(void);
extern void KmgrShmemInit(void);
extern void BootStrapKmgr(void);
extern void InitializeKmgr(void);
extern const CryptoKey *KmgrGetKey(int id);
#endif /* KMGR_H */

View File

@ -1010,6 +1010,9 @@ typedef enum
WAIT_EVENT_DATA_FILE_TRUNCATE,
WAIT_EVENT_DATA_FILE_WRITE,
WAIT_EVENT_DSM_FILL_ZERO_WRITE,
WAIT_EVENT_KEY_FILE_READ,
WAIT_EVENT_KEY_FILE_WRITE,
WAIT_EVENT_KEY_FILE_SYNC,
WAIT_EVENT_LOCK_FILE_ADDTODATADIR_READ,
WAIT_EVENT_LOCK_FILE_ADDTODATADIR_SYNC,
WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE,

View File

@ -30,6 +30,8 @@ extern bool enable_bonjour;
extern char *bonjour_name;
extern bool restart_after_crash;
extern int terminal_fd;
#ifdef WIN32
extern HANDLE PostmasterHandle;
#else

View File

@ -89,6 +89,7 @@ enum config_group
STATS,
STATS_MONITORING,
STATS_COLLECTOR,
ENCRYPTION,
AUTOVACUUM,
CLIENT_CONN,
CLIENT_CONN_STATEMENT,

View File

@ -30,7 +30,7 @@ endif
endif
ifeq ($(with_openssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
SUBDIRS += ssl crypto
endif
endif

View File

@ -122,18 +122,20 @@ sub mkvcbuild
archive.c base64.c checksum_helper.c
config_info.c controldata_utils.c d2s.c encnames.c exec.c
f2s.c file_perm.c file_utils.c hashfn.c hex_decode.c ip.c jsonapi.c
keywords.c kwlookup.c link-canary.c md5_common.c
keywords.c kmgr_utils.c kwlookup.c link-canary.c md5_common.c
pg_get_line.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
saslprep.c scram-common.c string.c stringinfo.c unicode_norm.c username.c
wait_error.c wchar.c);
if ($solution->{options}->{openssl})
{
push(@pgcommonallfiles, 'cipher_openssl.c');
push(@pgcommonallfiles, 'cryptohash_openssl.c');
push(@pgcommonallfiles, 'protocol_openssl.c');
}
else
{
push(@pgcommonallfiles, 'cipher.c');
push(@pgcommonallfiles, 'cryptohash.c');
push(@pgcommonallfiles, 'md5.c');
push(@pgcommonallfiles, 'sha2.c');