Users, groups, and roles Users, groups, and roles are the same thing in PostgreSQL, with the only difference being that users have permission to log in by default. The CREATE USER and CREATE GROUP statements are actually aliases for the CREATE ROLE statement.
According to the SQL standard, the owner of a schema always owns all objects within it. PostgreSQL allows schemas to contain objects owned by users other than the schema owner. This can happen only if the schema owner grants the CREATE privilege on their schema to someone else, or a superuser chooses to create objects in it.
根据SQL标准,一个模式的所有者总是拥有其中的所有对象。PostgreSQL允许模式包含由模式所有者以外的用户拥有的对象。只有当模式拥有者将模式的"CREATE"权限授予其他人,或者超级用户选择在模式中创建对象时,这种情况才会发生。
所以,就存在这么一种情况,一个对象属于两个owner,schema的owner可以直接drop其他用户创建的对象,是不是感觉怪怪的?见如下例子:
postgres=# create user u1;
CREATE ROLE
postgres=# create user u2;
CREATE ROLE
postgres=# grant create on database postgres to u1;
GRANT
postgres=# \c postgres u1
You are now connected to database "postgres" as user "u1".
postgres=> create schema myschema;
CREATE SCHEMA
postgres=> \dn+
List of schemas
Name | Owner | Access privileges | Description
----------+----------+----------------------+------------------------
myschema | u1 | |
public | postgres | postgres=UC/postgres+| standard public schema
| | =C/postgres +|
| | myuser=UC/postgres |
repack | postgres | |
(3 rows)
然后授予u2 在 myschema下面的创建权限
postgres=> grant create on schema myschema to u2;
GRANT
postgres=> \c postgres u2
You are now connected to database "postgres" as user "u2".
postgres=> create table myschema.t1(id int);
CREATE TABLE
postgres=> \dt+ myschema.t1
List of relations
Schema | Name | Type | Owner | Persistence | Size | Description
----------+------+-------+-------+-------------+---------+-------------
myschema | t1 | table | u2 | permanent | 0 bytes |
(1 row)
postgres=> \c postgres u1
You are now connected to database "postgres" as user "u1".
postgres=> drop table myschema.t1 ; ---schema的owner u1删掉了u2创建的表
DROP TABLE
---当然也可以更加暴力使用cascade删除整个模式
postgres=> drop schema myschema cascade;
NOTICE: drop cascades to table myschema.t1
DROP SCHEMA
可以看到,myschema的owner u1删除掉了u2创建的表对象。
对于database,也是类似的
postgres=# create user u3;
CREATE ROLE
postgres=# create user u4;
CREATE ROLE
postgres=# alter user u3 createdb;
ALTER ROLE
postgres=# \c postgres u3
You are now connected to database "postgres" as user "u3".
postgres=> create database db1;
CREATE DATABASE
postgres=> \l+ db1
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges | Size | Tablespace | Description
------+-------+----------+-------------+-------------+-------------------+---------+------------+-------------
db1 | u3 | UTF8 | en_US.UTF-8 | en_US.UTF-8 | | 7737 kB | pg_default |
(1 row)
db1=> \c db1 u4
You are now connected to database "db1" as user "u4".
db1=> create table t1(id int);
CREATE TABLE
db1=> \dt+
List of relations
Schema | Name | Type | Owner | Persistence | Size | Description
--------+------+-------+-------+-------------+---------+-------------
public | t1 | table | u4 | permanent | 0 bytes |
(1 row)
db1=> \c db1 u3
You are now connected to database "db1" as user "u3".
db1=> drop table t1; ---虽然无权删表
ERROR: must be owner of table t1
postgres=# \c postgres u3
You are now connected to database "postgres" as user "u3".
postgres=> drop database db1 ; ---那我直接删库
DROP DATABASE
可以看到,虽然我不能直接删除表,但我可以直接删除库,直接掀你的天灵盖
不过仅有删除的权限,要查询里面数据的话还是没有权限,仍然得按照之前说的那样层层赋权。
列级安全很好理解,如果在一些业务场景中,某些列存储敏感信息,需要对用户不可见,但其他列的数据又需要用户能够查看或操作,此时就需要针对数据表的特定列做访问控制,实现针对用户的列级别的访问控制。
针对表的权限,我们可以直接\z + 表名即可看到 Access privileges
1.has_table_privilege() 判断一个用户是否可以用某种特定的方式访问一个表。
2.has_sequence_privilege() 检查一个用户是否能以某种特定方式访问一个序列。
3.has_any_column_privilege() 检查一个用户是否能以特定方式访问一个表的任意列。
4.has_column_privilege() 检查一个用户是否能以特定方式访问一个列。
5.has_database_privilege() 检查一个用户是否能以特定方式访问一个数据库。比如查看哪些库可以被连接:
6.has_function_privilege() 检查一个用户是否能以特定方式访问一个函数。
7.has_schema_privilege() 检查一个用户是否可以以某种特定的方式访问一个模式。
8.has_tablespace_privilege() 检查一个用户是否可以以某种特定的方式访问一个表空间。
MySQL 相对简单,它没有schema,或者准确地说schema是database。笔者就多次见到从MySQL转型过来的,在PostgreSQL下面建了多个database然后又吐槽居然不能跨库访问的,还得用什么dblink,因为PostgreSQL里面的schema的概念对应MySQL的database。
Oracle则不一样,schema严格绑定user,虽然也有create schema的语法,但是作用完全不同。每次为 Oracle 创建用户时,都会自动创建一个schema。
删除跑路想必对于无数DBA来说,再熟悉不过了,那么又没有办法防止删库呢?有,默认是无法删除模板数据库的,所以改一下pg_database的datistemplate字段即可。
当然遇事不决找插件,如下三个插件也都是权限相关。
前面说的那么一大坨,头都晕了,这么绕的权限,那有没有方便的SQL直接查出来呢?有!
CREATE OR REPLACE FUNCTION table_privs(text) RETURNS table(username text, relname regclass, privs text[])
AS
$$
SELECT $1,c.oid::regclass, array(select privs from unnest(ARRAY [
( CASE WHEN has_table_privilege($1,c.oid,'SELECT') THEN 'SELECT' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'INSERT') THEN 'INSERT' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'UPDATE') THEN 'UPDATE' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'DELETE') THEN 'DELETE' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'TRUNCATE') THEN 'TRUNCATE' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'REFERENCES') THEN 'REFERENCES' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'TRIGGER') THEN 'TRIGGER' ELSE NULL END)]) foo(privs) where privs is not null) FROM pg_class c JOIN pg_namespace n on c.relnamespace=n.oid where n.nspname not in ('information_schema','pg_catalog','sys') and c.relkind='r' and
has_table_privilege($1,c.oid,'SELECT, INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER') AND has_schema_privilege($1,c.relnamespace,'USAGE')
$$ language sql;
SELECT grantee AS user, CONCAT(table_schema, '.', table_name) AS table,
CASE
WHEN COUNT(privilege_type) = 7 THEN 'ALL'
ELSE ARRAY_TO_STRING(ARRAY_AGG(privilege_type), ', ')
END AS grants
FROM information_schema.role_table_grants
GROUP BY table_name, table_schema, grantee;
CREATE OR REPLACE FUNCTION database_privs(text) RETURNS table(username text,dbname name,privileges text[])
AS
$$
SELECT $1, datname, array(select privs from unnest(ARRAY[
( CASE WHEN has_database_privilege($1,c.oid,'CONNECT') THEN 'CONNECT' ELSE NULL END),
(CASE WHEN has_database_privilege($1,c.oid,'CREATE') THEN 'CREATE' ELSE NULL END),
(CASE WHEN has_database_privilege($1,c.oid,'TEMPORARY') THEN 'TEMPORARY' ELSE NULL END),
(CASE WHEN has_database_privilege($1,c.oid,'TEMP') THEN 'CONNECT' ELSE NULL END)])foo(privs) WHERE privs IS NOT NULL) FROM pg_database c WHERE
has_database_privilege($1,c.oid,'CONNECT,CREATE,TEMPORARY,TEMP') AND datname not in ('template0');
$$ language sql;
CREATE OR REPLACE FUNCTION tablespace_privs(text) RETURNS table(username text,spcname name,privileges text[])
AS
$$
SELECT $1, spcname, ARRAY[
(CASE WHEN has_tablespace_privilege($1,spcname,'CREATE') THEN 'CREATE' ELSE NULL END)] FROM pg_tablespace WHERE has_tablespace_privilege($1,spcname,'CREATE');
$$ language sql;
CREATE OR REPLACE FUNCTION fdw_wrapper_privs(text) RETURNS table(username text,fdwname name,privleges text[])
AS
$$
SELECT $1, fdwname, ARRAY[
(CASE WHEN has_foreign_data_wrapper_privilege($1,fdwname,'USAGE') THEN 'USAGE' ELSE NULL END)] FROM pg_catalog.pg_foreign_data_wrapper WHERE has_foreign_data_wrapper_privilege($1,fdwname,'USAGE');
$$ language sql;
查看foreign server 权限
CREATE OR REPLACE FUNCTION foreign_server_privs(text) RETURNS table(username text, srvname name, privileges text[])
AS
$$
SELECT $1, s.srvname , ARRAY[
(CASE WHEN has_server_privilege($1,srvname,'USAGE') THEN 'USAGE' ELSE NULL END)] from pg_catalog.pg_foreign_server s WHERE has_server_privilege ($1,srvname,'USAGE');
$$
language sql;
CREATE OR REPLACE FUNCTION language_privs(text) RETURNS table(username text,srvname name, privileges text[])
AS
$$
SELECT $1, l.lanname, ARRAY[(CASE WHEN has_language_privilege($1,lanname,'USAGE') THEN 'USAGE' ELSE NULL END)] FROM pg_catalog.pg_language l where has_language_privilege($1,lanname,'USAGE');
$$ language sql;
CREATE OR REPLACE FUNCTION schema_privs(text) RETURNS table(username text, schemaname name, privileges text[])
AS
$$
SELECT $1, c.nspname, array(select privs from unnest(ARRAY[
( CASE WHEN has_schema_privilege($1,c.oid,'CREATE') THEN 'CREATE' ELSE NULL END),
(CASE WHEN has_schema_privilege($1,c.oid,'USAGE') THEN 'USAGE' ELSE NULL END)])foo(privs) WHERE privs IS NOT NULL)
FROM pg_namespace c where has_schema_privilege($1,c.oid,'CREATE,USAGE');
$$ language sql;
postgres=# select
r.usename as grantor, e.usename as grantee, nspname, privilege_type, is_grantable
from pg_namespace
join lateral (
SELECT
*
from
aclexplode(nspacl) as x
) a on true
join pg_user e on a.grantee = e.usesysid
join pg_user r on a.grantor = r.usesysid
where e.usename = 'postgres';
grantor | grantee | nspname | privilege_type | is_grantable
----------+----------+--------------------+----------------+--------------
postgres | postgres | pg_catalog | USAGE | f
postgres | postgres | pg_catalog | CREATE | f
postgres | postgres | public | USAGE | f
postgres | postgres | public | CREATE | f
postgres | postgres | information_schema | USAGE | f
postgres | postgres | information_schema | CREATE | f
(6 rows)
CREATE OR REPLACE FUNCTION view_privs(text) returns table(username text, viewname regclass, privileges text[])
AS
$$
SELECT $1, c.oid::regclass, array(select privs from unnest(ARRAY [
( CASE WHEN has_table_privilege($1,c.oid,'SELECT') THEN 'SELECT' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'INSERT') THEN 'INSERT' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'UPDATE') THEN 'UPDATE' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'DELETE') THEN 'DELETE' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'TRUNCATE') THEN 'TRUNCATE' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'REFERENCES') THEN 'REFERENCES' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'TRIGGER') THEN 'TRIGGER' ELSE NULL END)]) foo(privs) where privs is not null) FROM pg_class c JOIN pg_namespace n on c.relnamespace=n.oid where n.nspname not in ('information_schema','pg_catalog','sys') and c.relkind='v' and has_table_privilege($1,c.oid,'SELECT, INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER') AND has_schema_privilege($1,c.relnamespace,'USAGE')
$$ language sql;
CREATE OR REPLACE FUNCTION sequence_privs(text) RETURNS table(username text, sequence regclass, privileges text[])
AS
$$
SELECT $1, c.oid::regclass, array(select privs from unnest(ARRAY [
( CASE WHEN has_table_privilege($1,c.oid,'SELECT') THEN 'SELECT' ELSE NULL END),
(CASE WHEN has_table_privilege($1,c.oid,'UPDATE') THEN 'UPDATE' ELSE NULL END)]) foo(privs) where privs is not null) FROM pg_class c JOIN pg_namespace n on c.relnamespace=n.oid where n.nspname not in ('information_schema','pg_catalog','sys') and c.relkind='S' and
has_table_privilege($1,c.oid,'SELECT,UPDATE') AND has_schema_privilege($1,c.relnamespace,'USAGE')
$$ language sql;
然后配合union all,看看效果,这样我就批次查出某个用户拥有的权限了,包括库级、模式、表、视图、序列等。
postgres=# select * from (
select username,'SCHEMA' as object_type,schemaname as object_name,privileges
FROM schema_privs('xiongcc')
UNION ALL
SELECT username,'TABLE' as object_type ,relname::name as object_name ,privs
FROM table_privs('xiongcc')
UNION ALL
SELECT username,'DATABASE' as object_type ,dbname as object_name ,privileges
FROM database_privs('xiongcc')
UNION ALL
SELECT username,'TABLESPACE' as object_type ,spcname as object_name ,privileges
FROM tablespace_privs('xiongcc')
) as text1 order by 2 ;
username | object_type | object_name | privileges
----------+-------------+--------------------+-----------------------------------------------------------
xiongcc | DATABASE | mydatabase1 | {CONNECT,CREATE,TEMPORARY,CONNECT}
xiongcc | DATABASE | template1 | {CONNECT}
xiongcc | DATABASE | benchmarksql | {CONNECT,TEMPORARY,CONNECT}
xiongcc | DATABASE | mydb | {CONNECT,TEMPORARY,CONNECT}
xiongcc | DATABASE | postgres | {CONNECT,TEMPORARY,CONNECT}
xiongcc | SCHEMA | pg_catalog | {USAGE}
xiongcc | SCHEMA | information_schema | {USAGE}
xiongcc | SCHEMA | schema1 | {CREATE,USAGE}
xiongcc | SCHEMA | public | {USAGE}
xiongcc | TABLE | test99 | {SELECT,INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER}
(10 rows)
postgres=# select * from (
SELECT username,'VIEW' as object_type ,viewname as object_name ,privileges
FROM view_privs('xiongcc')
UNION ALL
SELECT username,'SEQUENCE' as object_type ,sequence as object_name ,privileges
FROM sequence_privs('xiongcc')
) as text1 order by 2 ;
username | object_type | object_name | privileges
----------+-------------+--------------------+-----------------------------------------------------------
xiongcc | SEQUENCE | myseq1 | {SELECT,UPDATE}
xiongcc | VIEW | pg_stat_statements | {SELECT}
xiongcc | VIEW | myview1 | {SELECT,INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER}
xiongcc | VIEW | pg_show_plans | {SELECT}
(4 rows)
PostgreSQL中的权限确实有点绕,不过搞清楚之后就不是大问题了。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!