PostgreSQL hook機制編寫插件限制超級用戶權限

PG版本:PostgreSQL 9.6.5node

因爲版本不一樣代碼會有所差異,本例在PostgreSQL 9.6.5編寫而來,其餘版本未經測試(PostgreSQL 10beta3測試不能直接用)。git

1.安裝插件前

sun@sun:~/PG/PG9.6.5/bin$ ./createdb testdb
sun@sun:~/PG/PG9.6.5/bin$ ./createuser testuser
sun@sun:~/PG/PG9.6.5/bin$ ./psql -U testuser testdb
psql (9.6.5)
Type "help" for help.

testdb=> create table testtable(a int);
CREATE TABLE
testdb=> create table testtable2(a int);
CREATE TABLE
testdb=> \q
sun@sun:~/PG/PG9.6.5/bin$ ./psql -U sun testdb
psql (9.6.5)
Type "help" for help.

testdb=# \d
           List of relations
 Schema |    Name    | Type  |  Owner   
--------+------------+-------+----------
 public | testtable  | table | testuser
 public | testtable2 | table | testuser
(2 rows)

testdb=# drop table testtable2;
DROP TABLE
testdb=# \d
           List of relations
 Schema |   Name    | Type  |  Owner   
--------+-----------+-------+----------
 public | testtable | table | testuser
(1 row)

2.安裝插件後

sun@sun:~/PG/PG9.6.5/bin$ ./psql -U sun testdb
psql (9.6.5)
Type "help" for help.

testdb=# \d
           List of relations
 Schema |   Name    | Type  |  Owner   
--------+-----------+-------+----------
 public | testtable | table | testuser
(1 row)

testdb=# drop table testtable;
ERROR:  Please drop with User: testuser 
STATEMENT:  drop table testtable;
ERROR:  Please drop with User: testuser

3.插件安裝方法:

在源碼contrib下創建目錄:limitsuperuserdrop,將C代碼文件和Makefile放入其中,編譯安裝,以下:sql

sun@sun:~/Data/postgresql-9.6.5/contrib/limitsuperuserdrop$ ll
總用量 80
-rw------- 1 sun sun 76898 9月  13 21:04 limitsuperuserdrop.c
-rw------- 1 sun sun   394 9月  13 19:57 Makefile
sun@sun:~/Data/postgresql-9.6.5/contrib/limitsuperuserdrop$ make ; make install

安裝完成後修改配置文件, 修改Data目錄下的postgresql.conf中的shared_preload_librariesshell

shared_preload_libraries = 'limitsuperuserdrop'	# (change requires restart)

重啓數據庫。安裝完成。數據庫

4.原理:

本插件使用PostgreSQL的hook機制,實現對PG數據庫的超級用戶的刪除操做的權限限制。bash

經常使用hook:async

Hook 初始版本 說明
check_password_hook 9.0 處理用戶密碼時調用的hook,能夠對用戶的密碼進行限制,增長密碼的規範。
ClientAuthentication_hook 9.1 處理鏈接時調用的hook,能夠對鏈接進行管理。
ExecutorStart_hook 8.4 處理查詢執行開始時調用的hook
ExecutorRun_hook 8.4 處理查詢執行時調用的hook
ExecutorFinish_hook 8.4 處理查詢結束時調用的hook
ExecutorEnd_hook 8.4 處理查詢完成後調用的hook
ExecutorCheckPerms_hook 9.1 處理訪問權限時調用的hook
ProcessUtility_hook 9.0 通用hook,能夠處理不少的過程

  本例使用ProcessUtility_hook函數

插件實現思路:

當數據庫調用ProcessUtility_hook時,首先會檢測是否爲NULL,不爲NULL的話,則調用由咱們編寫的函數,不然執行標準函數。 也就是說在咱們不使用插件的時候,數據庫老是在調用standard_ProcessUtility。所以咱們的插件代碼,就是在standard_ProcessUtility中執行drop操做的地方判斷要刪除的表是否屬於進行操做的用戶,不是的話就報錯。所以,本插件代碼絕大部分是由src/backend/tcop/utility.c文件複製而來。post

if (ProcessUtility_hook)
		(*ProcessUtility_hook) (parsetree, queryString,
								context, params,
								dest, completionTag);
	else
		standard_ProcessUtility(parsetree, queryString,
								context, params,
								dest, completionTag);

在此提供兩套代碼,完整版和精簡版,精簡版刪除了大多數的代碼。 點擊下載學習

Makefile代碼:

# contrib/limitsuperuserdrop/Makefile

MODULES = limitsuperuserdrop
OBJS = limitsuperuserdrop.o $(WIN32RES)
PGFILEDESC = "limitsuperuserdrop"

ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/limitsuperuserdrop
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif

如下是精簡版的C文件代碼:

/*
 * limitsuperuserdrop.c
 * 說明:限制超級用戶刪除表的操做。
 */
#include "postgres.h"
#include "miscadmin.h"
#include "nodes/parsenodes.h"
#include "nodes/pg_list.h"
#include "catalog/pg_class.h"
#include "executor/executor.h"
#include "tcop/utility.h"
#include "postgres.h"

#include "access/htup_details.h"
#include "access/reloptions.h"
#include "access/twophase.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/toasting.h"
#include "commands/alter.h"
#include "commands/async.h"
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/collationcmds.h"
#include "commands/conversioncmds.h"
#include "commands/copy.h"
#include "commands/createas.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/discard.h"
#include "commands/event_trigger.h"
#include "commands/explain.h"
#include "commands/extension.h"
#include "commands/matview.h"
#include "commands/lockcmds.h"
#include "commands/policy.h"
#include "commands/portalcmds.h"
#include "commands/prepare.h"
#include "commands/proclang.h"
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "commands/user.h"
#include "commands/vacuum.h"
#include "commands/view.h"
#include "miscadmin.h"
#include "parser/parse_utilcmd.h"
#include "postmaster/bgwriter.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteRemove.h"
#include "storage/fd.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/guc.h"
#include "utils/syscache.h"

PG_MODULE_MAGIC;

void _PG_init(void);
void _PG_fini(void);

static ProcessUtility_hook_type My_ProcessUtility_hook_type = NULL;
static void ExecDropStmt(DropStmt *stmt, bool isTopLevel);
Oid MyGetOwnerId(Oid class_oid);
Oid MyGetrelOid(DropStmt *drop);

void
My_ProcessUtility(Node *parsetree,
						const char *queryString,
						ProcessUtilityContext context,
						ParamListInfo params,
						DestReceiver *dest,
						char *completionTag)
{
	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);

	switch (nodeTag(parsetree))
	{

		case T_DropStmt:
			{
				DropStmt   *stmt = (DropStmt *) parsetree;

				if( MyGetOwnerId(MyGetrelOid(stmt)) != GetUserId() )
					ereport(ERROR,
									(errcode(ERRCODE_UNDEFINED_TABLE),
											errmsg("Please drop with User: %s ", GetUserNameFromId(MyGetOwnerId(MyGetrelOid(stmt)),false))
									)
								);
				else
					ExecDropStmt(stmt, isTopLevel);
			}
			break;
	}
}

static void
ExecDropStmt(DropStmt *stmt, bool isTopLevel)
{
	switch (stmt->removeType)
	{
		case OBJECT_INDEX:
			if (stmt->concurrent)
				PreventTransactionChain(isTopLevel,
										"DROP INDEX CONCURRENTLY");
			/* fall through */

		case OBJECT_TABLE:
		case OBJECT_SEQUENCE:
		case OBJECT_VIEW:
		case OBJECT_MATVIEW:
		case OBJECT_FOREIGN_TABLE:
			RemoveRelations(stmt);
			break;
		default:
			RemoveObjects(stmt);
			break;
	}
}

Oid
MyGetOwnerId(Oid class_oid)
{
	HeapTuple	tuple;
	Oid			ownerId;

	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(class_oid));
	if (!HeapTupleIsValid(tuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_TABLE),
				 errmsg("relation with OID %u does not exist", class_oid)));

	ownerId = ((Form_pg_class) GETSTRUCT(tuple))->relowner;

	ReleaseSysCache(tuple);
//	printf("User = %d\n",ownerId);
	return ownerId;
}


Oid
MyGetrelOid(DropStmt *drop)
{
	ListCell   *cell;
	LOCKMODE	lockmode = AccessExclusiveLock;
	Oid			relOid;

	foreach(cell, drop->objects)
	{
		RangeVar   *rel = makeRangeVarFromNameList((List *) lfirst(cell));

		relOid = RangeVarGetRelidExtended(rel, lockmode, true,
										  false,
										  NULL,
										  NULL);

	}
//	printf("relOid = %d\n",relOid);
	return relOid;
}


// Install Hook
void
_PG_init(void)
{
        My_ProcessUtility_hook_type = ProcessUtility_hook;
        ProcessUtility_hook = My_ProcessUtility;
}


// Uninstall Hook
void
_PG_fini(void)
{
        ProcessUtility_hook = My_ProcessUtility_hook_type;
}

精簡後的代碼運行drop操做效果同樣,便於閱讀學習。可是因爲其餘的操做(好比建立表)也會通過咱們編寫的代碼,然而精簡後的對其餘操做並無相應的實現代碼,因此並不會發生真實的操做(以下,create table testtable2(a int),並無成功建立testtable2表,所以使用精簡代碼進行實驗的話,請在安裝插件前先建立幾張表備用)。

sun@sun:~/PG/PG9.6.5/bin$ ./psql -U sun testdb
psql (9.6.5)
Type "help" for help.

testdb=# \d
           List of relations
 Schema |   Name    | Type  |  Owner   
--------+-----------+-------+----------
 public | testtable | table | testuser
(1 row)

testdb=# create table testtable2(a int);
CREATE TABLE
testdb=# \d
           List of relations
 Schema |   Name    | Type  |  Owner   
--------+-----------+-------+----------
 public | testtable | table | testuser
(1 row)

參考:

https://my.oschina.net/Suregogo/blog/312848

相關文章
相關標籤/搜索