暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

PostgreSQL中的TDE使用体验

数据库杂记 2023-11-11
292


1.前言

因为等不到TDE功能在PostgreSQL中的及时的代码入库,最终它以plugin的形式出现,如下是它的解释:

Percona已经收到用户反馈,认为这将是一个有用的特性,所以我们正在努力将其作为Postgres的开源扩展,任何人都可以部署。Percona的联合创始人Peter Zaitsev在他的博客《PostgreSQL为什么需要TDE》中强调了一些你可能需要TDE的技术和商业原因。由于PostgreSQL还没有TDE特性,Percona希望提供TDE特性作为PostgreSQL的扩展。

详情可以参考中译文:为什么PostgreSQL需要透明数据库加密(TDE) - 墨天轮 (modb.pro) :https://www.modb.pro/db/611703

Percona 公司是成立于 2006年,总部在美国北卡罗来纳的Raleigh。由 Peter Zaitsev 和 Vadim Tkachenko, 可以通过他们的名字看出这两个人,它是由俄罗斯人创建的一家公司。业界良心!

关于CLUSTER WIDE TDE (Ttransparent Data Encryption),  https://momjian.us/main/writings/pgsql/cfe.pdf  Cluster File Encryption in Postgres, BRUCE MOMJIA他也做了比较详细的介绍。

完整的wiki介绍 ,可以参考: https://wiki.postgresql.org/wiki/Transparent_Data_Encryption

从根本上说,TDE必须满足三个标准——显然,它必须是安全的,但它也必须以一种对其余Postgres代码影响最小的方式完成。这有两个原因—首先,只有少数用户会使用TDE,因此添加的代码越少,所需的测试就越少。其次,添加的代码越少,TDE因未来的Postgres更改而中断的可能性就越小。最后,TDE应该满足监管要求。此外,TDE不需要防止所有潜在的攻击向量,但它应该完全防止所有预期的攻击向量。

1.1 历史

第一个补丁于2016年[1]:https://www.postgresql.org/message-id/CA%2BCSw_tb3bk5i7if6inZFc3yyf%2B9HEVNTy51QFBoeUk7UE_V%3Dw%40mail.gmail.com提出,并使用单个密钥实现实例范围的加密。2018年,提出了表级透明数据加密[2]:https://www.postgresql.org/message-id/031401d3f41d%245c70ed90%241552c8b0%24%40lab.ntt.co.jp,以及与密钥管理系统集成的方法;第一个补丁于2019年提交:https://www.postgresql.org/message-id/CAD21AoBjrbxvaMpTApX1cEsO%3D8N%3Dnc2xVZPB0d9e-VjJ%3DYaRnw%40mail.gmail.com。该补丁使用两层密钥体系结构和通用密钥管理API实现了表空间级加密,以便与外部密钥管理系统通信。

1.2 TDE的范围

  • 内部密钥管理系统(KMS),将密钥存储在数据库中

  • 实例范围的TDE

  • 加密所有持久的内容

  • 不加密内存中的共享缓冲区或数据

实例范围加密的优点:

  • 体系结构简单

  • 适合于对所有数据加密的需求

就TDE而言,实例范围的加密满足合规性要求,并且符合要求。它还满足对静态数据(即持久数据)加密的标准。

虽然这种方法提供了对具有读访问权限的攻击者的保护,但它不能保证数据完整性,也就是说,不能保证对具有写访问权限的攻击者的保护。有一天可能会尝试进行完整性检查,但是需要加密的文件数量会更大(不仅仅是包含用户数据的文件),并且有些文件在没有显著复杂性的情况下很难加密和完整性检查,例如pg_xact。一种解决方案可能是,如果可以保证默认数据目录不受写攻击,则仅对表空间提供完整性检查保证。表空间目录中的vm和fsm文件会使这种方法变得复杂。

1.3 什么时候需要加解密

Buffer

它在文件系统I/O期间加密缓冲区数据:

  • 进程在写入文件系统时对数据进行加密

  • 从文件系统读取时解密

  • 共享缓冲区中的数据不加密

WAL

实例加密背景下:

  • 进程以非加密状态将WAL数据插入到WAL缓冲区

  • 当写入文件系统时,WAL缓冲区是加密的

  • WAL将使用专用的加密密钥

临时文件

临时文件将使用在postmaster启动时随机生成的临时密钥,该密钥仅在邮政管理员的生命周期内有效。对于并行查询,特别是并行哈希连接,由于多个并行工作人员可能使用相同的临时文件,因此应该与并行工作人员共享临时键。需要为临时文件编写统一的I/O API,以便集中加密更改。当前的API是流式的,因此可能需要使用流式密码模式。

备份文件

在pg_basebackup中将有一个选项用于更改堆/索引键以实现键轮换。在故障转移到备用服务器之后,也可以更改WAL密钥。

如何加密

我们将使用高级加密标准(AES)[4]:https://en.wikipedia.org/wiki/Advanced_Encryption_Standard。我们将提供在initdb时使用——file-encryption-method选择的三个密钥长度选项(128、192和256位)。

2. 实验体会

因为主代码实现并没有入库,我们可以试用一下TDE插件来了解一下TDE的功能。它需要系统安装配备好PG16。

而刚好前天2023.11.9,16.1版本也Release了。

PostgreSQL 16.1, 15.5, 14.10, 13.13, 12.17, and 11.22 Released!

Ref: https://www.postgresql.org/about/news/postgresql-161-155-1410-1313-1217-and-1122-released-2749/

我们可以在CentOS7.9下边安装PG16.1, 然后安装对应的插件。

2.1 简单安装

sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo yum makecache
sudo yum install -y postgresql16 postgresql16-contrib postgresql16-libs postgresql16-server

可能网络原因,报错:

No package postgresql16 available.
No package postgresql16-contrib available.
No package postgresql16-libs available.
No package postgresql16-server available.
Error: Nothing to do

改用源码安装:

假定数据库用户postgres已经提前准备好。

wget https://ftp.postgresql.org/pub/source/v16.1/postgresql-16.1.tar.gz
[06:16:08-postgres@centos3:/iihero/source/pgsrc]$ tar xf postgresql-16.1.tar.gz 
./configure --prefix=/usr/pgsql-16 -with-extra-version=" [Sean]" -with-openssl
[06:18:57-postgres@centos3:/iihero/source/pgsrc/postgresql-16.1]$ make -j4 world-bin && sudo make install-world-bin

-- 补充环境变量 ~/.bash_profile
export PGROOT=/usr/pgsql-16
export PGHOME=/var/lib/pgsql/16
export PGPORT=5432
export PGDATA=$PGHOME/data
export PATH=$PGROOT/bin:$PATH
export LD_LIBRARY_PATH=$PGROOT/lib:$LD_LIBRARY_PATH

TDE插件安装:

cd /iihero/source
git clone https://github.com/Percona-Lab/postgres-tde-ext.git

cd /iihero/source/postgres-tde-ext
source ~/.bash_profile
make USE_PGXS=1
sudo PATH=$PATH make USE_PGXS=1 install

在make时可能会报错:

/iihero/source/postgres-tde-ext/src/include/keyring/keyring_config.h:7:18: fatal error: json.h: No such file or directory
 #include <json.h>
                  ^
compilation terminated.

补充一下对应的包:

sudo yum install json-c-devel

这下就可以通过编译了。

sudo PATH=$PATH make USE_PGXS=1 install
......
/usr/bin/mkdir -p '/usr/pgsql-16/lib'
/usr/bin/mkdir -p '/usr/pgsql-16/share/extension'
/usr/bin/mkdir -p '/usr/pgsql-16/share/extension'
/usr/bin/install -c -m 755  pg_tde.so '/usr/pgsql-16/lib/pg_tde.so'
/usr/bin/install -c -m 644 .//pg_tde.control '/usr/pgsql-16/share/extension/'
/usr/bin/install -c -m 644 .//pg_tde--1.0.sql  '/usr/pgsql-16/share/extension/'

至此二进制安装完成。

2.2 使用TDE

初始化实例

mkdir /usr/lib/pgsql/16
cd /usr/lib/pgsql/16
initdb -E UTF8 --locale=C -D data/

调整修改配置文件:

vi postgresql.conf

log_filename = 'postgresql-%Y-%m-%d.log'
logging_collector = on
log_directory = 'log'

# 添加插件预加载
shared_preload_libraries = 'pg_tde'

# keyring配置文件项
pg_tde.keyringConfigFile = '/iihero/tde/keyring.json'

创建keyring配置文件:

vi /iihero/tde/keyring.json
{
      'provider''file',
      'datafile''/tmp/pgkeyring'
}

启动数据库

pg_ctl start
[06:47:32-postgres@centos3:/var/lib/pgsql/16/data]$ psql
psql (16.1 [Sean])
Type "help" for help.

postgres=# \dx
                 List of installed extensions
  Name   | Version |   Schema   |         Description          
---------+---------+------------+------------------------------
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
(1 row)

postgres=# create extension pg_tde;
CREATE EXTENSION
postgres=# \dx
                 List of installed extensions
  Name   | Version |   Schema   |         Description          
---------+---------+------------+------------------------------
 pg_tde  | 1.0     | public     | pg_tde access method
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
(2 rows)

经过简单验证,pg_tde确实安装上了。

2.3 建表对比测试

1、简单的表测试

两张表:t1,sbtest1,前者普通表,后者使用TDE. 使用的都是普通的整型字段。

postgres=# create table t1(id SERIAL, k INTEGER DEFAULT '0' NOT NULL, PRIMARY KEY (id));
CREATE TABLE
postgres=# CREATE TABLE sbtest1 ( id SERIAL, k INTEGER DEFAULT '0' NOT NULL, PRIMARY KEY (id)) USING pg_tde;
CREATE TABLE

postgres=# \timing on
Timing is on.
postgres=# insert into t1 (k) select generate_series(1, 1000000);
INSERT 0 1000000
Time: 2372.397 ms (00:02.372)
postgres=# insert into sbtest1 select generate_series(1, 1000000);
INSERT 0 1000000
Time: 7292.093 ms (00:07.292)

插入时间性能简单对比,差不多慢了3倍。空间大小比较:

postgres=# select pg_total_relation_size('t1');
 pg_total_relation_size 
------------------------
               58777600
(1 row)

Time: 9.352 ms
postgres=# select pg_total_relation_size('sbtest1');
 pg_total_relation_size 
------------------------
               58777600
(1 row)

Time: 0.151 ms

这个有点神奇,大小居然是一样的。

postgres=# select pg_relation_filepath('t1'), pg_relation_filepath('sbtest1');
 pg_relation_filepath | pg_relation_filepath
----------------------+----------------------
 base/5/16450         | base/5/16442
(1 row)

Time: 0.596 ms

postgres=# \! stat /var/lib/pgsql/16/data/base/5/16450
  File: ‘/var/lib/pgsql/16/data/base/5/16450’
  Size: 36249600        Blocks: 70800      IO Block: 4096   regular file
Device: 803h/2051d      Inode: 6623009     Links: 1
Access: (0600/-rw-------)  Uid: (   26/postgres)   Gid: (   26/postgres)
Access: 2023-11-11 07:29:32.516799960 +0800
Modify: 2023-11-11 07:36:53.453002230 +0800
Change: 2023-11-11 07:36:53.453002230 +0800
 Birth: -
postgres=# \! stat /var/lib/pgsql/16/data/base/5/16442
  File: ‘/var/lib/pgsql/16/data/base/5/16442’
  Size: 36249600        Blocks: 70800      IO Block: 4096   regular file
Device: 803h/2051d      Inode: 6623008     Links: 1
Access: (0600/-rw-------)  Uid: (   26/postgres)   Gid: (   26/postgres)
Access: 2023-11-11 07:24:53.844581957 +0800
Modify: 2023-11-11 07:34:38.873064530 +0800
Change: 2023-11-11 07:34:38.873064530 +0800
 Birth: -

2、使用text型字段对比

再看两张表:t是普通表,t_tde是使用TDE的表

CREATE TABLEpostgres=# create table t(id int primary key, col2 text);
CREATE TABLE
postgres=# create table t_tde(id int primary key, col2 text) using pg_tde;
CREATE TABLE

postgres=# \timing on
Timing is on.
postgres=# insert into t select n, md5(random()::varchar) || n from generate_series(1, 1000000) as n;
INSERT 0 1000000
Time3345.107 ms (00:03.345)

postgres=# insert into t_tde select n, md5(random()::varchar) || n from generate_series(1, 1000000) as n;
2023-11-11 06:53:56.092 CST [12609ERROR:  EVP_CipherFinal_ex failed. OpenSSL errorerror:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
2023-11-11 06:53:56.092 CST [12609STATEMENT:  insert into t_tde select n, md5(random()::varchar) || n from generate_series(11000000as n;
ERROR:  EVP_CipherFinal_ex failed. OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Time: 80.529 ms

postgres=# insert into t_tde values(0, 'abcdefg');
ERROR:  EVP_CipherFinal_ex failed. OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Time: 1.043 ms

似乎对TEXT类型加解密有点问题。。。。。。怀疑是BUG。我想我可以向他们提一个BUG了。

3、使用VARCHAR型字段对比

建表:

postgres=# create table t3(id int primary key, col2 varchar(128));
CREATE TABLE
postgres=# create table t3_tde(id int primary key, col2 varchar(128)) using pg_tde;
CREATE TABLE

插入数据:

postgres=# \timing on
Timing is on.
postgres=# insert into t3 select n, md5(random()::varchar) || n from generate_series(1, 1000000) as n;
INSERT 0 1000000
Time: 3963.480 ms (00:03.963)
postgres=# insert into t3_tde select n, md5(random()::varchar) || n from generate_series(1, 1000000) as n;
INSERT 0 1000000
Time: 13146.733 ms (00:13.147)

能看到,时间上差不多3倍的差距。

表的空间大小:

postgres=# select pg_total_relation_size('t3');
 pg_total_relation_size 
------------------------
               99090432
(1 row)

Time: 0.656 ms
postgres=# select pg_total_relation_size('t3_tde');
 pg_total_relation_size 
------------------------
               99090432
(1 row)

Time: 0.227 ms

表的空间大小上看不出什么差别。简单看看内容:

postgres=# SELECT cmin, cmax, xmin, xmax, ctid, * FROM t3_tde limit 2;
 cmin | cmax | xmin | xmax | ctid  | id |               col2                
------+------+------+------+-------+----+-----------------------------------
    0 |    0 |  766 |    0 | (0,1|  1 | 10468dac2373043fac7515ea079c9b3e1
    0 |    0 |  766 |    0 | (0,2|  2 | eb5042dc2e9f054493174c24f48634f42
(2 rows)

Time: 0.505 ms

postgres=# explain select * from t3_tde where id = 99997;
                                 QUERY PLAN                                 
----------------------------------------------------------------------------
 Index Scan using t3_tde_pkey on t3_tde  (cost=0.42..8.44 rows=1 width=278)
   Index Cond: (id = 99997)
(2 rows)

Time: 2.917 ms

检查server log, 会发现若干解密的调用:

2023-11-11 11:30:47.568 CST [26143] LOG:  DECRYPT-TUPLE-ExecStoreBuffer: table Oid: 16472 block no: 933 data size: 42, tuple offset in file: 7647152
2023-11-11 11:30:47.568 CST [26143] STATEMENT:  select * from t3_tde where id = 99997;
2023-11-11 11:30:47.568 CST [26143] LOG:  DECRYPT-TUPLE-ExecStoreBuffer: Batch-No:0 Start offset: 7647152 Data_Len: 42, batch_start_block: 477947, batch_end_block: 477950
2023-11-11 11:30:47.568 CST [26143] STATEMENT:  select * from t3_tde where id = 99997;
2023-11-11 11:31:17.404 CST [26143ERROR:  syntax error at or near "alalyze" at character 9
2023-11-11 11:31:17.404 CST [26143] STATEMENT:  explain alalyze select * from t3_tde where id = 99997;
2023-11-11 11:31:19.514 CST [26488ERROR:  EVP_CipherFinal_ex failed. OpenSSL errorerror:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
2023-11-11 11:31:19.514 CST [26488] CONTEXT:  while scanning block 0 of relation "public.t3_tde"
        automatic vacuum of table "postgres.public.t3_tde"
2023-11-11 11:31:19.514 CST [26488ERROR:  EVP_CipherFinal_ex failed. OpenSSL errorerror:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
2023-11-11 11:31:19.514 CST [26488] CONTEXT:  while scanning block 0 of relation "public.sbtest1"
        automatic vacuum of table "postgres.public.sbtest1"

4、再多一点字段类型的试验

前边已经看到text字段出问题了,如果换上另的字段类型,不知道结果如何?

create table t4_tde(id int primary key, col2 varchar(36), col3 timestamp, col4 char(36), col5 bytea, col6 varbit(256)) using pg_tde;
postgres=# postgres=# insert into t4_tde values(1'wang''2023-11-11 11:40:00''aaaabbbb''aaaabbbbccdd'::bytea, B'101010101');
ERROR:  EVP_CipherFinal_ex failed. OpenSSL errorerror:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

嗯,同样报错。

postgres=# create table t4_tde(id int primary key, col2 varchar(36), col3 timestamp, col4 char(36), col6 varbit(256)) using pg_tde;
CREATE TABLE
postgres=# insert into t4_tde values(1'wang''2023-11-11 11:40:00''aaaabbbb', B'101010101');
INSERT 0 1

如此看来,如果字段类型是TEXT或者bytea, 就会出现相关的加解密的错了,似乎是跟PADDING有关。

3. 总结:

pg_tde的推出,不管怎么说,是一件好事。目前可能还有一些BUG, 可以观察一段时间,用的人多了,估计就可以大规模推出了。

目前只是有一个latest BUILD,应该还没有正式的release版本。等出了Release版本,我们可以重新再考查一下看看。





文章转载自数据库杂记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论