I've generated some create scripts in SSMS (2014) and as part of the script there are several accounts created. We will be using different accounts based on whether the db is deployed to test, preproduction or production.
I would like to make it easy for the installer to change the accounts so I thought it would be a good idea if I could store the login(s) and declare this at the very top of the script, however I'm not sure how to go about this.
Currently I have:
CREATE USER [RAMBOLL\SVC_D-StructureDB] FOR LOGIN [somedomain\someaccount] WITH DEFAULT_SCHEMA=[dbo]
GO
ALTER ROLE [db_datareader] ADD MEMBER [somedomain\someaccount]
GO
ALTER ROLE [db_datawriter] ADD MEMBER [somedomain\someaccount]
GO
How can I declare the "somedomain\someaccount" and then reference it in the SQL code above? I tried but SQL didn't like it.
DECLARE #SomeAccount VARCHAR(255)
SET #SomeAccount = "[somedomain\someaccount]"
CREATE USER #SomeAccount FOR LOGIN #SomeAccount WITH DEFAULT_SCHEMA=[dbo]
GO
ALTER ROLE [db_datareader] ADD MEMBER #SomeAccount
GO
ALTER ROLE [db_datawriter] ADD MEMBER #SomeAccount
GO
These legacy and will-be-removed stored procedures allow parametrisation
sp_grantdbaccess
sp_addrolemember
However, I would consider an IF statement if only a few options. Then dynamic SQL if you need more flexibility
DECLARE #env VARCHAR(10)
SET #env = 'test'
IF #env = 'test'
BEGIN
CREATE USER [somedomain\someaccount] FOR LOGIN [somedomain\someaccount] WITH DEFAULT_SCHEMA=[dbo]
ALTER ROLE [db_datareader] ADD MEMBER [somedomain\someaccount]
ALTER ROLE [db_datawriter] ADD MEMBER [somedomain\someaccount]
END
ELSE IF #env = 'live'
BEGIN
CREATE USER [somedomain\liveaccount] FOR LOGIN [somedomain\liveaccount]WITH DEFAULT_SCHEMA=[dbo]
ALTER ROLE [db_datareader] ADD MEMBER [somedomain\liveaccount]
ALTER ROLE [db_datawriter] ADD MEMBER [somedomain\liveaccount]
END
...
Related
I have a DB with many objects. All are owned by a role called app. This role is currently used by everyone and everything (manual users, rollout scripts, app itself ...).
The goal now is to have a shorter idle_in_transaction_session_timeout for manual users than for the rollout scripts.
I was told that creating a new role for manual users is the way to go. For simplicity, let's say there is only one manual user frank.
The goal would be that frank can create/alter/drop every object he or app created/owns and that app can create/alter/drop every object he or frank created/owns.
This seems difficult in postgres due to the way it works (creator = owner, only owner can alter/drop etc...).
Searching the internet for a solution only brings partial and quite complicated solutions such as event trigger on create table or not forgetting to issue set role always.
What is the best practice here?
*** Update: one suggestion below is to grant app to frank, but that does not do the trick. It causes the scenario that app can not modify objects created by frank:
CREATE ROLE "app" LOGIN;
CREATE ROLE "frank" LOGIN;
GRANT "app" TO "frank";
set role app;
create table created_by_app (id int);
set role frank;
create table created_by_frank (id int);
drop table created_by_app; -- works
set role app;
drop table created_by_frank; -- does not work
-- SQL Error [42501]: ERROR: must be owner of table created_by_frank
Use a single owner, like the "app" role, and let other inherit this role. Something like this:
CREATE ROLE "app" LOGIN;
CREATE ROLE "frank" LOGIN;
GRANT "app" TO "frank";
When "app" is the owner of a table, "frank" can still make changes to this table or even drop this table.
An event trigger could solve the issue that user might forget to set the ownership of a table. The function and the trigger could be something like this:
CREATE OR REPLACE FUNCTION tr_set_table_owner()
RETURNS event_trigger
LANGUAGE plpgsql
AS
$$
DECLARE
_table_name TEXT;
_schema_name TEXT;
_ddl_owner TEXT;
BEGIN
SELECT
objid::regclass
, schema_name
FROM pg_event_trigger_ddl_commands()
WHERE object_type = 'table'
AND command_tag = 'CREATE TABLE'
INTO _table_name, _schema_name;
IF FOUND THEN
_ddl_owner := format('ALTER TABLE %I.%I OWNER TO app;', _schema_name, _table_name);
RAISE NOTICE 'DDL: %', _ddl_owner;
EXECUTE _ddl_owner;
END IF;
END;
$$;
CREATE EVENT TRIGGER name
ON ddl_command_end
EXECUTE FUNCTION tr_set_table_owner();
Any new table created in your database, should now be assigned to the role "app". If you want this for other objects as well, just change the code in the trigger function.
I have a sql script to create a login and user as follows:
USE [master]
GO
CREATE LOGIN [MyLogin] WITH PASSWORD=N'MyPassword',
DEFAULT_DATABASE=[MyNewDatabase],
DEFAULT_LANGUAGE=[us_english],
CHECK_EXPIRATION=OFF,
CHECK_POLICY=OFF
USE [MyNewDatabase]
GO
CREATE USER [MyLogin] FOR LOGIN [MyLogin] WITH DEFAULT_SCHEMA=[MyLogin]
exec sp_addrolemember db_datareader, MyLogin;
exec sp_addrolemember db_datawriter, MyLogin;
GO
CREATE SCHEMA [MyLogin]
GO
However, I only want all of this to happen if the login doesn't exist. If I try and wrap it in a 'IF NOT EXISTS....BEGIN...END' it wouldn't work as the GO statements would override it. I need the 'GO' after the login so it can then be used for the creation of the user. So how can I make sure the login doesn't exist before trying to create both login and user?
SQL Server doesn't support syntax such as CREATE {object} IF NOT EXISTS. It did introduce CREATE OR ALTER syntax in SQL Server 2017, however, that is only for objects where the object CREATE statement must be the only one in the batch.
For objects that don't need that, such as a LOGIN and USER, you can check if the object exists using an IF and NOT EXISTS. For example:
USE master;
GO
IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE [name] = N'MyLogin')
CREATE LOGIN [MyLogin]
WITH PASSWORD=N'MyPassword',
DEFAULT_DATABASE=[MyNewDatabase],
DEFAULT_LANGUAGE=[us_english],
CHECK_EXPIRATION=OFF,
CHECK_POLICY=OFF;
GO
USE [MyNewDatabase];
GO
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE [name] = N'MyLogin')
BEGIN
CREATE USER [MyLogin] FOR LOGIN [MyLogin] WITH DEFAULT_SCHEMA=[MyLogin];
ALTER ROLE db_datareader ADD MEMBER MyLogin; --Don't use sp_addrolemember is has been deprecated for some time
ALTER ROLE db_datawriter ADD MEMBER MyLogin; --Don't use sp_addrolemember is has been deprecated for some time
END;
I am having a slight problem getting permissions to work the way I want them.
I have a role that should generally be allowed to SELECT everywhere, there are a bunch of members to this role. One of them should NOT be allowed to select from a certain table.
I thought this would be possible by granting role membership to the general reader role and revoking SELECT from the restricted table.
It seems the the permissions of the parent role apply and not the specific permissions. Is there a way around this without having to maintain the permissions of the more restricted role or am I applying the role concept in PostgreSQL in a wrong way?
Here's a sample script:
-- as superuser
CREATE DATABASE permission_test;
\c permission_test
CREATE ROLE r_general_select;
CREATE ROLE r_restricted_select IN ROLE r_general_select;
-- set the default permissions
ALTER DEFAULT PRIVILEGES IN SCHEMA "public" GRANT SELECT ON TABLES TO "r_general_select";
CREATE TABLE "open"(
id SERIAL,
payload TEXT
);
insert into "open"(payload) values ('test');
-- covered by default privileges
GRANT SELECT ON "open" TO PUBLIC;
-- Tests
-- this is good
SET ROLE r_general_select;
SELECT * FROM "open";
RESET ROLE;
-- this is good
SET ROLE r_restricted_select;
SELECT * FROM "open";
RESET ROLE;
CREATE TABLE "restricted" (
id SERIAL,
payload TEXT
);
insert into "restricted"(payload) values ('test');
-- the role and it's members should be able to read
GRANT SELECT ON "restricted" TO r_general_select;
-- except for this one!
REVOKE SELECT ON "restricted" FROM r_restricted_select;
-- Tests
-- this is good
SET ROLE r_general_select;
SELECT * FROM restricted;
RESET ROLE;
-- this should barf with a permission violation
SET ROLE r_restricted_select;
SELECT * FROM restricted;
RESET ROLE;
--- CLEANUP
DROP OWNED BY "r_restricted_select" CASCADE;
DROP ROLE r_restricted_select ;
DROP OWNED BY "r_general_select" CASCADE;
DROP ROLE r_general_select ;
In PostgreSQL, role permissions are purely additive. There is no way in such a model to revoke from a descendant, inheriting role a permission granted on the inherited one.
To fix this you need to change your permissions approach and base it on permissions that always occur together. I usually do this by looking at functional dependencies and operational dependencies together.
I would like to have user who can read and insert data to all tables within one schema. I've executed following SQl statements:
CREATE SCHEMA ABC;
CREATE USER MyUser with PASSWORD '12345678';
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA ABC TO MyUser;
When I try to login with this user I am getting exception: Role 'MyUser' does not exists.... What is not correct here?
How to Define Privileges Upon Role Creation
Now, we are ready to recreate the "demo_role" role with altered permissions. We can do this by specifying the permissions we want after the main create clause:
CREATE ROLE role_name WITH optional_permissions;
You can see a full list of the options by typing:
\h CREATE ROLE
We want to give this user the ability to log in, so we will type:
CREATE ROLE demo_role WITH LOGIN;
CREATE ROLE
If we want to get to this state without specifying the "login" attribute with every role creation, we can actually use the following command instead of the "CREATE ROLE" command:
CREATE USER role_name;
The only difference between the two commands is that "CREATE USER" automatically gives the role login privileges.
here or here about PostgreSQL User Administration.
Thanks
UPDATE
We want to give this user the ability to log in, so we will type:
CREATE ROLE demo_role WITH LOGIN;
CREATE ROLE
If we check the attributes \du
demo_role | | {}
my user name was case sensitive. using small letters solves problem.
I know I can define the search_path variable for a login this way:
ALTER ROLE myrole SET search_path=public, foo, bar;
But if I use a lot of logins I will also use groups (groups in pgadmin which are in fact roles). I would like to define the search_path variable on group level, so I remove this previous SET and instead wrote:
ALTER ROLE mygroup SET search_path=public, foo, bar;
And just define login as a member of the group
GRANT mygroup TO myrole
Now, a SHOW search_path; show me only the default search-path "$user",public. Is there a way to define variables for the group level (so with inheritance)?
No you can't do that. The search_path is a variable not a grant-able object.