mirror of https://github.com/postgres/postgres
This can change the key that encrypts the data encryption keys used for cluster file encryption. Discussion: https://postgr.es/m/20201202213814.GG20285@momjian.us Backpatch-through: masterpull/59/head
parent
f234899353
commit
62afb42a7f
@ -0,0 +1,186 @@ |
|||||||
|
<!-- |
||||||
|
doc/src/sgml/ref/pg_alterckey.sgml |
||||||
|
PostgreSQL documentation |
||||||
|
--> |
||||||
|
|
||||||
|
<refentry id="app-pg_alterckey"> |
||||||
|
<indexterm zone="app-pg_alterckey"> |
||||||
|
<primary>pg_alterckey</primary> |
||||||
|
</indexterm> |
||||||
|
|
||||||
|
<refmeta> |
||||||
|
<refentrytitle><application>pg_alterckey</application></refentrytitle> |
||||||
|
<manvolnum>1</manvolnum> |
||||||
|
<refmiscinfo>Application</refmiscinfo> |
||||||
|
</refmeta> |
||||||
|
|
||||||
|
<refnamediv> |
||||||
|
<refname>pg_alterckey</refname> |
||||||
|
<refpurpose>alter the <productname>PostgreSQL</productname> cluster key</refpurpose> |
||||||
|
</refnamediv> |
||||||
|
|
||||||
|
<refsynopsisdiv> |
||||||
|
<cmdsynopsis> |
||||||
|
<command>pg_alterckey</command> |
||||||
|
<group choice="opt"> |
||||||
|
<arg choice="plain"><option>-R</option></arg> |
||||||
|
<arg choice="plain"><option>--authprompt</option></arg> |
||||||
|
</group> |
||||||
|
<replaceable class="parameter">old_cluster_key_command</replaceable> |
||||||
|
<replaceable class="parameter">new_cluster_key_command</replaceable> |
||||||
|
<group choice="opt"> |
||||||
|
<group choice="opt"> |
||||||
|
<arg choice="plain"><option>-D</option></arg> |
||||||
|
<arg choice="plain"><option>--pgdata</option></arg> |
||||||
|
</group> |
||||||
|
<replaceable class="parameter">datadir</replaceable> |
||||||
|
</group> |
||||||
|
</cmdsynopsis> |
||||||
|
|
||||||
|
<cmdsynopsis> |
||||||
|
<command>pg_alterckey</command> |
||||||
|
<group choice="opt"> |
||||||
|
<arg choice="plain"><option>-R</option></arg> |
||||||
|
<arg choice="plain"><option>--authprompt</option></arg> |
||||||
|
</group> |
||||||
|
<group choice="plain"> |
||||||
|
<arg choice="plain"><option>-r</option></arg> |
||||||
|
<arg choice="plain"><option>--repair</option></arg> |
||||||
|
</group> |
||||||
|
<group choice="opt"> |
||||||
|
<group choice="opt"> |
||||||
|
<arg choice="plain"><option>-D</option></arg> |
||||||
|
<arg choice="plain"><option>--pgdata</option></arg> |
||||||
|
</group> |
||||||
|
<replaceable class="parameter">datadir</replaceable> |
||||||
|
</group> |
||||||
|
</cmdsynopsis> |
||||||
|
</refsynopsisdiv> |
||||||
|
|
||||||
|
<refsect1 id="r1-app-pg_alterckey-1"> |
||||||
|
<title>Description</title> |
||||||
|
<para> |
||||||
|
<command>pg_alterckey</command> alters the cluster key used |
||||||
|
for cluster file encryption. The cluster key is initially set |
||||||
|
during <xref linkend="app-initdb"/>. The command can be run while the |
||||||
|
server is running or stopped. The new password must be used the next |
||||||
|
time the server is started. |
||||||
|
</para> |
||||||
|
|
||||||
|
<para> |
||||||
|
Technically, <command>pg_alterckey</command> changes the key |
||||||
|
encryption key (<acronym>KEK</acronym>) which encrypts the data |
||||||
|
encryption keys; it does not change the data encryption keys. It does |
||||||
|
this by decrypting each data encryption key using the <replaceable |
||||||
|
class="parameter">old_cluster_key_command</replaceable>, |
||||||
|
re-encrypting it using the <replaceable |
||||||
|
class="parameter">new_cluster_key_command</replaceable>, and |
||||||
|
then writes the result back to the cluster directory. |
||||||
|
</para> |
||||||
|
|
||||||
|
<para> |
||||||
|
See the <xref linkend="app-initdb"/> documentation for how to define |
||||||
|
the old and new passphrase commands. You can use different executables |
||||||
|
for these commands, or you can use the same executable with different |
||||||
|
arguments to specify retrieval of the old or new key. |
||||||
|
</para> |
||||||
|
|
||||||
|
<para> |
||||||
|
When started, <command>pg_alterckey</command> repairs any files that |
||||||
|
remain from previous <command>pg_alterckey</command> failures before |
||||||
|
altering the cluster key. To perform only the repair task, |
||||||
|
use the <option>--repair</option> option. The server will not start |
||||||
|
if repair is needed, though a running server is unaffected by an |
||||||
|
unrepaired cluster key configuration. |
||||||
|
</para> |
||||||
|
|
||||||
|
<para> |
||||||
|
You can specify the data directory on the command line, or use |
||||||
|
the environment variable <envar>PGDATA</envar>. |
||||||
|
</para> |
||||||
|
</refsect1> |
||||||
|
|
||||||
|
<refsect1> |
||||||
|
<title>Options</title> |
||||||
|
|
||||||
|
<para> |
||||||
|
<varlistentry> |
||||||
|
<term><option>-R</option></term> |
||||||
|
<term><option>--authprompt</option></term> |
||||||
|
<listitem> |
||||||
|
<para> |
||||||
|
Allows the <option>old_cluster_key_command</option> and |
||||||
|
<option>new_cluster_key_command</option> commands |
||||||
|
to prompt for a passphrase or PIN. |
||||||
|
</para> |
||||||
|
</listitem> |
||||||
|
</varlistentry> |
||||||
|
</variablelist> |
||||||
|
</para> |
||||||
|
|
||||||
|
<para> |
||||||
|
Other options: |
||||||
|
|
||||||
|
<variablelist> |
||||||
|
<varlistentry> |
||||||
|
<term><option>-V</option></term> |
||||||
|
<term><option>--version</option></term> |
||||||
|
<listitem> |
||||||
|
<para> |
||||||
|
Print the <application>pg_alterckey</application> version and exit. |
||||||
|
</para> |
||||||
|
</listitem> |
||||||
|
</varlistentry> |
||||||
|
|
||||||
|
<varlistentry> |
||||||
|
<term><option>-?</option></term> |
||||||
|
<term><option>--help</option></term> |
||||||
|
<listitem> |
||||||
|
<para> |
||||||
|
Show help about <application>pg_alterckey</application> command line |
||||||
|
arguments, and exit. |
||||||
|
</para> |
||||||
|
</listitem> |
||||||
|
</varlistentry> |
||||||
|
|
||||||
|
</variablelist> |
||||||
|
</para> |
||||||
|
|
||||||
|
</refsect1> |
||||||
|
|
||||||
|
<refsect1> |
||||||
|
<title>Environment</title> |
||||||
|
|
||||||
|
<variablelist> |
||||||
|
<varlistentry> |
||||||
|
<term><envar>PGDATA</envar></term> |
||||||
|
|
||||||
|
<listitem> |
||||||
|
<para> |
||||||
|
Default data directory location |
||||||
|
</para> |
||||||
|
</listitem> |
||||||
|
</varlistentry> |
||||||
|
|
||||||
|
<varlistentry> |
||||||
|
<term><envar>PG_COLOR</envar></term> |
||||||
|
<listitem> |
||||||
|
<para> |
||||||
|
Specifies whether to use color in diagnostic messages. Possible values |
||||||
|
are <literal>always</literal>, <literal>auto</literal> and |
||||||
|
<literal>never</literal>. |
||||||
|
</para> |
||||||
|
</listitem> |
||||||
|
</varlistentry> |
||||||
|
|
||||||
|
</refsect1> |
||||||
|
|
||||||
|
<refsect1> |
||||||
|
<title>See Also</title> |
||||||
|
|
||||||
|
<simplelist type="inline"> |
||||||
|
<member><xref linkend="app-initdb"/></member> |
||||||
|
</simplelist> |
||||||
|
</refsect1> |
||||||
|
|
||||||
|
</refentry> |
||||||
@ -0,0 +1 @@ |
|||||||
|
/pg_alterckey |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Makefile for src/bin/pg_alterckey
|
||||||
|
#
|
||||||
|
# Copyright (c) 1998-2020, PostgreSQL Global Development Group
|
||||||
|
#
|
||||||
|
# src/bin/pg_alterckey/Makefile
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
PGFILEDESC = "pg_alterckey - alter the cluster key"
|
||||||
|
PGAPPICON=win32
|
||||||
|
|
||||||
|
subdir = src/bin/pg_alterckey
|
||||||
|
top_builddir = ../../..
|
||||||
|
include $(top_builddir)/src/Makefile.global |
||||||
|
|
||||||
|
OBJS = \
|
||||||
|
$(WIN32RES) \
|
||||||
|
pg_alterckey.o
|
||||||
|
|
||||||
|
all: pg_alterckey |
||||||
|
|
||||||
|
pg_alterckey: $(OBJS) | submake-libpgport |
||||||
|
$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
|
||||||
|
|
||||||
|
install: all installdirs |
||||||
|
$(INSTALL_PROGRAM) pg_alterckey$(X) '$(DESTDIR)$(bindir)/pg_alterckey$(X)'
|
||||||
|
|
||||||
|
installdirs: |
||||||
|
$(MKDIR_P) '$(DESTDIR)$(bindir)'
|
||||||
|
|
||||||
|
uninstall: |
||||||
|
rm -f '$(DESTDIR)$(bindir)/pg_alterckey$(X)'
|
||||||
|
|
||||||
|
clean distclean maintainer-clean: |
||||||
|
rm -f pg_alterckey$(X) $(OBJS)
|
||||||
|
rm -rf tmp_check
|
||||||
|
|
||||||
|
check: |
||||||
|
$(prove_check)
|
||||||
|
|
||||||
|
installcheck: |
||||||
|
$(prove_installcheck)
|
||||||
@ -0,0 +1,693 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* pg_alterckey.c |
||||||
|
* A utility to change the cluster key (key encryption key, KEK) |
||||||
|
* used for cluster file encryption. |
||||||
|
* |
||||||
|
* The theory of operation is fairly simple: |
||||||
|
* 1. Create lock file |
||||||
|
* 2. Retrieve current and new cluster key using the supplied |
||||||
|
* commands. |
||||||
|
* 3. Revert any failed alter operation. |
||||||
|
* 4. Create a temporary directory in PGDATA |
||||||
|
* 5. For each data encryption key in the pg_cryptokeys directory, |
||||||
|
* decrypt it with the old cluster key and re-encrypt it |
||||||
|
* with the new cluster key. |
||||||
|
* 6. Make the temporary directory the new pg_cryptokeys directory. |
||||||
|
* 7. Remove lock file |
||||||
|
* |
||||||
|
* |
||||||
|
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group |
||||||
|
* Portions Copyright (c) 1994, Regents of the University of California |
||||||
|
* |
||||||
|
* src/bin/pg_alterckey/pg_alterckey.c |
||||||
|
* |
||||||
|
*------------------------------------------------------------------------- |
||||||
|
*/ |
||||||
|
|
||||||
|
|
||||||
|
#define FRONTEND 1 |
||||||
|
|
||||||
|
#include "postgres_fe.h" |
||||||
|
|
||||||
|
#include <signal.h> |
||||||
|
#include <unistd.h> |
||||||
|
#include <sys/stat.h> |
||||||
|
|
||||||
|
#include "common/file_perm.h" |
||||||
|
#include "common/file_utils.h" |
||||||
|
#include "common/hex_decode.h" |
||||||
|
#include "common/restricted_token.h" |
||||||
|
#include "crypto/kmgr.h" |
||||||
|
#include "common/logging.h" |
||||||
|
#include "getopt_long.h" |
||||||
|
#include "pg_getopt.h" |
||||||
|
|
||||||
|
typedef enum { |
||||||
|
SUCCESS_EXIT = 0, |
||||||
|
ERROR_EXIT, |
||||||
|
RMDIR_EXIT, |
||||||
|
REPAIR_EXIT |
||||||
|
} exit_action; |
||||||
|
|
||||||
|
static int lock_fd = -1; |
||||||
|
static bool pass_terminal_fd = false; |
||||||
|
int terminal_fd = -1; |
||||||
|
static bool repair_mode = false; |
||||||
|
static char *old_cluster_key_cmd = NULL, |
||||||
|
*new_cluster_key_cmd = NULL; |
||||||
|
static char old_cluster_key[KMGR_CLUSTER_KEY_LEN], |
||||||
|
new_cluster_key[KMGR_CLUSTER_KEY_LEN]; |
||||||
|
static CryptoKey in_key, data_key, out_key; |
||||||
|
static char top_path[MAXPGPATH], pid_path[MAXPGPATH], live_path[MAXPGPATH], |
||||||
|
new_path[MAXPGPATH], old_path[MAXPGPATH]; |
||||||
|
|
||||||
|
static char *DataDir = NULL; |
||||||
|
static const char *progname; |
||||||
|
|
||||||
|
static void create_lockfile(void); |
||||||
|
static void recover_failure(void); |
||||||
|
static void retrieve_cluster_keys(void); |
||||||
|
static void bzero_keys_and_exit(exit_action action); |
||||||
|
static void reencrypt_data_keys(void); |
||||||
|
static void install_new_keys(void); |
||||||
|
|
||||||
|
static void |
||||||
|
usage(const char *progname) |
||||||
|
{ |
||||||
|
printf(_("%s changes the cluster key of a PostgreSQL database cluster.\n\n"), progname); |
||||||
|
printf(_("Usage:\n")); |
||||||
|
printf(_(" %s [OPTION] old_cluster_key_command new_cluster_key_command [DATADIR]\n"), progname); |
||||||
|
printf(_(" %s [repair_option] [DATADIR]\n"), progname); |
||||||
|
printf(_("\nOptions:\n")); |
||||||
|
printf(_(" -R, --authprompt prompt for a passphrase or PIN\n")); |
||||||
|
printf(_(" [-D, --pgdata=]DATADIR data directory\n")); |
||||||
|
printf(_(" -V, --version output version information, then exit\n")); |
||||||
|
printf(_(" -?, --help show this help, then exit\n")); |
||||||
|
printf(_("\nRepair options:\n")); |
||||||
|
printf(_(" -r, --repair repair previous failure\n")); |
||||||
|
printf(_("\nIf no data directory (DATADIR) is specified, " |
||||||
|
"the environment variable PGDATA\nis used.\n\n")); |
||||||
|
printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT); |
||||||
|
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
int |
||||||
|
main(int argc, char *argv[]) |
||||||
|
{ |
||||||
|
static struct option long_options1[] = { |
||||||
|
{"authprompt", required_argument, NULL, 'R'}, |
||||||
|
{"repair", required_argument, NULL, 'r'}, |
||||||
|
{NULL, 0, NULL, 0} |
||||||
|
}; |
||||||
|
|
||||||
|
static struct option long_options2[] = { |
||||||
|
{"pgdata", required_argument, NULL, 'D'}, |
||||||
|
{NULL, 0, NULL, 0} |
||||||
|
}; |
||||||
|
|
||||||
|
int c; |
||||||
|
|
||||||
|
pg_logging_init(argv[0]); |
||||||
|
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_alterckey")); |
||||||
|
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_alterckey (PostgreSQL) " PG_VERSION); |
||||||
|
exit(0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* check for -r/-R */ |
||||||
|
while ((c = getopt_long(argc, argv, "rR", long_options1, NULL)) != -1) |
||||||
|
{ |
||||||
|
switch (c) |
||||||
|
{ |
||||||
|
case 'r': |
||||||
|
repair_mode = true; |
||||||
|
break; |
||||||
|
|
||||||
|
case 'R': |
||||||
|
pass_terminal_fd = true; |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!repair_mode) |
||||||
|
{ |
||||||
|
/* get cluster key commands */ |
||||||
|
if (optind < argc) |
||||||
|
old_cluster_key_cmd = argv[optind++]; |
||||||
|
else |
||||||
|
{ |
||||||
|
pg_log_error("missing old_cluster_key_command"); |
||||||
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), |
||||||
|
progname); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
if (optind < argc) |
||||||
|
new_cluster_key_cmd = argv[optind++]; |
||||||
|
else |
||||||
|
{ |
||||||
|
pg_log_error("missing new_cluster_key_command"); |
||||||
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), |
||||||
|
progname); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* check for datadir */ |
||||||
|
argc -= optind; |
||||||
|
argv += optind; |
||||||
|
|
||||||
|
while ((c = getopt_long(argc, argv, "D:", long_options2, NULL)) != -1) |
||||||
|
{ |
||||||
|
switch (c) |
||||||
|
{ |
||||||
|
case 'D': |
||||||
|
DataDir = optarg; |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (DataDir == NULL) |
||||||
|
{ |
||||||
|
if (optind < argc) |
||||||
|
DataDir = argv[optind++]; |
||||||
|
else |
||||||
|
DataDir = getenv("PGDATA"); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Disallow running as root because we create directories in PGDATA |
||||||
|
*/ |
||||||
|
#ifndef WIN32 |
||||||
|
if (geteuid() == 0) |
||||||
|
{ |
||||||
|
pg_log_error("%s: cannot be run as root\n" |
||||||
|
"Please log in (using, e.g., \"su\") as the " |
||||||
|
"(unprivileged) user that will\n" |
||||||
|
"own the server process.\n", |
||||||
|
progname); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
get_restricted_token(); |
||||||
|
|
||||||
|
/* Set mask based on PGDATA permissions */ |
||||||
|
if (!GetDataDirectoryCreatePerm(DataDir)) |
||||||
|
{ |
||||||
|
pg_log_error("could not read permissions of directory \"%s\": %m", |
||||||
|
DataDir); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
umask(pg_mode_mask); |
||||||
|
|
||||||
|
snprintf(top_path, sizeof(top_path), "%s/%s", DataDir, KMGR_DIR); |
||||||
|
snprintf(pid_path, sizeof(pid_path), "%s/%s", DataDir, KMGR_DIR_PID); |
||||||
|
snprintf(live_path, sizeof(live_path), "%s/%s", DataDir, LIVE_KMGR_DIR); |
||||||
|
snprintf(new_path, sizeof(new_path), "%s/%s", DataDir, NEW_KMGR_DIR); |
||||||
|
snprintf(old_path, sizeof(old_path), "%s/%s", DataDir, OLD_KMGR_DIR); |
||||||
|
|
||||||
|
/* Complain if any arguments remain */ |
||||||
|
if (optind < argc) |
||||||
|
{ |
||||||
|
pg_log_error("too many command-line arguments (first is \"%s\")", |
||||||
|
argv[optind]); |
||||||
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), |
||||||
|
progname); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
if (DataDir == NULL) |
||||||
|
{ |
||||||
|
pg_log_error("no data directory specified"); |
||||||
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
create_lockfile(); |
||||||
|
|
||||||
|
recover_failure(); |
||||||
|
|
||||||
|
if (!repair_mode) |
||||||
|
{ |
||||||
|
retrieve_cluster_keys(); |
||||||
|
reencrypt_data_keys(); |
||||||
|
install_new_keys(); |
||||||
|
} |
||||||
|
|
||||||
|
#ifndef WIN32 |
||||||
|
/* remove file system reference to file */ |
||||||
|
if (unlink(pid_path) < 0) |
||||||
|
{ |
||||||
|
pg_log_error("could not delete lock file \"%s\": %m", KMGR_DIR_PID); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
close (lock_fd); |
||||||
|
|
||||||
|
bzero_keys_and_exit(SUCCESS_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
/* This prevents almost all cases of concurrent access */ |
||||||
|
void |
||||||
|
create_lockfile(void) |
||||||
|
{ |
||||||
|
struct stat buffer; |
||||||
|
char lock_pid_str[20]; |
||||||
|
|
||||||
|
if (stat(top_path, &buffer) != 0 || !S_ISDIR(buffer.st_mode)) |
||||||
|
{ |
||||||
|
pg_log_error("cluster file encryption directory \"%s\" is missing; is it enabled?", KMGR_DIR_PID); |
||||||
|
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
/* Does a lockfile exist? */ |
||||||
|
if ((lock_fd = open(pid_path, O_RDONLY, 0)) != -1) |
||||||
|
{ |
||||||
|
int lock_pid; |
||||||
|
int len; |
||||||
|
|
||||||
|
/* read the PID */ |
||||||
|
if ((len = read(lock_fd, lock_pid_str, sizeof(lock_pid_str) - 1)) == 0) |
||||||
|
{ |
||||||
|
pg_log_error("cannot read pid from lock file \"%s\": %m", KMGR_DIR_PID); |
||||||
|
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
lock_pid_str[len] = '\0'; |
||||||
|
|
||||||
|
if ((lock_pid = atoi(lock_pid_str)) == 0) |
||||||
|
{ |
||||||
|
pg_log_error("invalid pid in lock file \"%s\": %m", KMGR_DIR_PID); |
||||||
|
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
/* Is the PID running? */ |
||||||
|
if (kill(lock_pid, 0) == 0) |
||||||
|
{ |
||||||
|
pg_log_error("active process %d currently holds a lock on this operation, recorded in \"%s\"", |
||||||
|
lock_pid, KMGR_DIR_PID); |
||||||
|
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
close(lock_fd); |
||||||
|
|
||||||
|
if (repair_mode) |
||||||
|
printf("old lock file removed\n"); |
||||||
|
|
||||||
|
/*
|
||||||
|
* pid is no longer running, so remove the lock file. |
||||||
|
* This is not 100% safe from concurrent access, e.g.: |
||||||
|
* |
||||||
|
* process 1 exits and leaves stale lock file |
||||||
|
* process 2 checks stale lock file of process 1 |
||||||
|
* process 3 checks stale lock file of process 1 |
||||||
|
* process 2 remove the lock file of process 1 |
||||||
|
* process 4 creates a lock file |
||||||
|
* process 3 remove the lock file of process 4 |
||||||
|
* process 5 creates a lock file |
||||||
|
* |
||||||
|
* The sleep(2) helps with this since it reduces the likelihood |
||||||
|
* a process that did an unlock will interfere with another unlock |
||||||
|
* process. We could ask users to remove the lock, but that seems |
||||||
|
* even more error-prone, especially since this might happen |
||||||
|
* on server start. Many PG tools seem to have problems with |
||||||
|
* concurrent access. |
||||||
|
*/ |
||||||
|
unlink(pid_path); |
||||||
|
|
||||||
|
/* Sleep to reduce the likelihood of concurrent unlink */ |
||||||
|
sleep(2); |
||||||
|
} |
||||||
|
|
||||||
|
/* Create our own lockfile? */ |
||||||
|
lock_fd = open(pid_path, O_RDWR | O_CREAT | O_EXCL |
||||||
|
#ifdef WIN32 |
||||||
|
/* delete on close */ |
||||||
|
| O_TEMPORARY |
||||||
|
#endif |
||||||
|
, pg_file_create_mode); |
||||||
|
|
||||||
|
if (lock_fd == -1) |
||||||
|
{ |
||||||
|
if (errno == EEXIST) |
||||||
|
pg_log_error("an active process currently holds a lock on this operation, recorded in \"%s\"", |
||||||
|
KMGR_DIR_PID); |
||||||
|
else |
||||||
|
pg_log_error("unable to create lock file \"%s\": %m", KMGR_DIR_PID); |
||||||
|
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
snprintf(lock_pid_str, sizeof(lock_pid_str), "%d\n", getpid()); |
||||||
|
if (write(lock_fd, lock_pid_str, strlen(lock_pid_str)) != strlen(lock_pid_str)) |
||||||
|
{ |
||||||
|
pg_log_error("could not write pid to lock file \"%s\": %m", KMGR_DIR_PID); |
||||||
|
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* recover_failure |
||||||
|
* |
||||||
|
* A previous pg_alterckey might have failed, so it might need recovery. |
||||||
|
* The normal operation is: |
||||||
|
* 1. reencrypt LIVE_KMGR_DIR -> NEW_KMGR_DIR |
||||||
|
* 2. rename KMGR_DIR -> OLD_KMGR_DIR |
||||||
|
* 3. rename NEW_KMGR_DIR -> LIVE_KMGR_DIR |
||||||
|
* remove OLD_KMGR_DIR |
||||||
|
* |
||||||
|
* There are eight possible directory configurations: |
||||||
|
* |
||||||
|
* LIVE_KMGR_DIR NEW_KMGR_DIR OLD_KMGR_DIR |
||||||
|
* |
||||||
|
* Normal: |
||||||
|
* 0. normal X
|
||||||
|
* 1. remove new X X
|
||||||
|
* 2. install new X X |
||||||
|
* 3. remove old X X |
||||||
|
* |
||||||
|
* Abnormal: |
||||||
|
* fatal
|
||||||
|
* restore old X |
||||||
|
* install new X
|
||||||
|
* remove old and new X X X |
||||||
|
* |
||||||
|
* We don't handle the abnormal cases, just report an error. |
||||||
|
*/ |
||||||
|
static void |
||||||
|
recover_failure(void) |
||||||
|
{ |
||||||
|
struct stat buffer; |
||||||
|
bool is_live, is_new, is_old; |
||||||
|
|
||||||
|
is_live = !stat(live_path, &buffer); |
||||||
|
is_new = !stat(new_path, &buffer); |
||||||
|
is_old = !stat(old_path, &buffer); |
||||||
|
|
||||||
|
/* normal #0 */ |
||||||
|
if (is_live && !is_new && !is_old) |
||||||
|
{ |
||||||
|
if (repair_mode) |
||||||
|
printf("repair unnecessary\n"); |
||||||
|
return; |
||||||
|
} |
||||||
|
/* remove new #1 */ |
||||||
|
else if (is_live && is_new && !is_old) |
||||||
|
{ |
||||||
|
if (!rmtree(new_path, true)) |
||||||
|
{ |
||||||
|
pg_log_error("unable to remove new directory \"%s\": %m", NEW_KMGR_DIR); |
||||||
|
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
printf(_("removed files created during previously aborted alter operation\n")); |
||||||
|
return; |
||||||
|
} |
||||||
|
/* install new #2 */ |
||||||
|
else if (!is_live && is_new && is_old) |
||||||
|
{ |
||||||
|
if (rename(new_path, live_path) != 0) |
||||||
|
{ |
||||||
|
pg_log_error("unable to rename directory \"%s\" to \"%s\": %m", |
||||||
|
NEW_KMGR_DIR, LIVE_KMGR_DIR); |
||||||
|
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
printf(_("Installed new cluster password supplied in previous alter operation\n")); |
||||||
|
return; |
||||||
|
} |
||||||
|
/* remove old #3 */ |
||||||
|
else if (is_live && !is_new && is_old) |
||||||
|
{ |
||||||
|
if (!rmtree(old_path, true)) |
||||||
|
{ |
||||||
|
pg_log_error("unable to remove old directory \"%s\": %m", OLD_KMGR_DIR); |
||||||
|
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
printf(_("Removed old files invalidated during previous alter operation\n")); |
||||||
|
return; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
pg_log_error("cluster file encryption directory \"%s\" is in an abnormal state and cannot be processed", |
||||||
|
KMGR_DIR); |
||||||
|
fprintf(stderr, _("Exiting with no changes made.\n")); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* Retrieve old and new cluster keys */
|
||||||
|
void |
||||||
|
retrieve_cluster_keys() |
||||||
|
{ |
||||||
|
int cluster_key_len; |
||||||
|
char cluster_key_hex[ALLOC_KMGR_CLUSTER_KEY_LEN]; |
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have been asked to pass an open file descriptor to the user |
||||||
|
* terminal to the commands, set one up. |
||||||
|
*/ |
||||||
|
if (pass_terminal_fd) |
||||||
|
{ |
||||||
|
#ifndef WIN32 |
||||||
|
terminal_fd = open("/dev/tty", O_RDWR, 0); |
||||||
|
#else |
||||||
|
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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* Get old key encryption key from the cluster key command */ |
||||||
|
cluster_key_len = kmgr_run_cluster_key_command(old_cluster_key_cmd, |
||||||
|
(char *) cluster_key_hex, |
||||||
|
ALLOC_KMGR_CLUSTER_KEY_LEN, |
||||||
|
live_path); |
||||||
|
if (hex_decode(cluster_key_hex, cluster_key_len, (char *) old_cluster_key) != |
||||||
|
KMGR_CLUSTER_KEY_LEN) |
||||||
|
{ |
||||||
|
pg_log_error("cluster key must be at %d hex bytes", KMGR_CLUSTER_KEY_LEN); |
||||||
|
bzero_keys_and_exit(ERROR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Create new key directory here in case the new cluster key command needs it |
||||||
|
* to exist. |
||||||
|
*/ |
||||||
|
if (mkdir(new_path, pg_dir_create_mode) != 0) |
||||||
|
{ |
||||||
|
pg_log_error("unable to create new cluster key directory \"%s\": %m", NEW_KMGR_DIR); |
||||||
|
bzero_keys_and_exit(ERROR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
/* Get new key */ |
||||||
|
cluster_key_len = kmgr_run_cluster_key_command(new_cluster_key_cmd, |
||||||
|
(char *) cluster_key_hex, |
||||||
|
ALLOC_KMGR_CLUSTER_KEY_LEN, |
||||||
|
live_path); |
||||||
|
if (hex_decode(cluster_key_hex, cluster_key_len, (char *) new_cluster_key) != |
||||||
|
KMGR_CLUSTER_KEY_LEN) |
||||||
|
{ |
||||||
|
pg_log_error("cluster key must be at %d hex bytes", KMGR_CLUSTER_KEY_LEN); |
||||||
|
bzero_keys_and_exit(ERROR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
if (pass_terminal_fd) |
||||||
|
close(terminal_fd); |
||||||
|
|
||||||
|
/* output newline */ |
||||||
|
puts(""); |
||||||
|
|
||||||
|
if (strcmp(old_cluster_key, new_cluster_key) == 0) |
||||||
|
{ |
||||||
|
pg_log_error("cluster keys are identical, exiting\n"); |
||||||
|
bzero_keys_and_exit(RMDIR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/* Decrypt old keys encrypted with old pass phrase and reencrypt with new one */ |
||||||
|
void |
||||||
|
reencrypt_data_keys(void) |
||||||
|
{ |
||||||
|
DIR *dir; |
||||||
|
struct dirent *de; |
||||||
|
PgCipherCtx *old_ctx, *new_ctx; |
||||||
|
|
||||||
|
if ((dir = opendir(live_path)) == NULL) |
||||||
|
{ |
||||||
|
pg_log_error("unable to open live cluster key directory \"%s\": %m", LIVE_KMGR_DIR); |
||||||
|
bzero_keys_and_exit(RMDIR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
old_ctx = pg_cipher_ctx_create(PG_CIPHER_AES_GCM, |
||||||
|
(unsigned char *)old_cluster_key, |
||||||
|
KMGR_CLUSTER_KEY_LEN, true); |
||||||
|
if (!old_ctx) |
||||||
|
pg_log_error("could not initialize encryption context"); |
||||||
|
|
||||||
|
new_ctx = pg_cipher_ctx_create(PG_CIPHER_AES_GCM, |
||||||
|
(unsigned char *)new_cluster_key, |
||||||
|
KMGR_CLUSTER_KEY_LEN, true); |
||||||
|
if (!new_ctx) |
||||||
|
pg_log_error("could not initialize encryption context"); |
||||||
|
|
||||||
|
while ((de = readdir(dir)) != NULL) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* We copy only the numeric files/keys, since there might be encrypted |
||||||
|
* cluster key files in the old directory that only match the old key. |
||||||
|
*/ |
||||||
|
if (strspn(de->d_name, "0123456789") == strlen(de->d_name)) |
||||||
|
{ |
||||||
|
char src_path[MAXPGPATH], dst_path[MAXPGPATH]; |
||||||
|
int src_fd, dst_fd; |
||||||
|
int len; |
||||||
|
uint32 id = strtoul(de->d_name, NULL, 10); |
||||||
|
|
||||||
|
CryptoKeyFilePath(src_path, live_path, id); |
||||||
|
CryptoKeyFilePath(dst_path, new_path, id); |
||||||
|
|
||||||
|
if ((src_fd = open(src_path, O_RDONLY | PG_BINARY, 0)) < 0) |
||||||
|
{ |
||||||
|
pg_log_error("could not open file \"%s\": %m", src_path); |
||||||
|
bzero_keys_and_exit(RMDIR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
if ((dst_fd = open(dst_path, O_RDWR | O_CREAT | O_TRUNC | PG_BINARY, |
||||||
|
pg_file_create_mode)) < 0) |
||||||
|
{ |
||||||
|
pg_log_error("could not open file \"%s\": %m", dst_path); |
||||||
|
bzero_keys_and_exit(RMDIR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
/* Read the source key */ |
||||||
|
len = read(src_fd, &in_key, sizeof(CryptoKey)); |
||||||
|
if (len != sizeof(CryptoKey)) |
||||||
|
{ |
||||||
|
if (len < 0) |
||||||
|
pg_log_error("could read file \"%s\": %m", src_path); |
||||||
|
else |
||||||
|
pg_log_error("could read file \"%s\": read %d of %zu", |
||||||
|
src_path, len, sizeof(CryptoKey)); |
||||||
|
bzero_keys_and_exit(RMDIR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
/* decrypt with old key */ |
||||||
|
if (!kmgr_unwrap_key(old_ctx, &in_key, &data_key)) |
||||||
|
{ |
||||||
|
pg_log_error("incorrect old key specified"); |
||||||
|
bzero_keys_and_exit(RMDIR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
/* encrypt with new key */ |
||||||
|
if (!kmgr_wrap_key(new_ctx, &data_key, &out_key)) |
||||||
|
{ |
||||||
|
pg_log_error("could not encrypt new key"); |
||||||
|
bzero_keys_and_exit(RMDIR_EXIT); |
||||||
|
}
|
||||||
|
|
||||||
|
/* Write to the dest key */ |
||||||
|
len = write(dst_fd, &out_key, sizeof(CryptoKey)); |
||||||
|
if (len != sizeof(CryptoKey)) |
||||||
|
{ |
||||||
|
pg_log_error("could not write fie \"%s\"", dst_path); |
||||||
|
bzero_keys_and_exit(RMDIR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
close(src_fd); |
||||||
|
close(dst_fd); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* The cluster key is correct, free the cipher context */ |
||||||
|
pg_cipher_ctx_free(old_ctx); |
||||||
|
pg_cipher_ctx_free(new_ctx); |
||||||
|
|
||||||
|
closedir(dir); |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
install_new_keys(void) |
||||||
|
{ |
||||||
|
/* add fsyncs? XXX */ |
||||||
|
if (rename(live_path, old_path) != 0) |
||||||
|
{ |
||||||
|
pg_log_error("unable to rename directory \"%s\" to \"%s\": %m", |
||||||
|
LIVE_KMGR_DIR, OLD_KMGR_DIR); |
||||||
|
bzero_keys_and_exit(RMDIR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
if (rename(new_path, live_path) != 0) |
||||||
|
{ |
||||||
|
pg_log_error("unable to rename directory \"%s\" to \"%s\": %m", |
||||||
|
NEW_KMGR_DIR, LIVE_KMGR_DIR); |
||||||
|
bzero_keys_and_exit(REPAIR_EXIT); |
||||||
|
} |
||||||
|
|
||||||
|
if (!rmtree(old_path, true)) |
||||||
|
{ |
||||||
|
pg_log_error("unable to remove old directory \"%s\": %m", OLD_KMGR_DIR); |
||||||
|
bzero_keys_and_exit(REPAIR_EXIT); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
bzero_keys_and_exit(exit_action action) |
||||||
|
{ |
||||||
|
explicit_bzero(old_cluster_key, sizeof(old_cluster_key)); |
||||||
|
explicit_bzero(new_cluster_key, sizeof(new_cluster_key)); |
||||||
|
|
||||||
|
explicit_bzero(&in_key, sizeof(in_key)); |
||||||
|
explicit_bzero(&data_key, sizeof(data_key)); |
||||||
|
explicit_bzero(&out_key, sizeof(out_key)); |
||||||
|
|
||||||
|
if (action == RMDIR_EXIT) |
||||||
|
{ |
||||||
|
if (!rmtree(new_path, true)) |
||||||
|
pg_log_error("unable to remove new directory \"%s\": %m", NEW_KMGR_DIR); |
||||||
|
printf("Re-running pg_alterckey to repair might be needed before the next server start\n"); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
else if (action == REPAIR_EXIT) |
||||||
|
{ |
||||||
|
unlink(pid_path); |
||||||
|
printf("Re-running pg_alterckey to repair might be needed before the next server start\n"); |
||||||
|
} |
||||||
|
|
||||||
|
/* return 0 or 1 */ |
||||||
|
exit(action != SUCCESS_EXIT); |
||||||
|
} |
||||||
Loading…
Reference in new issue