目前PostgreSQL官方并未推出透明加密功能,但是cybertec开源了一个分支,支持透明加密。感兴趣的同学可以参考:
https://www.cybertec-postgresql.com/en/products/postgresql-transparent-data-encryption/
它支持对数据和WAL进行透明加密。本文主要介绍WAL的透明加密功能及原理。
WAL透明加密架构
WAL加密主要由一个缓冲来完成,该缓冲未encrypt_buf_xlog,该缓冲大小是8个页大小,在启动时创建,由函数setup_encryption完成,其堆栈如下:
PostgresMain->setup_encryption:: encrypt_buf_xlog = (char *) MemoryContextAlloc(TopMemoryContext, ENCRYPT_BUF_XLOG_SIZE);
加密时将WAL数据加密到该缓冲中,然后刷写到磁盘。恢复回放时从磁盘上读取加密的WAL日志,然后进行in place解密,之后读取其中wal record进行回放。
加密
在XLogWrite刷写日志时进行加密:
首先会先计算有多少页需要刷写,然后将这些页一页一页的进行加密,时间线+段文件号+日志偏移作为向量和密钥encryption_key一起对页进行加密,将WAL加密到加密缓冲encrypt_buf_xlog中,然后将加密缓冲中的WAL刷写磁盘。
恢复时解密
重启时,将日志读取到缓冲中进行解密。由函数XLogPageRead完成。首先需要创建一个XLogReaderState即xlogreader,使用XLogPageRead函数读取WAL日志。然后读取checkpoint和WAL RECORD进行回放。读取WAL RECORD的函数是ReadRecord,从下面代码可以看出,其实真正读取是由XLogPageRead函数来完成的,也就是将磁盘上加密的WAL日志读取到xlogreader的readBuf中,该缓冲1个页大小。
然后,对readBuf中的WAL记录进行解密。可以看出是in place解密。
流复制场景
主上执行start replication命令开启流复制,即函数StartReplication完成的功能:
WalSndLoop不断循环,调用XLogSendPhysical函数从磁盘中将加密的WAL日志读取到encrypt_buf_xlog中,然后进行in plcace解密。将解密后的明文拷贝到output_message.data[]中,用于流复制传输。备机接收后将该日志持久化到磁盘。
备机receiver进程接收日志并写入磁盘,写入函数为XLogWalRcvWrite,这里可以看到它并没有加密。那么恢复时需要将磁盘上的日志加载到内存,这个流程认为磁盘上的WAL日志是加密的,恢复前需要先解密。但从代码上看,这个流程就存在矛盾了!
另外,Start replication命令支持对复制流加密,如下结构体:
上述中,XLogRead函数中的decrypt来源如下,在start replication命令中指定解密。若没有指定解密的话,传输的即为加密的日志流。此时,备机接收后写入磁盘为加密的日志。那么,回放时加载加密的WAL,然后解密,最后回放,这样流程是合理的。
也就是说,流复制场景下,WAL流为解密的情况下,有bug!使用时需注意。