Skip to content

PG-1710 Create helpers for decrypting/encrypting archived WAL #487

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: TDE_REL_17_STABLE
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion contrib/pg_tde/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ __pycache__
/configure~
/log
/results
/src/pg_tde_change_key_provider
/src/bin/pg_tde_archive_decrypt
/src/bin/pg_tde_change_key_provider
/src/bin/pg_tde_restore_encrypt
/t/results
/tmp_check

Expand Down
24 changes: 21 additions & 3 deletions contrib/pg_tde/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,16 @@ src/libkmip/libkmip/src/kmip_bio.o \
src/libkmip/libkmip/src/kmip_locate.o \
src/libkmip/libkmip/src/kmip_memset.o

SCRIPTS_built = src/pg_tde_change_key_provider
SCRIPTS_built = src/bin/pg_tde_archive_decrypt \
src/bin/pg_tde_change_key_provider \
src/bin/pg_tde_restore_encrypt

EXTRA_INSTALL += contrib/pg_buffercache contrib/test_decoding
EXTRA_CLEAN += src/pg_tde_change_key_provider.o
EXTRA_CLEAN += src/bin/pg_tde_archive_decrypt.o \
src/bin/pg_tde_change_key_provider.o \
src/bin/pg_tde_restore_encrypt.o \
xlogreader.c \
xlogreader.o

ifdef USE_PGXS
PG_CONFIG = pg_config
Expand All @@ -71,9 +77,21 @@ endif

override SHLIB_LINK += -lcurl -lcrypto -lssl

src/pg_tde_change_key_provider: src/pg_tde_change_key_provider.o $(top_srcdir)/src/fe_utils/simple_list.o $(top_builddir)/src/libtde/libtde.a
src/bin/pg_tde_change_key_provider: src/bin/pg_tde_change_key_provider.o $(top_srcdir)/src/fe_utils/simple_list.o $(top_builddir)/src/libtde/libtde.a
$(CC) -DFRONTEND $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)

src/bin/pg_tde_archive_decrypt: src/bin/pg_tde_archive_decrypt.o xlogreader.o $(top_srcdir)/src/fe_utils/simple_list.o $(top_builddir)/src/libtde/libtdexlog.a $(top_builddir)/src/libtde/libtde.a
$(CC) -DFRONTEND $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)

src/bin/pg_tde_restore_encrypt: src/bin/pg_tde_restore_encrypt.o xlogreader.o $(top_srcdir)/src/fe_utils/simple_list.o $(top_builddir)/src/libtde/libtdexlog.a $(top_builddir)/src/libtde/libtde.a
$(CC) -DFRONTEND $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)

xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/%
rm -f $@ && $(LN_S) $< .

xlogreader.o: xlogreader.c
$(CC) $(CPPFLAGS) -DFRONTEND -c $< -o $@

# Fetches typedefs list for PostgreSQL core and merges it with typedefs defined in this project.
# https://wiki.postgresql.org/wiki/Running_pgindent_on_non-core_code_or_development_code
update-typedefs:
Expand Down
31 changes: 30 additions & 1 deletion contrib/pg_tde/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ tap_tests = [
't/rotate_key.pl',
't/tde_heap.pl',
't/unlogged_tables.pl',
't/wal_archiving.pl',
't/wal_encrypt.pl',
]

Expand Down Expand Up @@ -151,7 +152,7 @@ pg_tde_frontend = static_library('pg_tde_frontend',
)

pg_tde_change_key_provider_sources = files(
'src/pg_tde_change_key_provider.c',
'src/bin/pg_tde_change_key_provider.c',
)

pg_tde_change_key_provider = executable('pg_tde_change_key_provider',
Expand All @@ -163,3 +164,31 @@ pg_tde_change_key_provider = executable('pg_tde_change_key_provider',
link_with: [pg_tde_frontend]
)
contrib_targets += pg_tde_change_key_provider

pg_tde_archive_decrypt_sources = files(
'src/bin/pg_tde_archive_decrypt.c',
) + xlogreader_sources

pg_tde_archive_decrypt = executable('pg_tde_archive_decrypt',
pg_tde_archive_decrypt_sources,
dependencies: [frontend_code],
c_args: ['-DFRONTEND'], # needed for xlogreader et al
kwargs: default_bin_args,
include_directories: [postgres_inc, pg_tde_inc],
link_with: [pg_tde_frontend]
)
contrib_targets += pg_tde_archive_decrypt

pg_tde_restore_encrypt_sources = files(
'src/bin/pg_tde_restore_encrypt.c',
) + xlogreader_sources

pg_tde_restore_encrypt = executable('pg_tde_restore_encrypt',
pg_tde_restore_encrypt_sources,
dependencies: [frontend_code],
c_args: ['-DFRONTEND'], # needed for xlogreader et al
kwargs: default_bin_args,
include_directories: [postgres_inc, pg_tde_inc],
link_with: [pg_tde_frontend]
)
contrib_targets += pg_tde_restore_encrypt
15 changes: 15 additions & 0 deletions contrib/pg_tde/src/access/pg_tde_xlog_smgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,21 @@ TDEXLogSmgrInitWrite(bool encrypt_xlog)
pg_tde_set_db_file_path(GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID).dbOid, EncryptionState->db_map_path);
}

void
TDEXLogSmgrInitWriteReuseKey()
{
InternalKey *key = pg_tde_read_last_wal_key();

if (key)
{
EncryptionKey = *key;
TDEXLogSetEncKeyLsn(EncryptionKey.start_lsn);
pfree(key);
}

pg_tde_set_db_file_path(GLOBAL_SPACE_RLOCATOR(XLOG_TDE_OID).dbOid, EncryptionState->db_map_path);
}

/*
* Encrypt XLog page(s) from the buf and write to the segment file.
*/
Expand Down
228 changes: 228 additions & 0 deletions contrib/pg_tde/src/bin/pg_tde_archive_decrypt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#include "postgres_fe.h"

#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>

#include "access/xlog_internal.h"
#include "access/xlog_smgr.h"
#include "common/logging.h"

#include "access/pg_tde_fe_init.h"
#include "access/pg_tde_xlog_smgr.h"

static bool
is_segment(const char *filename)
{
return strspn(filename, "0123456789ABCDEF") == 24 && filename[24] == '\0';
}

static void
write_decrypted_segment(const char *segpath, const char *segname, int pipewr)
{
int fd;
off_t fsize;
int r;
int w;
TimeLineID tli;
XLogSegNo segno;
PGAlignedXLogBlock buf;
off_t pos = 0;

fd = open(segpath, O_RDONLY | PG_BINARY, 0);
if (fd < 0)
pg_fatal("could not open file \"%s\": %m", segname);

/*
* WalSegSz extracted from the first page header but it might be
* encrypted. But we need to know the segment seize to decrypt it (it's
* required for encryption offset calculations). So we get the segment
* size from the file's actual size. XLogLongPageHeaderData->xlp_seg_size
* there is "just as a cross-check" anyway.
*/
fsize = lseek(fd, 0, SEEK_END);
XLogFromFileName(segname, &tli, &segno, fsize);

r = xlog_smgr->seg_read(fd, buf.data, XLOG_BLCKSZ, pos, tli, segno, fsize);

if (r == XLOG_BLCKSZ)
{
XLogLongPageHeader longhdr = (XLogLongPageHeader) buf.data;
int walsegsz = longhdr->xlp_seg_size;

if (walsegsz != fsize)
pg_fatal("mismatch of segment size in WAL file \"%s\" (header: %d bytes, file size: %ld bytes)",
segname, walsegsz, fsize);

if (!IsValidWalSegSize(walsegsz))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this part be a helper function to avoid the duplication in the two tools? And is_segment is also common

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the duplicated code is enough to bother but I could give it a shot and see if anything improves.

{
pg_log_error(ngettext("invalid WAL segment size in WAL file \"%s\" (%d byte)",
"invalid WAL segment size in WAL file \"%s\" (%d bytes)",
walsegsz),
segname, walsegsz);
pg_log_error_detail("The WAL segment size must be a power of two between 1 MB and 1 GB.");
exit(1);
}
}
else if (r < 0)
pg_fatal("could not read file \"%s\": %m",
segpath);
else
pg_fatal("could not read file \"%s\": read %d of %d",
segpath, r, XLOG_BLCKSZ);

pos += r;

w = write(pipewr, buf.data, XLOG_BLCKSZ);

if (w < 0)
pg_fatal("could not write to pipe: %m");
else if (w != r)
pg_fatal("could not write to pipe: wrote %d of %d", w, r);

while (1)
{
r = xlog_smgr->seg_read(fd, buf.data, XLOG_BLCKSZ, pos, tli, segno, fsize);

if (r == 0)
break;
else if (r < 0)
pg_fatal("could not read file \"%s\": %m", segpath);

pos += r;

w = write(pipewr, buf.data, r);

if (w < 0)
pg_fatal("could not write to pipe: %m");
else if (w != r)
pg_fatal("could not write to pipe: wrote %d of %d", w, r);
}

close(fd);
}

static void
usage(const char *progname)
{
printf(_("%s wraps an archive command to make it archive plain text WAL.\n\n"), progname);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably plain text is not right term here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is fine but am open to suggestions.

printf(_("Usage:\n %s %%p <archive command>\n\n"), progname);
printf(_("Options:\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" -?, --help show this help, then exit\n"));
}

int
main(int argc, char *argv[])
{
const char *progname;
char *sourcepath;
char *sep;
char *sourcename;
char stdindir[MAXPGPATH] = "/tmp/pg_tde_archiveXXXXXX";
char stdinpath[MAXPGPATH];
bool issegment;
int pipefd[2];
pid_t child;
int status;
int r;

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_tde_archive_deceypt (PostgreSQL) " PG_VERSION);
exit(0);
}
}

if (argc < 3)
{
pg_log_error("too few arguments");
pg_log_error_detail("Try \"%s --help\" for more information.", progname);
exit(1);
}

sourcepath = argv[1];

pg_tde_fe_init("pg_tde");
TDEXLogSmgrInit();

sep = strrchr(sourcepath, '/');

if (sep != NULL)
sourcename = sep + 1;
else
sourcename = sourcepath;

issegment = is_segment(sourcename);

if (issegment)
{
char *s;

if (mkdtemp(stdindir) == NULL)
pg_fatal("could not create temporary directory \"%s\": %m", stdindir);

s = strcpy(stdinpath, stdindir);
s = strcpy(s, "/");
strcpy(s, sourcename);
Comment on lines +175 to +177
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not strcat?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it is wasteful. The strcat() function is basically the below.

char *
strcat(char *restrict dst, const char *restrict src)
{
    stpcpy(dst + strlen(dst), src);
    return dst;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that it matters for performance but I personally thought the code became clearer by not wasting those few clock cycles.


if (pipe(pipefd) < 0)
pg_fatal("could not create pipe: %m");

if (symlink("/dev/stdin", stdinpath) < 0)
pg_fatal("could not create symlink \"%s\": %m", stdinpath);

for (int i = 2; i < argc; i++)
if (strcmp(sourcepath, argv[i]) == 0)
argv[i] = stdinpath;
}

child = fork();
if (child == 0)
{
if (issegment)
{
close(0);
dup2(pipefd[0], 0);
close(pipefd[0]);
close(pipefd[1]);
}

if (execvp(argv[2], argv + 2) < 0)
pg_fatal("exec failed: %m");
}
else if (child < 0)
pg_fatal("could not create background process: %m");

if (issegment)
{
close(pipefd[0]);
write_decrypted_segment(sourcepath, sourcename, pipefd[1]);
close(pipefd[1]);
}

r = waitpid(child, &status, 0);
if (r == (pid_t) -1)
pg_fatal("could not wait for child process: %m");
if (r != child)
pg_fatal("child %d died, expected %d", (int) r, (int) child);
if (status != 0)
pg_fatal("%s", wait_result_to_str(status));

if (issegment && unlink(stdinpath) < 0)
pg_log_warning("could not remove symlink \"%s\": %m", stdinpath);
if (issegment && rmdir(stdindir) < 0)
pg_log_warning("could not remove directory \"%s\": %m", stdindir);

return 0;
}
Loading
Loading