I have a web app backed by Postgres.
Each web app request should only read/write data for the current logged-in user.
Every table with user data has a user_id column.
I occasionally have bugs where I forget to add user_id = ? to the WHERE clause of an SQL request. To protect against this problem in a general way, I'm looking into Postgres row-level security (article):
Set a policy on every user data table: CREATE POLICY table_policy ON table USING (user_id::TEXT = current_setting('app.user_id'))
In the web app, when a request begins, set the current logged-in user ID on the request's connection: SET app.user_id = ?.
This allows me to completely ignore user_id when writing SELECT and UPDATE requests.
My remaining problem is INSERTs. Is there a way to avoid having to provide user_id on INSERTs?
Just having a look at the manual :
Existing table rows are checked against the expression specified in USING, while new rows that would be created via INSERT or UPDATE are
checked against the expression specified in WITH CHECK
it seems that you just have to add a WITH CHECK clause to your policy in addition of the USING clause, and which will apply to the INSERT and UPDATE statements.
Related
Problem
I want to prevent a database user from discovering other tables through the use of queries like select * from pg_tables; by adding sanitization in the application that executes the query.
I'm busy investigating how to limit access to a PostgreSQL database for queries coming through an application layer where I can still sanitize a query with code. The user does not connect straight to the database. They can execute queries through an application layer (e.g. query is passed through as text and then executed by my application).
I've already enabled Row Security Policies (row level security) and the user is accessing their data through a view, so "access to data" has been solved (I believe). The problem I'm trying to solve now is to prevent them from "seeing" other tables in the database, especially the built-in PG tables.
The only grant the user has is grant select on my_view to my_user_role;
Assumption / attempted solution
My assumption is that a user can't access a table without explicitly writing it into the query, so if I were to look for certain keywords in the query string, I can reject a query without executing it. E.g. if the clause/characters "pg_tables" are anywhere in the query, then I can reject it. But, this feels naive.
const query = "select * from pg_tables;";
const flagged = query.includes("pg_tables");
if (flagged) throw Error("Not allowed!");
// Continue to run the user's query
^ This would work reliably if the only way to access a table like pg_tables is to type that out explicitly.
Question
Is there a way to access a table like pg_tables without naming it explicitly, in PostgreSQL [given the context above]?
An example of a similar situation in javascript is if you have a function fooBar(), then you can access it indirectly by calling global["foo" + "Bar"]() – hence not using the text "fooBar" exactly.
In this example, the second column should not be visible for a member (role) of the group 'user_group', because this column is only required internally to regulate the row level security. however, records can only be deleted if this column is also visible. How can you get around that?
Options that come to mind would be:
just make the second column visible (i.e. selectable), which would
actually be completely superfluous and I want to hide internally as
much as possible
write a function that is called with elevated rights
(security definer), which I want even less.
Are there any other options?
(and especially with deletions I want to use nice things like 'ON DELETE SET NULL' for foreign keys in other tables, instead of having to unnecessarily program triggers for them)
create table test (
internal_id serial primary key,
user_id int not null default session_user_id(),
info text default null
);
grant
select(internal_id, info),
insert(info),
update(info),
delete
on test to user_group;
create policy test_policy on policy for all to public using (
user_id = session_user_id());
RLS just implicitly adds unavoidable WHERE clauses to all queries, it doesn't mess with the roles under which code is evaluated. From the docs:
"Since policy expressions are added to the user's query directly, they will be run with the rights of the user running the overall query. Therefore, users who are using a given policy must be able to access any tables or functions referenced in the expression or they will simply receive a permission denied error when attempting to query the table that has row-level security enabled."
This feature is orthogonal to the granted column permissions. So the public role must be able to view the user_id column, otherwise evaluating user_id = session_user_id() leads to an error. There's really no way around making the column visible.
completely superfluous and I want to hide internally as much as possible
The solution for that would be a VIEW that doesn't include the column. It will even be updatable!
I have created the following materialized query table:
CREATE TABLE SCHEMA.TABLE AS
(SELECT * FROM SCHEMA.TABLEEXAMPLE)
DATA INITIALLY DEFERRED
REFRESH DEFERRED
MAINTAINED BY USER
DISABLE QUERY OPTIMIZATION;
When I execute a REFRESH TABLE SCHEMA.TABLE it get locked for others users to read from it.
Reading this doc from IBM https://www.ibm.com/support/knowledgecenter/en/SSEPGG_9.7.0/com.ibm.db2.luw.sql.ref.doc/doc/r0000977.html
I tried to execute this statement:
REFRESH TABLE SCHEMA.TABLE ALLOW READ ACCESS
But I get the following error: SQL State: 42601 Unexpected keyword ALLOW
What I'm missing on statement? Is there other way to allow read access to materialized query table while it is beign updated?
MQTs on Db2 for IBM i lag behind the functionality available in Db2 for LUW.
I've never bother with them, instead an encoded vector index (EVI) with computed columns meets every need I've every considered. (Note that Db2 LUW doesn't have EVIs)
Per Mao's comment, you might try deleting an recreating the MQT with the following:
CREATE TABLE SCHEMA.TABLE AS
(SELECT * FROM SCHEMA.TABLEEXAMPLE)
DATA INITIALLY DEFERRED
REFRESH DEFERRED
MAINTAINED BY USER
DISABLE QUERY OPTIMIZATION
with NC;
But I think a refresh would still require exclusive access to the MQT.
The only options I can think of for "refreshing" an MQT while it is being used
programmatically , using either triggers on the base tables or perhaps a process that uses SQL to update a few rows at a time.
removing the DISABLE QUERY OPTIMIZATION and not accessing the MQT directly. Instead depend on the optimizer to access it when appropriate. Now you can create a version of it every few hours and the Db should start using the newer version for new queries. Once the older one is no longer being used, you delete it (or REFRESH it)
I am seeking suggestions on methods to trigger the running of code based on specific event occurring.
Basically I need to monitor all inserts into a table and compare a column value against a parameter set in another table.
For example, when a new record is added to the table and the column [Temperature] is greater than 30 (which is a value set in another table). Send an alert email to notify of this situation.
You can create a trigger (special type of stored procedure) that is automatically executed after an insert happened. Documentation for triggers is here: https://technet.microsoft.com/en-us/library/ms189799(v=sql.120).aspx
You will not be able to send an email out of SQL Database though.
Depending on how quick you need the notification after the insert, maybe you could make an insert into yet another table from within the trigger and query this new table periodically (e.g. using a script in Azure automation) and have the email logic outside the database.
I am very much new to db2. My requirement is I just want to remove tracking system on a user table. I mean tracking of all kind of DML operations on a particular table. I read some thing from the google, according to my understanding this can be done by this parameter in (MON_OBJ_METRICS) parameter at db level.does my understanding is worth ?
How can i disable this parameter ? If i disable this parameter at db level, does all the tables under this particular database are quit from tracking ?
On sql server we can do this by set_change_tracking parameter. I am looking for same functionality in db2luw.
Kindly help me out & please excuse me , as i m a kid in db2
MON_OBJ_METRICS (and DFT_MON_TABLE, from your other post) are used for performance monitoring only. The keep track of the number of rows read, inserted, deleted and updated (among other things), but do not track actual data changes.
Change tracking in MS SQL Server is used for data capture to feed replication processes, not monitoring.
In DB2 you can set the DATA CAPTURE table parameter to NONE, but be aware that this is its default value. You can check the current value for a table with the following query:
SELECT datacapture
FROM syscat.tables
WHERE tabschema = <schema>
AND tabname = <tablename>
You can modify the setting using:
ALTER TABLE <schema>.<tablename> DATA CAPTURE NONE
In addition to the DATA CAPTURE option that Ian described, DB2 v10.1 and newer can also track changes made to tables that are enabled for system-period temporal versioning. System-period versioning is not enabled by default, but if it is, every DML change to the table is automatically captured into a designated history table with the same column layout. Unlike SQL Server's retention policy for tracking changes, the DBA is responsible for pruning old row versions out of DB2 temporal history tables.
The SYSCAT.TABLES view will reveal which tables are enabled for system-period versioning:
SELECT tabschema, tabname FROM syscat.tables
WHERE temporaltype IN ( 'B', 'S' )
The following statement will disable whichever temporal features are enabled ( system-period, business-period, or both):
ALTER TABLE <schema>.<tablename> DROP VERSIONING