作者:姜殿斌
账本数据库目的
为了防止数据库运维人员可能存在监守自盗、篡改数据库并擦除痕迹等行为,可以利用账本数据库特性来进行更加全面的审计和追溯历史。在修改防篡改用户表数据时,数据库会将修改行为记录至只可追加数据的历史表中,从而达到记录操作历史和操作溯源的能力。

账本数据库通过生成数据hash摘要的方式来保存和验证历史操作。账本指的是用户历史表和全局区块表,对于表级别数据修改操作,系统将操作信息以及hash摘要记录至全局区块表中,同时每个防篡改用户表对应一个用户历史表来记录行级数据变更的hash摘要。

用户可以通过重新计算hash摘要、验证hash摘要一致等方式判断防篡改用户表是否被篡改。

账本中的每条记录代表一条已经发生了的既定操作事实,其内容只可追加不可修改,通过对防篡改用户表和相应历史表进行一致性校验可以实现对篡改行为的识别和追踪溯源。此外,账本数据库提供了防篡改用户表一致性校验接口、历史表恢复和归档接口以满足篡改识别、数据膨胀消减、历史数据修复归档等需求。

防篡改表与普通表的定义

在账本数据库特性中,我们使用schema级别进行防篡改表和普通表的隔离。

防篡改表定义:在防篡改schema中的表,具有校验信息,且每次涉及到增、删、改的操作均会记录相应的数据变化以及操作的语句,我们称这些表为防篡改表。而普通的schema中的表,我们称其为普通表。

实验来理解账本数据库这一新特性:

问题1:openGauss2.1.0中新增加的blockchain模式作用是什么?是无论如何都会存在的吗?

blockchain模式下会创建一张用户历史表来记录对应用户表中每条数据的变更行为;由于历史表具有只可追加不可修改的特点,因此历史表记录串联起来便形成了用户对防篡改用户表的修改历史。
用户历史表命名:
用户历史表的表名一般为blockchain.<schemaname>_<tablename>hist形式。当防篡改用户表模式名或者表名过长导致前述方式生成的表名超出表名长度限制,则会采用blockchain.<schema_oid><table_oid>_hist的方式命名。
实验:
登录2.1.0数据库,查看默认的用户和模式:
[omm@node1 ~]$ gsql -d postgres -p 26000 -r
gsql ((openGauss 2.1.0 build 590b0f8e) compiled at 2021-09-30 14:29:04 commit 0 last mr )
image.png
openGauss=# drop schema blockchain;
ERROR: DROP not supported for blockchain schema.
openGauss=#

说明:
从实验看,blockchain模式默认自动创建,用于存储账本数据库特性中创建防篡改表时自动创建的用户历史表,并且不能删除。后面是会用到这个模式下的用户历史表,查看表中记录的变化历史。

一、创建防篡改模式:

模式名称:chain_schema,只是本文章中使用了该schema name,实际上名称可以任意命名。

在账本数据库特性中,我们使用schema级别进行防篡改表和普通表的隔离。

创建防篡改模式,模式名称为:chain_schema

openGauss=# create schema chain_schema with blockchain;
CREATE SCHEMA

说明:
账本数据库是基于模式级别的,不是基于用户级别的。
防篡改模式chain_schema的owner是omm。

问题2:如何判断一个模式是否是防篡改模式?

可以查看PG_NAMESPACE系统表,查看新建的模式 chain_schema是否是防篡改模式:

openGauss=#  select * from PG_NAMESPACE where nspname like 'chain_schema' ;
  nspname  | nspowner | nsptimeline | nspacl | in_redistribution | nspblockchain
-----------+----------+-------------+--------+-------------------+---------------
 chain_schema |       10 |           0 |        | n                 | t
(1 row)
openGauss=#

说明
PG_NAMESPACE系统表存储名称空间,即存储schema相关的信息。
其中nspblockchain :
●如果为真,则该模式为防篡改模式。
● 如果为假,则此模式为非防篡改模式。
结论:nspblockchain=t,该用户具有防篡改模式。

问题3:由于一个用户可以对应多个模式,如何查出来一个用户下的哪个模式是防篡改模式?

可以对系统表PG_NAMESPACE和PG_USER执行如下关联查询,nspblockchain=t是防篡改模式:

openGauss=#  SELECT u.usename,s.nspname,s.nspblockchain AS nspblockchain FROM pg_namespace s, pg_user u WHERE
usename='omm' AND s.nspowner = u.usesysid;
usename |      nspname       | nspblockchain
---------+--------------------+----------
omm     | chain_schema       | t
omm     | dbe_pldebugger     | f
omm     | sqladvisor         | f
omm     | db4ai              |
omm     | blockchain         |
omm     | information_schema |
omm     | public             |
omm     | pg_catalog         |
omm     | snapshot           |
omm     | dbe_perf           |
omm     | pkg_service        |
omm     | cstore             |
omm     | pg_toast           |
(13 rows)

说明:
其中nspblockchain ,如果为真,则该模式为防篡改模式。 如果为假,则此模式为非防篡改模式。
用户omm下的chain_schema模式的nspblockchain=t,故是防篡改模式。

二、在防篡改模式下创建防篡改用户表:

问题4:一个用户的某一个模式with blockchain之后,该模式创建的所有的表都是有区块链特性的?

是的。
根据防篡改表定义,在防篡改schema中的表,具有校验信息,且每次涉及到增、删、改的操作均会记录相应的数据变化以及操作的语句,我们称这些表为防篡改表。而普通的schema中的表,我们称其为普通表。
工作原理:
当用户创建防篡改用户表chain_schema.chain_user_tb1时,系统将自动为该表添加一个hash列来保存每行数据的hash摘要信息,同时在blockchain模式下会创建一张用户历史表blockchain.chain_schema_chain_user_tb1_hist来记录对应用户表中每条数据的变更行为。
实验:创建防篡改表。进行插入、更新、删除操作,并查看变更记录。

1、创建防篡改表

openGauss=# CREATE TABLE chain_schema.chain_user_tb1(id int, name text);
CREATE TABLE

查看防篡改用户表结构及其对应的用户历史表结构:
image.png

2、修改防篡改用户表数据,并查看hash值的相应变化

1)插入数据:

openGauss=# INSERT INTO chain_schema.chain_user_tb1 VALUES(1, 'j1'), (2, 'j2'), (3, 'j3');
INSERT 0 3
openGauss=# SELECT *, hash FROM chain_schema.chain_user_tb1 ORDER BY id;
id | name | hash
----+-------+------------------
1 | j1 | 1f2e543c580cb8c5
2 | j2 | 8fcd74a8a6a4b484
3 | j3 | f51b4b1b12d0354b
(3 rows)

说明:
如果是普通用户创建的表(普通表)是没有hash这个值的。

2)更新数据:

openGauss=# UPDATE chain_schema.chain_user_tb1 SET name = 'j22' WHERE id = 2;
UPDATE 1
openGauss=# SELECT *, hash FROM chain_schema.chain_user_tb1 ORDER BY id;
id | name | hash
----+-------+------------------
1 | j1 | 1f2e543c580cb8c5
2 | j22 | 437761affbb7c605
3 | j3 | f51b4b1b12d0354b
(3 rows)

说明:
id=2的行的hash值发生变化

3)删除数据:

openGauss=# DELETE FROM chain_schema.chain_user_tb1 WHERE id = 3;
DELETE 1
openGauss=# SELECT *, hash FROM chain_schema.chain_user_tb1 ORDER BY id;
id | name | hash
----+------+------------------
1 | j1 | 1f2e543c580cb8c5
2 | j22 | 437761affbb7c605
(2 rows)

说明:
防篡改用户表,支持用户对其insert /update/delete操作。

4)查询历史表记录:
查询blockchain模式下的用户历史表,或者通过查询gs_global_chain,查询全局区块表记录


openGauss=# select * from blockchain.chain_schema_chain_user_tb1_hist;
rec_num | hash_ins | hash_del | pre_hash
---------+------------------+------------------+----------------------------------
0 | 1f2e543c580cb8c5    |                                 | e45acf22fe042b2373d148f52903d29a
1 | 8fcd74a8a6a4b484    |                                 | af08f23d38ecfec2ad9c6f1c4685a837
2 | f51b4b1b12d0354b   |                                 | 69e2885fb802fbb2b191211623115f9d
3 | 437761affbb7c605     | 8fcd74a8a6a4b484 | fd61cb772033da297d10c4e658e898d7
4 |                                    | f51b4b1b12d0354b | 6475a497b7a272a92bab012d7f3d615b
(5 rows)

openGauss=# \c postgres user1
Password for user user1:
Non-SSL connection (SSL connection is recommended when requiring high-security)
You are now connected to database "postgres" as user "user1".
openGauss=> SELECT * FROM gs_global_chain;
 blocknum |  dbname  | username |           starttime           | relid |    relnsp    |    relname     |     relhash      |
         globalhash            |                                   txcommand
----------+----------+----------+-------------------------------+-------+--------------+----------------+------------------+---
-------------------------------+--------------------------------------------------------------------------------
        0 | postgres | omm      | 2021-11-22 10:46:04.844784+08 | 24790 | chain_schema | chain_user_tb1 | 30f45a9150f70150 | 9f
e40008d23befe1268f6198ebe2616f | INSERT INTO chain_schema.chain_user_tb1 VALUES(1, 'j1'), (2, 'j2'), (3, 'j3');
        1 | postgres | omm      | 2021-11-22 10:47:52.728669+08 | 24790 | chain_schema | chain_user_tb1 | bbe3b3f8c3a79eed | 9c
a92a1ad471e825720ee39f39b569ad | UPDATE chain_schema.chain_user_tb1 SET name = 'j22' WHERE id = 2;
        2 | postgres | omm      | 2021-11-22 10:48:50.539364+08 | 24790 | chain_schema | chain_user_tb1 | e61868f1223e2506 | 4a
5850dc2f881478236c270baa9b2582 | DELETE FROM chain_schema.chain_user_tb1 WHERE id = 3;
(3 rows)

openGauss=> select * from blockchain.chain_schema_chain_user_tb1_hist;
ERROR:  could not open file "base/15103/24796": No such file or directory
openGauss=>

说明:
在blockchain模式下会创建一张用户历史表blockchain.chain_schema_chain_user_tb1_hist来记录对应用户表中每条数据的变更行为。也可以通过查询gs_global_chain,查询全局区块表记录,还可以看到具体的操作语句。

问题5:blockchain模式下的表的访问权限是怎样的?sysadmin可以直接访问吗?

实验:
新建用户user1,并查看修改记录:

openGauss=# create user user1 password 'Passw0rd@1234';
CREATE ROLE
openGauss=# \c postgres user1
Password for user user1:
Non-SSL connection (SSL connection is recommended when requiring high-security)
You are now connected to database "postgres" as user "user1".
openGauss=>  select * from blockchain.chain_schema_chain_user_tb1_hist;
ERROR:  permission denied for schema blockchain
LINE 1: select * from blockchain.chain_schema_chain_user_tb1_hist;
                      ^
DETAIL:  N/A
openGauss=> SELECT * FROM gs_global_chain;
ERROR:  permission denied for relation gs_global_chain
DETAIL:  N/A
openGauss=> \du
           List of roles
 Role name | Attributes | Member of
-----------+------------+-----------
 user1     |            | {}
openGauss=>

说明:
权限不足,无法访问

赋予user1 用户sysadmin权限:

openGauss=# alter user user1  with sysadmin;
ALTER ROLE
openGauss=# \du
                                                              List of roles
 Role name |                                                    Attributes                                                    |
 Member of
-----------+------------------------------------------------------------------------------------------------------------------+
-----------
 omm       | Sysadmin, Create role, Create DB, Replication, Administer audit, Monitoradmin, Operatoradmin, Policyadmin, UseFT |
 {}
 user1     | Sysadmin                                                                                                         |
 {}

openGauss=#
openGauss=> \dn
    List of schemas
      Name      | Owner
----------------+-------
.....
 user1          | user1

openGauss=> \du
                                                              List of roles
 Role name |                                                    Attributes                                                    |
 Member of
-----------+------------------------------------------------------------------------------------------------------------------+
-----------
 omm       | Sysadmin, Create role, Create DB, Replication, Administer audit, Monitoradmin, Operatoradmin, Policyadmin, UseFT |
 {}
 user1     | Sysadmin                                                                                                         |
 {}

再次访问相关系统表:

openGauss=> select current_user;
 current_user
--------------
 user1
(1 row)

openGauss=> select * from blockchain.chain_schema_chain_user_tb1_hist;
 rec_num |     hash_ins     |     hash_del     |             pre_hash
---------+------------------+------------------+----------------------------------
       0 | 0eb8cc6df3759329 |                  | 4ce187097745e274d86d0fa07041309c
       1 | 0853f7147fbf932d |                  | a803acc54a4bd52dc24df9766c5e2d23
       2 | 19e7970eddc1dafa |                  | e4a1fc9b086aaa5472372183c0159f57
       3 | c437ab0d4367321a | 0853f7147fbf932d | 90c97604df5d2124b1614175a20f6037
       4 |                  | 19e7970eddc1dafa | 1b7cd4766f94557c745d8fc185ca4206
(5 rows)

openGauss=> SELECT * FROM gs_global_chain;
 blocknum |  dbname  | username |           starttime           | relid |    relnsp    |    relname     |     relhash      |
         globalhash            |                                   txcommand
----------+----------+----------+-------------------------------+-------+--------------+----------------+------------------+---
-------------------------------+--------------------------------------------------------------------------------
        0 | postgres | omm      | 2021-11-24 16:04:14.575589+08 | 16389 | chain_schema | chain_user_tb1 | 30f45a9150f70150 | 9f
e40008d23befe1268f6198ebe2616f | INSERT INTO chain_schema.chain_user_tb1 VALUES(1, 'j1'), (2, 'j2'), (3, 'j3');
        1 | postgres | omm      | 2021-11-24 16:04:26.108026+08 | 16389 | chain_schema | chain_user_tb1 | bbe3b3f8c3a79eed | 9c
a92a1ad471e825720ee39f39b569ad | UPDATE chain_schema.chain_user_tb1 SET name = 'j22' WHERE id = 2;
        2 | postgres | omm      | 2021-11-24 16:04:33.050815+08 | 16389 | chain_schema | chain_user_tb1 | e61868f1223e2506 | 4a
5850dc2f881478236c270baa9b2582 | DELETE FROM chain_schema.chain_user_tb1 WHERE id = 3;
(3 rows)

说明:

用户需要sysadmin权限,才能访问账本表。

问题6:是否能够对账本中的每条记录进行修改?

账本中的每条记录代表一条已经发生了的既定操作事实,其内容只可追加不可修改,追加也是系统自动完成。
实验:

1、truncate用户历史表、全局区块表:

可以使用omm用户或者user1用户操作,结果都一样,不支持删除这个系统表:

gsql -d postgres -p 26000 -r
openGauss=# truncate  blockchain.chain_schema_chain_user_tb1_hist;
ERROR:  It is not supported to truncate blockchain table "chain_schema_chain_user_tb1_hist"
openGauss=#
openGauss=# truncate  gs_global_chain;
ERROR:  It is not supported to truncate blockchain table "gs_global_chain"
openGauss=#

结论:

不支持truncate用户历史表、全局区块表的操作;

2、drop blockchain模式

openGauss=# drop schema  blockchain;
ERROR:  DROP not supported for blockchain schema.

3、对blockchain.chain_schema_chain_user_tb1_hist进行 INSERT、 DELETE、 UPDATYE操作

openGauss=# insert into  blockchain.chain_schema_chain_user_tb1_hist (hash_ins) values('c437ab0d4367321b');
ERROR:  Un-support feature
openGauss=# update blockchain.chain_schema_chain_user_tb1_hist  set hash_ins='c437ab0d4367321b' where rec_num=3                          ;
ERROR:  Un-support feature
DETAIL:  internal relation doesn't allow UPDATE
openGauss=# delete blockchain.chain_schema_chain_user_tb1_hist where rec_num=3;                                                          ERROR:  Un-support feature
DETAIL:  internal relation doesn't allow DELETE

说明:
均不支持

三、校验账本数据一致性

数据库正常运行,并且对防篡改数据库执行了一系列增、删、改等操作,保证在查询时段内有账本操作记录结果产生。
账本数据库校验功能目前提供两种校验接口,分别为:
ledger_hist_check(text,…和ledger_gchain_check(text…。
普通用户调用校验接口,仅能校验自己有权限访问的表。

1、校验防篡改用户表’chain_schema.chain_user_tb1’与其对应的历史表是否一致。

函数说明:
校验防篡改用户表和用户历史表的接口为pg_catalog.ledger_hist_check,操作为:
SELECT pg_catalog.ledger_hist_check(schema_name text,table_name text);
如果校验通过,函数返回t,反之则返回f。

chaindb=> SELECT pg_catalog.ledger_hist_check('chain_schema', 'chain_user_tb1');
 ledger_hist_check
-------------------
 t
(1 row)

说明:
pg_catalog.ledger_hist_check(‘chain_schema’—模式名, ‘chain_user_tb1’–表名字);
该结果表明防篡改用户表和用户历史表中记录的结果能够一一对应,保持一致。

2、查询防篡改用户表’chain_schema.chain_user_tb1’与其对应的历史表以及全局区块表中关于该表的记录是否一致。

函数说明:
校验防篡改用户表、用户历史表和全局区块表三者是否一致的接口为pg_catalog.ledger_gchain_check,操作为:
SELECT pg_catalog.ledger_gchain_check(schema_name text, table_name text);
如果校验通过,函数返回t,反之则返回f。

chaindb=> SELECT pg_catalog.ledger_gchain_check('chain_schema', 'chain_user_tb1');
 ledger_gchain_check
---------------------
 t
(1 row)

查询结果显示,上述三表中关于chain_schema.chain_user_tb1记录保持一致,未发生篡改行为。

四、OS层面破坏性实验:如果删掉用户表、用户历史表的物理文件

openGauss=# select pg_relation_filepath('blockchain.chain_schema_chain_user_tb1_hist');
 pg_relation_filepath
----------------------
 base/15103/24796
(1 row)

如果删掉用户历史表的物理文件24796,就无法查询修改记录了。

openGauss=# select * from blockchain.chain_schema_chain_user_tb1_hist;
ERROR:  could not open file "base/15103/24796": No such file or directory
openGauss=#

如果删掉防篡改用户下的表的物理文件24790,当然也无就法验证了:

openGauss=#  SELECT pg_catalog.ledger_gchain_check('chain_schema', 'chain_user_tb1');
ERROR:  could not open file "base/15103/24790": No such file or directory
CONTEXT:  referenced column: ledger_gchain_check
openGauss=#
#

五、非法篡改验证(未完成)

数据库层面,经过测试,只要是有权限的人,无论是通过应用系统更新了数据,还是登入到数据库直接运行SQL更新了数据,ledger_hist_check的结果都是true,是合法授权,不是非法篡改。 
所以,想到需要从操作系统层面进行非法篡改。

非法篡改基本思路:

  1. 使用工具,对表所在的磁盘块进行直接篡改;
  2. select * from 这张表显示是被篡改后的数据;
  3. 但是ledger_hist_check会显示False,这样我们就知道这张表被篡改过;
  4. 然后再去查gs_global_chain去找到最后的实际SQL再正常改回来;
  5. 再执行ledger_hist_check,显示True。
    但是由于无法实现非法篡改账本数据库的某个表,所以未能完成该实验。
    后续会按照这个思路进行测试,如果有结果会更新本文。

六、限制:

1、防篡改表不支持非行存表、临时表、外表、unlog表,非行存表均无防篡改属性。
2、防篡改表在创建时会自动增加一个名为hash的系统列,所以防篡改表单表最大列数为1599。

测试总结:
1、使用防篡改表,有权限的用户仍然可以对该表进行操作;不能防止有权限的用户对表进行修改,这是正常的。
2、omm用户、表的所有者、有权限的访问者都无法删除防篡改记录,即使表的所有者有sysadmin权限也不允许删除。这点和审计有所不同。

个人对防篡改数据库的理解:
是可以防止篡改账本(账本指的是用户历史表和全局区块表,对于表级别数据修改操作,系统将操作信息以及hash摘要记录至全局区块表中)。称为账本数据库还是比较贴切的。

感谢:
感谢乐奕总的修改意见,谢谢!

参考:
1、《openGauss2.1.0开发者指南、技术白皮书、特性描述》
2、《关于openGauss账本数据库:你想知道的这里都有》
https://w1. ww.modb.pro/db/144989
3、《用户权限控制》
https://www.modb.pro/db/23483

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐