agora inbox for [email protected]
help / color / mirror / Atom feedFrom: Antonin Houska <[email protected]>
Subject: [PATCH 03/17] Teach postmaster to accept encryption key
Date: Fri, 5 Jul 2019 16:24:01 +0200
This patch teaches postmaster to accept encryption key (as a special kind of
startup packet) before recovery starts. In fact the key is received by a
backend that postmaster launches if a client tries to initiate connection
while the postmaster is in PM_INIT state. At this stage, only the key is
received, whereas regular connection attempt ends up with
FATAL: the database system is starting up
If postmaster receives no key for some time, it raises this error and exits:
FATAL: Encryption key not received.
With this patch, user can start postmaster as usual:
postgres -D data
and use pg_keytool utility in another session to send the key:
echo 71916b8b93063c280e4e0ee646395f450bda4f1bac85c57bc3a6afd168f3ab47 | pg_keytool -s
---
src/backend/postmaster/postmaster.c | 211 +++++++++++++++++++++++++++++++++-
src/backend/storage/file/encryption.c | 34 ++++++
src/backend/storage/ipc/ipci.c | 2 +
src/bin/Makefile | 1 +
src/bin/pg_keytool/.gitignore | 1 +
src/bin/pg_keytool/Makefile | 31 +++++
src/bin/pg_keytool/pg_keytool.c | 199 ++++++++++++++++++++++++++++++++
src/fe_utils/encryption.c | 101 ++++++++++++++++
src/include/fe_utils/encryption.h | 2 +
src/include/libpq/pqcomm.h | 24 ++++
src/include/storage/encryption.h | 26 +++++
11 files changed, 628 insertions(+), 4 deletions(-)
create mode 100644 src/bin/pg_keytool/.gitignore
create mode 100644 src/bin/pg_keytool/Makefile
create mode 100644 src/bin/pg_keytool/pg_keytool.c
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e8e3f8a4d3..47f7bc93f7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -117,6 +117,7 @@
#include "postmaster/syslogger.h"
#include "replication/logicallauncher.h"
#include "replication/walsender.h"
+#include "storage/encryption.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
@@ -402,12 +403,13 @@ static void PostmasterStateMachine(void);
static void BackendInitialize(Port *port);
static void BackendRun(Port *port) pg_attribute_noreturn();
static void ExitPostmaster(int status) pg_attribute_noreturn();
-static int ServerLoop(void);
+static int ServerLoop(bool receive_encryption_key);
static void ServerLoopCheckTimeouts(void);
static int BackendStartup(Port *port);
static int ProcessStartupPacket(Port *port, bool secure_done);
static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
static void processCancelRequest(Port *port, void *pkt);
+static void processEncryptionKey(void *pkt);
static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
static CAC_state canAcceptConnections(void);
@@ -1365,6 +1367,41 @@ PostmasterMain(int argc, char *argv[])
AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STARTING);
/*
+ * If encryption key is needed and we don't have it yet, call ServerLoop()
+ * in a restricted mode which allows a client application to send us the
+ * key. Regular client connections are not allowed yet.
+ */
+ if (data_encrypted && !encryption_setup_done)
+ {
+ char sample[ENCRYPTION_SAMPLE_SIZE];
+
+ status = ServerLoop(true);
+
+ /* No other return code should be seen here. */
+ Assert(status == STATUS_OK);
+
+ /* ServerLoop() shouldn't have exited otherwise. */
+ Assert(encryption_key_shmem->initialized);
+
+ /*
+ * Take a local copy of the key so that we don't have to receive it
+ * again if restarting after a crash.
+ */
+ memcpy(encryption_key, encryption_key_shmem, ENCRYPTION_KEY_LENGTH);
+
+ /* Finalize the setup. */
+ setup_encryption();
+
+ /* Verify the key. */
+ sample_encryption(sample);
+ if (memcmp(encryption_verification, sample, ENCRYPTION_SAMPLE_SIZE))
+ ereport(FATAL,
+ (errmsg("invalid encryption key"),
+ errdetail("The passed encryption key does not match"
+ " database encryption key.")));
+ }
+
+ /*
* We're ready to rock and roll...
*/
StartupPID = StartupDataBase();
@@ -1375,7 +1412,7 @@ PostmasterMain(int argc, char *argv[])
/* Some workers may be scheduled to start now */
maybe_start_bgworkers();
- status = ServerLoop();
+ status = ServerLoop(false);
/*
* ServerLoop probably shouldn't ever return, but if it does, close down.
@@ -1615,18 +1652,37 @@ DetermineSleepTime(struct timeval *timeout)
static time_t last_lockfile_recheck_time, last_touch_time;
/*
+ * The maximum amount of time postmaster can wait for encryption key if the
+ * cluster is encrypted. It should be shorter than the time pg_ctl waits for
+ * postgres to start (60 seconds) and should not be too long so that failure
+ * to specify encryption key command is recognized soon.
+ */
+#define MAX_WAIT_FOR_KEY_SECS 20
+
+/* When should waiting for encryption key end. */
+static TimestampTz wait_for_keys_until = 0;
+
+/*
* Main idle loop of postmaster
*
+ * If receive_encryption_key is true, only launch backend(s) to receive
+ * cluster encryption key and stop as soon as the key is in shared memory.
+ *
* NB: Needs to be called with signals blocked
*/
static int
-ServerLoop(void)
+ServerLoop(bool receive_encryption_key)
{
fd_set readmask;
int nSockets;
last_lockfile_recheck_time = last_touch_time = time(NULL);
+ /* Compute wait_for_keys_until if needed and not done yet. */
+ if (receive_encryption_key && wait_for_keys_until == 0)
+ wait_for_keys_until = PgStartTime +
+ MAX_WAIT_FOR_KEY_SECS * USECS_PER_SEC;
+
nSockets = initMasks(&readmask);
for (;;)
@@ -1663,6 +1719,27 @@ ServerLoop(void)
/* Needs to run with blocked signals! */
DetermineSleepTime(&timeout);
+ /*
+ * If waiting for the encryption key, make sure
+ * MAX_WAIT_FOR_KEY_SECS is not exceeded.
+ */
+ if (receive_encryption_key)
+ {
+ TimestampTz timeout_tz, timeout_end;;
+
+ timeout_tz = timeout.tv_sec * USECS_PER_SEC +
+ timeout.tv_usec;
+ timeout_end = GetCurrentTimestamp() + timeout_tz;
+
+ if (timeout_end > wait_for_keys_until)
+ {
+ timeout_tz -= (timeout_end - wait_for_keys_until);
+
+ timeout.tv_sec = timeout_tz / USECS_PER_SEC;
+ timeout.tv_usec = timeout_tz % USECS_PER_SEC;
+ }
+ }
+
PG_SETMASK(&UnBlockSig);
selres = select(nSockets, &rmask, NULL, NULL, &timeout);
@@ -1714,6 +1791,33 @@ ServerLoop(void)
}
}
+ /*
+ * If only waiting for the encryption key, it's too early to launch
+ * the other processes.
+ */
+ if (receive_encryption_key)
+ {
+ /* The regular checks should take place though. */
+ ServerLoopCheckTimeouts();
+
+ /* Done if the key has been delivered. */
+ if (encryption_key_shmem->initialized)
+ return STATUS_OK;
+
+ /*
+ * Do not wait for the key forever. One problem we solve here is
+ * that pg_ctl (or custom script that starts postgres) does not
+ * have to check whether the cluster is encrypted. If DBA forgets
+ * to pass the encryption key command, he'll simply see the
+ * startup to fail and the appropriate message to appear in the
+ * server log.
+ */
+ if (GetCurrentTimestamp() >= wait_for_keys_until)
+ ereport(FATAL, (errmsg("Encryption key not received.")));
+
+ continue;
+ }
+
/* If we have lost the log collector, try to start a new one */
if (SysLoggerPID == 0 && Logging_collector)
SysLoggerPID = SysLogger_Start();
@@ -1990,6 +2094,13 @@ ProcessStartupPacket(Port *port, bool secure_done)
return STATUS_ERROR;
}
+ if (proto == ENCRYPTION_KEY_MSG_CODE && data_encrypted)
+ {
+ processEncryptionKey(buf);
+ /* Not really an error, but we don't want to proceed further */
+ return STATUS_ERROR;
+ }
+
if (proto == NEGOTIATE_SSL_CODE && !secure_done)
{
char SSLok;
@@ -2394,6 +2505,77 @@ processCancelRequest(Port *port, void *pkt)
}
/*
+ * Process a message from backend that contains the encryption key.
+ *
+ * Currently there's no guarantee that only a single backend tries to deliver
+ * the key, but it's not worth trying to synchronize the access. If multiple
+ * callers try to process the same message, nothing should get broken. If some
+ * caller is delivering a wrong key (e.g. due to misconfiguration of another
+ * cluster), it does not help if it waits until processing of the correct key
+ * is finished.
+ *
+ * If the key gets messed up, the worst case is that the server fails to start
+ * up. (No data corruption is expected.)
+ *
+ * Client should not expect any response to this request: failure on client
+ * side indicates either broken connection, wrong key, bug in
+ * send_key_to_postmaster() or repeated call of the client - nothing to be
+ * handled easily by client code. Most of these failures need attention of the
+ * DBA.
+ */
+void
+processEncryptionKey(void *pkt)
+{
+ EncryptionKeyMsg *msg = (EncryptionKeyMsg *) pkt;
+
+ /* Backend to receive the key should not be launched otherwise. */
+ Assert(data_encrypted);
+
+ /*
+ * The checks below would fire in this case too, but this error message is
+ * clearer for the case like this.
+ */
+ if (pmState != PM_INIT)
+ {
+ ereport(COMMERROR,
+ (errmsg("encryption key can only be setup during startup"),
+ errhint("Check if the cluster is encrypted.")));
+ return;
+ }
+
+ /*
+ * Someone else already initialized the key. This is not fatal but seems
+ * to indicate misconfiguration which DBA should be aware of. Check also
+ * encryption_setup_done because shared memory could have been reset. As
+ * postmaster has a local copy of the key, the new key should not be used,
+ * but it'd be inconsistent if we didn't complain in this special case.
+ */
+ if (encryption_key_shmem->initialized || encryption_setup_done)
+ {
+ ereport(COMMERROR,
+ (errmsg("received encryption key more than once")));
+ return;
+ }
+
+ if (msg->version != 1)
+ {
+ ereport(COMMERROR,
+ (errmsg("unexpected version of encryption key message %d",
+ msg->version)));
+ return;
+ }
+
+ memcpy(encryption_key_shmem->data, msg->data, ENCRYPTION_KEY_LENGTH);
+ /*
+ * XXX Is memory barrier needed here to make sure that the copying is
+ * done?
+ */
+ encryption_key_shmem->initialized = true;
+
+ ereport(DEBUG1, (errmsg_internal("encryption key received")));
+}
+
+/*
* canAcceptConnections --- check to see if database state allows connections.
*/
static CAC_state
@@ -2418,7 +2600,8 @@ canAcceptConnections(void)
else if (Shutdown > NoShutdown)
return CAC_SHUTDOWN; /* shutdown is pending */
else if (!FatalError &&
- (pmState == PM_STARTUP ||
+ ((data_encrypted && pmState == PM_INIT) ||
+ pmState == PM_STARTUP ||
pmState == PM_RECOVERY))
return CAC_STARTUP; /* normal startup */
else if (!FatalError &&
@@ -2761,6 +2944,16 @@ pmdie(SIGNAL_ARGS)
pmState = (pmState == PM_RUN) ?
PM_WAIT_BACKUP : PM_WAIT_READONLY;
}
+ else if (pmState == PM_INIT && data_encrypted &&
+ !encryption_setup_done)
+ {
+ /*
+ * Only backends to receive encryption key may be active now,
+ * and these should not be involved in any transactions.
+ */
+ SignalSomeChildren(SIGTERM, BACKEND_TYPE_NORMAL);
+ pmState = PM_WAIT_BACKENDS;
+ }
/*
* Now wait for online backup mode to end and backends to exit. If
@@ -2828,6 +3021,16 @@ pmdie(SIGNAL_ARGS)
signal_child(WalWriterPID, SIGTERM);
pmState = PM_WAIT_BACKENDS;
}
+ else if (pmState == PM_INIT && data_encrypted &&
+ !encryption_setup_done)
+ {
+ /*
+ * Only backends to receive encryption key may be active now,
+ * and these should not be involved in any transactions.
+ */
+ SignalSomeChildren(SIGTERM, BACKEND_TYPE_NORMAL);
+ pmState = PM_WAIT_BACKENDS;
+ }
/*
* Now wait for backends to exit. If there are none,
diff --git a/src/backend/storage/file/encryption.c b/src/backend/storage/file/encryption.c
index 629138fae7..1eda334762 100644
--- a/src/backend/storage/file/encryption.c
+++ b/src/backend/storage/file/encryption.c
@@ -53,6 +53,10 @@ EVP_CIPHER_CTX *ctx_encrypt,
unsigned char encryption_key[ENCRYPTION_KEY_LENGTH];
+#ifndef FRONTEND
+ShmemEncryptionKey *encryption_key_shmem = NULL;
+#endif /* FRONTEND */
+
bool data_encrypted = false;
char encryption_verification[ENCRYPTION_SAMPLE_SIZE];
@@ -66,6 +70,36 @@ static void evp_error(void);
#ifndef FRONTEND
/*
+ * Report space needed for our shared memory area
+ */
+Size
+EncryptionShmemSize(void)
+{
+ return sizeof(ShmemEncryptionKey);
+}
+
+/*
+ * Initialize our shared memory area
+ */
+void
+EncryptionShmemInit(void)
+{
+ bool found;
+
+ encryption_key_shmem = ShmemInitStruct("Cluster Encryption Key",
+ EncryptionShmemSize(),
+ &found);
+ if (!IsUnderPostmaster)
+ {
+ Assert(!found);
+
+ encryption_key_shmem->initialized = false;
+ }
+ else
+ Assert(found);
+}
+
+/*
* Read encryption key in hexadecimal form from stdin and store it in
* encryption_key variable.
*/
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index d7d733530f..771bf30c63 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -147,6 +147,7 @@ CreateSharedMemoryAndSemaphores(int port)
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, AsyncShmemSize());
+ size = add_size(size, EncryptionShmemSize());
#ifdef EXEC_BACKEND
size = add_size(size, ShmemBackendArraySize());
#endif
@@ -263,6 +264,7 @@ CreateSharedMemoryAndSemaphores(int port)
BTreeShmemInit();
SyncScanShmemInit();
AsyncShmemInit();
+ EncryptionShmemInit();
#ifdef EXEC_BACKEND
diff --git a/src/bin/Makefile b/src/bin/Makefile
index 903e58121f..b05a8bbb4e 100644
--- a/src/bin/Makefile
+++ b/src/bin/Makefile
@@ -22,6 +22,7 @@ SUBDIRS = \
pg_controldata \
pg_ctl \
pg_dump \
+ pg_keytool \
pg_resetwal \
pg_rewind \
pg_test_fsync \
diff --git a/src/bin/pg_keytool/.gitignore b/src/bin/pg_keytool/.gitignore
new file mode 100644
index 0000000000..249876a6ed
--- /dev/null
+++ b/src/bin/pg_keytool/.gitignore
@@ -0,0 +1 @@
+/pg_keytool
diff --git a/src/bin/pg_keytool/Makefile b/src/bin/pg_keytool/Makefile
new file mode 100644
index 0000000000..6da0631f32
--- /dev/null
+++ b/src/bin/pg_keytool/Makefile
@@ -0,0 +1,31 @@
+# src/bin/pg_keysetup/Makefile
+
+PGFILEDESC = "pg_keytool - handle cluster encryption key"
+PGAPPICON=win32
+
+subdir = src/bin/pg_keytool
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = pg_keytool.o $(RMGRDESCOBJS) $(WIN32RES)
+
+override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
+override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
+
+all: pg_keytool
+
+pg_keytool: $(OBJS) | submake-libpgport
+ $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+install: all installdirs
+ $(INSTALL_PROGRAM) pg_keytool$(X) '$(DESTDIR)$(bindir)/pg_keytool$(X)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(bindir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(bindir)/pg_keytool$(X)'
+
+clean distclean maintainer-clean:
+ rm -f pg_keytool$(X) $(OBJS) encryption.c
diff --git a/src/bin/pg_keytool/pg_keytool.c b/src/bin/pg_keytool/pg_keytool.c
new file mode 100644
index 0000000000..322625da41
--- /dev/null
+++ b/src/bin/pg_keytool/pg_keytool.c
@@ -0,0 +1,199 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_keytool.c - Handle cluster encryption key.
+ *
+ * Copyright (c) 2013-2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/bin/pg_keytool/pg_keytool.c
+ *-------------------------------------------------------------------------
+ */
+/*
+ * TODO Adopt the new frontend logging API, after some things are clarified:
+ * https://www.postgresql.org/message-id/1939.1560773970%40localhost
+ */
+#define FRONTEND 1
+#include "postgres.h"
+
+#include <dirent.h>
+#include <unistd.h>
+
+#include "common/fe_memutils.h"
+#include "common/logging.h"
+#include "fe_utils/encryption.h"
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "libpq/pqcomm.h"
+#include "port/pg_crc32c.h"
+#include "storage/encryption.h"
+#include "getopt_long.h"
+
+#ifdef USE_ENCRYPTION
+static const char *progname;
+
+unsigned char encryption_key[ENCRYPTION_KEY_LENGTH];
+
+static void
+usage(const char *progname)
+{
+ const char *env;
+
+ printf(_("%s is a tool to handle cluster encryption key.\n\n"),
+ progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION]...\n"), progname);
+ printf(_("\nOptions:\n"));
+ /* Display default host */
+ env = getenv("PGHOST");
+ printf(_(" -h, --host=HOSTNAME database server host or socket directory (default: \"%s\")\n"),
+ env ? env : _("local socket"));
+ /* Display default port */
+ env = getenv("PGPORT");
+ printf(_(" -p, --port=PORT database server port (default: \"%s\")\n"),
+ env ? env : DEF_PGPORT_STR);
+ printf(_(" -s, send output to database server\n"));
+ printf(_(" -?, --help show this help, then exit\n\n"));
+ printf(_("Key is read from stdin and sent either to stdout or to PostgreSQL server being started\n"));
+}
+#endif /* USE_ENCRYPTION */
+
+int
+main(int argc, char **argv)
+{
+/*
+ * If no encryption library is linked, let the utility fail immediately. It'd
+ * be weird if we reported incorrect usage just to say later that no useful
+ * work can be done anyway.
+ */
+#ifdef USE_ENCRYPTION
+ int c;
+ char *host = NULL;
+ char *port_str = NULL;
+ bool to_server = false;
+ int i, n;
+ int optindex;
+ char key_chars[ENCRYPTION_KEY_CHARS];
+
+ static struct option long_options[] =
+ {
+ {"pgdata", required_argument, NULL, 'D'},
+ {"host", required_argument, NULL, 'h'},
+ {"port", required_argument, NULL, 'p'},
+ {NULL, 0, NULL, 0}
+ };
+
+ pg_logging_init(argv[0]);
+ progname = get_progname(argv[0]);
+
+ if (argc > 1)
+ {
+ if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+ {
+ usage(progname);
+ exit(0);
+ }
+ if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
+ {
+ puts("pg_keytool (PostgreSQL) " PG_VERSION);
+ exit(0);
+ }
+ }
+
+ while ((c = getopt_long(argc, argv, "h:p:s",
+ long_options, &optindex)) != -1)
+ {
+ switch (c)
+ {
+ case 'h':
+ host = pg_strdup(optarg);
+ break;
+
+ case 'p':
+ port_str = pg_strdup(optarg);
+ break;
+
+ case 's':
+ to_server = true;
+ break;
+
+ case '?':
+ /* Actual help option given */
+ if (strcmp(argv[optind - 1], "-?") == 0)
+ {
+ usage(progname);
+ exit(EXIT_SUCCESS);
+ }
+
+ default:
+ pg_log_error("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+ }
+
+ /* Complain if any arguments remain */
+ if (optind < argc)
+ {
+ fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"),
+ progname, argv[optind]);
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+
+ if ((host || port_str) && !to_server)
+ {
+ pg_log_error("host and port can only be passed along with the -s option");
+ exit(1);
+ }
+
+ /* Read the key. */
+ n = 0;
+ /* Key length in characters (two characters per hexadecimal digit) */
+ while ((c = getchar()) != EOF && c != '\n')
+ {
+ if (n >= ENCRYPTION_KEY_CHARS)
+ {
+ pg_log_error("The key is too long");
+ exit(EXIT_FAILURE);
+ }
+
+ key_chars[n++] = c;
+ }
+
+ if (n < ENCRYPTION_KEY_CHARS)
+ {
+ pg_log_error("The key is too short");
+ exit(EXIT_FAILURE);
+ }
+
+ for (i = 0; i < ENCRYPTION_KEY_LENGTH; i++)
+ {
+ if (sscanf(key_chars + 2 * i, "%2hhx", encryption_key + i) == 0)
+ {
+ pg_log_error("Invalid character in encryption key at position %d",
+ 2 * i);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /*
+ * Send the encryption key either to stdout or to server.
+ */
+ if (!to_server)
+ {
+ for (i = 0; i < ENCRYPTION_KEY_LENGTH; i++)
+ printf("%.2x", encryption_key[i]);
+ printf("\n");
+ }
+ else
+ {
+ if (!send_key_to_postmaster(host, port_str, encryption_key))
+ pg_log_error("could not send encryption key to server");
+ }
+
+#else
+ pg_log_fatal(ENCRYPTION_NOT_SUPPORTED_MSG);
+ exit(EXIT_FAILURE);
+#endif /* USE_ENCRYPTION */
+ return 0;
+}
diff --git a/src/fe_utils/encryption.c b/src/fe_utils/encryption.c
index dab1435014..ca37c9f373 100644
--- a/src/fe_utils/encryption.c
+++ b/src/fe_utils/encryption.c
@@ -88,3 +88,104 @@ run_encryption_key_command(unsigned char *encryption_key)
pfree(buf);
pclose(fp);
}
+
+/*
+ * Send the contents of encryption_key in the form of special startup packet
+ * to a server that is being started.
+ *
+ * Returns true if we could send the message and false if not, however even
+ * success does not guarantee that server started up - caller should
+ * eventually test server connection himself.
+ *
+ * TODO Windows stuff?
+ */
+bool
+send_key_to_postmaster(const char *host, const char *port,
+ const unsigned char *encryption_Key)
+{
+ const char **keywords = pg_malloc0(3 * sizeof(*keywords));
+ const char **values = pg_malloc0(3 * sizeof(*values));
+ int i;
+ PGconn *conn = NULL;
+ int sock = -1;
+ EncryptionKeyMsg message;
+ int msg_size, packet_size;
+ char *packet = NULL;
+ bool res = true;
+
+/* How many seconds we can wait for the postmaster to receive the key. */
+#define SEND_ENCRYPT_KEY_TIMEOUT 60
+
+ if (host)
+ {
+ keywords[0] = "host";
+ values[0] = host;
+ }
+ keywords[1] = "port";
+ values[1] = port;
+
+ /* Compose the message. */
+ message.encryptionKeyCode = pg_hton32(ENCRYPTION_KEY_MSG_CODE);
+ message.version = 1;
+ memcpy(message.data, encryption_key, ENCRYPTION_KEY_LENGTH);
+ msg_size = offsetof(EncryptionKeyMsg, data) + ENCRYPTION_KEY_LENGTH;
+
+ packet_size = msg_size + 4;
+ packet = (char *) palloc(packet_size);
+ *((int32 *) packet) = pg_hton32(packet_size);
+ memcpy(packet + 4, &message, msg_size);
+
+ /*
+ * Although we don't expect the server to accept regular libpq messages,
+ * we try to get at least a valid socket.
+ */
+ for (i = 0; i < SEND_ENCRYPT_KEY_TIMEOUT + 1; i++)
+ {
+ if (i > 0)
+ /* Sleep for 1 second. */
+ pg_usleep(1000000L);
+
+ if (conn)
+ {
+ PQfinish(conn);
+ conn = NULL;
+ }
+
+ conn = PQconnectStartParams(keywords, values, false);
+ if (conn == NULL)
+ continue;
+
+ sock = PQsocket(conn);
+ /* Cannot send the key if there's no valid socket. */
+ if (sock == -1)
+ continue;
+
+ /* Non-blocking write would only make this simple case tricky. */
+ if (!pg_set_block(sock))
+ continue;
+
+ retry:
+ /*
+ * Send the packet. Here we need to use low level API because the server
+ * does is not fully up so libpq cannot be used properly.
+ */
+ if (send(sock, (char *) packet, packet_size, 0) != packet_size)
+ {
+ if (SOCK_ERRNO == EINTR)
+ /* Interrupted system call - we'll just try again */
+ goto retry;
+ continue;
+ }
+ else
+ /* Success */
+ break;
+ }
+
+ pg_free(keywords);
+ pg_free(values);
+ if (conn)
+ PQfinish(conn);
+ pfree(packet);
+
+ return res;
+}
diff --git a/src/include/fe_utils/encryption.h b/src/include/fe_utils/encryption.h
index 69e3b2f756..7307557ed6 100644
--- a/src/include/fe_utils/encryption.h
+++ b/src/include/fe_utils/encryption.h
@@ -16,3 +16,5 @@
extern char *encryption_key_command;
extern void run_encryption_key_command(unsigned char *encryption_key);
+extern bool send_key_to_postmaster(const char *host, const char *port,
+ const unsigned char *encryption_Key);
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index baf6a4b6c0..09822801c7 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -23,6 +23,8 @@
#endif
#include <netinet/in.h>
+#include "storage/encryption.h"
+
#ifdef HAVE_STRUCT_SOCKADDR_STORAGE
#ifndef HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY
@@ -205,4 +207,26 @@ typedef struct CancelRequestPacket
#define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679)
#define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680)
+/* Client can also send cluster encryption key to the postmaster. */
+#define ENCRYPTION_KEY_MSG_CODE PG_PROTOCOL(1234,5681)
+
+/*
+ * Message containing the encryption key, received by postmaster during
+ * startup.
+ *
+ * TODO Consider adding extension identifier and key_length field so that
+ * postmaster can also receove extension-specific keys. Extension that needs a
+ * key would call a function from _PG_init() that tells postmaster where to
+ * store the key and how long the key is. (Should also postgres in standalone
+ * mode accept keys for extensions?)
+ */
+typedef struct EncryptionKeyMsg
+{
+ /* Note this integer field is stored in network byte order! */
+ MsgType encryptionKeyCode; /* code to identify a key message */
+
+ unsigned char version; /* message format version. */
+ char data[ENCRYPTION_KEY_LENGTH]; /* the key itself. */
+} EncryptionKeyMsg;
+
#endif /* PQCOMM_H */
diff --git a/src/include/storage/encryption.h b/src/include/storage/encryption.h
index 8479b76a34..4f7b96d4f3 100644
--- a/src/include/storage/encryption.h
+++ b/src/include/storage/encryption.h
@@ -60,6 +60,29 @@ typedef enum CipherKind
/* Key to encrypt / decrypt data. */
extern unsigned char encryption_key[];
+#ifndef FRONTEND
+/*
+ * Space for the encryption key in shared memory. Backend that receives the
+ * key during startup stores it here so postmaster can eventually take a local
+ * copy.
+ *
+ * We cannot protect the "initialized" field with a spinlock because the data
+ * will be accessed by postmaster, but it should not be necessary for bool
+ * type. Furthermore, synchronization is not really critical here, see
+ * processEncryptionKey().
+ */
+typedef struct ShmemEncryptionKey
+{
+ char data[ENCRYPTION_KEY_LENGTH]; /* the key */
+ bool initialized; /* received the key? */
+} ShmemEncryptionKey;
+
+/*
+ * Encryption key in the shared memory.
+ */
+extern ShmemEncryptionKey *encryption_key_shmem;
+#endif /* FRONTEND */
+
/*
* The encrypted data is a series of blocks of size
* ENCRYPTION_BLOCK. Currently we use the EVP_aes_256_xts implementation. Make
@@ -86,6 +109,9 @@ extern char encryption_verification[];
extern bool encryption_setup_done;
#ifndef FRONTEND
+extern Size EncryptionShmemSize(void);
+extern void EncryptionShmemInit(void);
+
typedef int (*read_encryption_key_cb) (void);
extern void read_encryption_key(read_encryption_key_cb read_char);
#endif /* FRONTEND */
--
2.13.7
--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
filename=v04-0004-Minor-refactoring-of-pg_ctl.c.patch
view thread (427+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: [PATCH 03/17] Teach postmaster to accept encryption key
In-Reply-To: <no-message-id-1881959@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox