postgresqlsecuritypermissionsrbacabac

Conditional Column-level permissions in PostgreSQL


Let’s create a user and table with some data:

CREATE ROLE admin;
CREATE TABLE employee (empno int, ename text, address text, salary int, account_number text);
INSERT INTO employee VALUES
  (1, 'john'  , '2 down str'   ,  20000, 'HDFC-22001')
, (2, 'clark' , '132 south avn',  80000, 'HDFC-23029')
, (3, 'soojie', 'Down st 17th' ,  60000, 'ICICI-19022')
;

Now, let's create column-level permissions:

postgres=> \c postgres edb
You are now connected to database "postgres" as user "edb".

postgres=# grant select (empno, ename, address) on employee to admin;
GRANT

postgres=# \c postgres admin
You are now connected to database "postgres" as user "admin".
postgres=> select empno, ename, address, salary from employee;
ERROR:  permission denied for table employee
postgres=> select empno, ename, address from employee;

 empno | ename  |    address    
-------+--------+---------------
     1 | john   | 2 down str
     2 | clark  | 132 south avn
     3 | soojie | Down st 17th

Everything is working fine so far.

But is it possible in PostgreSQL to create a more complex rule — rather than completely restricting the admin user from seeing the salary column, for example, to return NULL values for all rows and show the actual value in the salary column only where, for instance, ename = 'clark'?

In other words, instead of returning an error as in the example below:

postgres=> select empno, ename, address, salary from employee;
ERROR:  permission denied for table employee

Return the following result:

 empno | ename  |    address    | salary 
-------+--------+---------------+--------
     1 | john   | 2 down str    |  NULL
     2 | clark  | 132 south avn |  80000 
     3 | soojie | Down st 17th  |  NULL

Most likely, this can be done through a view, but I am specifically interested in achieving this result using simple rules as shown above.

If possible, I would appreciate an example.


Solution

  • The correct way to do that is a view:

    CREATE VIEW emp_view WITH (security_barrier = on) AS
    SELECT empno, ename, address,
           CASE WHEN ename = 'clark'
                THEN salary
           END AS salary
    FROM employee;
    
    GRANT SELECT ON emp_view TO admin;
    

    Then admin can see the data as you want without having direct access to the underlying table.

    security_barrier is necessary to prevent admin from accessing data she shouldn't see. See the documentation for details.