We are currently transitioning from using sa credentials in our applications (which is stupid).
But I'm breaking my head over the permission system in SQL Server, I have no clue what could be going wrong and no way it just doesn't work.
This is the SQL statement I used:
CREATE SCHEMA [audit]
GO
BEGIN TRAN T1
USE [master]
CREATE LOGIN [pcm-api_access_logs]
WITH PASSWORD = 'verysecretpassword',
DEFAULT_DATABASE = PCM;
USE [PCM]
CREATE USER [access_logs] FOR LOGIN [pcm-api_access_logs] WITH DEFAULT_SCHEMA = [audit]
CREATE USER [audit_agent] WITHOUT LOGIN WITH DEFAULT_SCHEMA = [audit]
CREATE USER [audit] WITHOUT LOGIN WITH DEFAULT_SCHEMA = [audit]
ALTER AUTHORIZATION ON SCHEMA :: [audit] TO [audit];
DENY ALTER,CONTROL ON SCHEMA :: [audit] TO public;
-- REVOKE ALTER,CONTROL ON SCHEMA :: [audit] FROM audit_agent;
-- REVOKE ALTER,CONTROL ON SCHEMA :: [audit] FROM access_logs;
GRANT SELECT ON SCHEMA :: [audit] TO [access_logs] WITH GRANT OPTION;
-- GRANT INSERT ON SCHEMA :: [audit] TO [audit_agent] WITH GRANT OPTION;
CREATE TABLE [audit].[accessLogs]
(
[id] INT PRIMARY KEY IDENTITY(1, 1),
[action] VARCHAR(32) NOT NULL,
[date] DATETIME DEFAULT GETUTCDATE(),
[userId] INT NULL,
[documentId] INT NULL,
[directoryId] INT NULL,
[supplierId] INT NULL,
[message] VARCHAR(200) NOT NULL
)
ALTER AUTHORIZATION ON OBJECT::[audit].[accessLogs] TO [audit];
GO
CREATE PROCEDURE [audit].[uspGetAccessLogEntries]
(#user_id INT = null)
AS
BEGIN
DECLARE #isValid BIT = 0
IF #user_id IS NOT NULL
BEGIN
SET #isValid = 1
END
SELECT
[error] = NULL,
[verified] = #isValid
SELECT *
FROM [audit].[accessLogs]
END
GO
CREATE PROCEDURE [audit].[uspAddAccessLogEntry]
(#action VARCHAR(32),
#message VARCHAR(200),
#user_id INT = NULL,
#document_id INT = NULL,
#directory_id INT = NULL,
#supplier_id INT = NULL)
WITH EXECUTE AS 'audit_agent'
AS
BEGIN
SELECT [current-user] = CURRENT_USER;
INSERT INTO [audit].[accessLogs] ([action], [message], [userId], [documentId], [directoryId], [supplierId])
VALUES (#action, #message, #user_id, #document_id, #directory_id, #supplier_id)
END
GO
TRUNCATE TABLE audit.accessLogs
EXEC [audit].[uspAddAccessLogEntry] 'login', 'user ''vangeyja'' has logged in at {{date}}.', 1
EXEC [audit].[uspGetAccessLogEntries] 1
ROLLBACK TRAN T1
DROP SCHEMA [audit]
GO
The transaction is rolled back and the schema is dropped so you can run it yourself with ease.
When I run this The Insert within the sp [uspAddAccessLogEntry] just runs without problems. Since I have not given the permissions to audit_agent, I expect this to fail running. I already tried putting a DENY on the whole schema for the public role if there was any issue with public cascading permissions.
What I want; create a schema audit which can only be accessed by the audit or sa user (sysadmin role) the user audit_agent should only have INSERT on accessLogs, the user access_logs should only have SELECT on accessLogs.
all other db users should not be able to read write or do anything on the audit schema and its objects.
Users with direct permissions on a stored procedure, view, or function do not need permissions on indirectly referenced objects as long as the object owners are the same. This behavior is called ownership chaining.
You can leverage ownership chaining to limit access to data strictly via stored procedures with no direct access to tables. Users with only execute permissions are limited to the functionality encapsulated by the proc.
Note that normal users have no permissions initially. There is no need to introduce DENY except in special cases where a GRANT was inherited via membership of a role, which is not the case here.
Privileged users are an exception to consider:
sysadmin server role role members
db_owner role members
object owner (inherited from the schema owner by default)
create a schema audit which can only be accessed by the audit or sa
user
I removed EXECUTE AS from the proc as it is not needed.
CREATE SCHEMA audit;
GO
ALTER AUTHORIZATION ON SCHEMA::audit TO audit;
GO
CREATE TABLE [audit].[accessLogs]
(
[id] INT PRIMARY KEY IDENTITY(1, 1),
[action] VARCHAR(32) NOT NULL,
[date] DATETIME DEFAULT GETUTCDATE(),
[userId] INT NULL,
[documentId] INT NULL,
[directoryId] INT NULL,
[supplierId] INT NULL,
[message] VARCHAR(200) NOT NULL
)
ALTER AUTHORIZATION ON OBJECT::[audit].[accessLogs] TO [audit];
GO
CREATE PROCEDURE [audit].[uspGetAccessLogEntries]
(#user_id INT = null)
AS
BEGIN
DECLARE #isValid BIT = 0
IF #user_id IS NOT NULL
BEGIN
SET #isValid = 1
END
SELECT
[error] = NULL,
[verified] = #isValid
SELECT *
FROM [audit].[accessLogs]
END
GO
CREATE PROCEDURE [audit].[uspAddAccessLogEntry]
(#action VARCHAR(32),
#message VARCHAR(200),
#user_id INT = NULL,
#document_id INT = NULL,
#directory_id INT = NULL,
#supplier_id INT = NULL)
AS
BEGIN
SELECT [current-user] = CURRENT_USER;
INSERT INTO [audit].[accessLogs] ([action], [message], [userId], [documentId], [directoryId], [supplierId])
VALUES (#action, #message, #user_id, #document_id, #directory_id, #supplier_id)
END
GO
the user audit_agent should only have INSERT on accessLogs
GRANT EXECUTE ON [audit].[uspAddAccessLogEntry] TO audit_agent;
the user access_logs should only have SELECT on accessLogs.
GRANT EXECUTE ON [audit].[uspGetAccessLogEntries] TO access_logs;
Test permissions:
EXECUTE AS USER = 'audit_agent';
GO
SELECT * FROM audit.accessLogs --select permission denied
EXEC [audit].[uspGetAccessLogEntries] --execute permission denied
EXEC [audit].[uspAddAccessLogEntry] ... --success
GO
REVERT
GO
EXECUTE AS USER = 'access_logs';
GO
SELECT * FROM audit.accessLogs --select permission denied
EXEC [audit].[uspAddAccessLogEntry] ... --execute permission denied
EXEC [audit].[uspGetAccessLogEntries] --success
GO
REVERT
GO
Related
I have a case where SQL Server ownership chaining doesn't seem to work - or am I missing something?
I have two schemas: Schema1 and Schema2.
In Schema1, I only have SELECT permissions, while in Schema2, I only have EXEC permissions.
I call a stored procedure in Schema2 which inserts a record into a table in Schema1.
This works fine (even though I don't have INSERT permissions in Schema1) due to ownership chaining.
Now, when I call another stored procedure which turns off the identity column in the table before inserting I get an error:
Msg 1088, Level 16, State 11, Procedure Schema2.AddRecordWithSpecificId, Line 7 [Batch Start Line 60]
Cannot find the object "Schema1.MyTable" because it does not exist or you do not have permissions.
If I grant ALTER permissions to Schema1, it works fine - but why is that necessary? Why doesn't schema chaining work in this case?
Script that reproduces the problem:
CREATE DATABASE OwnershipChainingTest
GO
USE OwnershipChainingTest
GO
CREATE SCHEMA Schema1 AUTHORIZATION [dbo]
GO
CREATE SCHEMA Schema2 AUTHORIZATION [dbo]
GO
CREATE TABLE Schema1.MyTable
(
Id int IDENTITY(1,1) NOT NULL,
Title varchar(50) NOT NULL
)
GO
CREATE PROCEDURE Schema2.AddRecord
#title nvarchar(100)
AS
BEGIN
INSERT INTO Schema1.MyTable (Title)
VALUES (#title)
END
GO
CREATE PROCEDURE Schema2.AddRecordWithSpecificId
#id int,
#title nvarchar(100)
AS
BEGIN
SET IDENTITY_INSERT Schema1.MyTable ON
INSERT INTO Schema1.MyTable (Id, Title)
VALUES (#id, #title)
SET IDENTITY_INSERT Schema1.MyTable OFF
END
GO
CREATE USER MyUser WITHOUT LOGIN
GO
CREATE ROLE MyRole AUTHORIZATION [dbo]
GO
EXEC sp_addrolemember MyRole, MyUser
GO
-- With this it works: GRANT SELECT, ALTER ON Schema::Schema1 TO MyRole
GRANT SELECT ON Schema::Schema1 TO MyRole
GO
GRANT EXEC ON Schema::Schema2 TO MyRole
GO
EXEC AS user = 'MyUser'
EXEC Schema2.AddRecord 'hello1'
GO
-- This causes an error
EXEC Schema2.AddRecordWithSpecificId 42, 'hello2'
GO
REVERT;
--SELECT CURRENT_USER
SELECT * FROM Schema1.MyTable
USE MASTER
DROP DATABASE OwnershipChainingTest
GO
Ownership chaining applies only to DML. SET IDENTITY_INSERT is essentially a DDL operation, which is why it requires at least ALTER permissions on the table.
A good way to allow minimally privileged users with only execute permissions to run the proc is by signing the proc with a certificate based on a user with ALTER permissions:
--create certificate and sign proc
CREATE CERTIFICATE AddRecordWithSpecificIdCert
ENCRYPTION BY PASSWORD = 'temporary password'
WITH SUBJECT = 'Allow ALTER on Schema1';
ADD SIGNATURE TO Schema2.AddRecordWithSpecificId BY CERTIFICATE AddRecordWithSpecificIdCert WITH PASSWORD = 'temporary password';
--remove ephemeral private key
ALTER CERTIFICATE AddRecordWithSpecificIdCert REMOVE PRIVATE KEY;
--create a user from certificate with the needed permissions
CREATE USER AddRecordWithSpecificIdCertUser FROM CERTIFICATE AddRecordWithSpecificIdCert;
GRANT ALTER ON SCHEMA::Schema1 TO AddRecordWithSpecificIdCertUser;
GO
--this test now works
EXEC AS user = 'MyUser';
GO
EXEC Schema2.AddRecordWithSpecificId 42, 'hello2'
GO
REVERT;
GO
I'm newbie in SQL Server. I create a database and added 3 role in my database roles:
SCHOOL: datareader
BRAND: dataowner
TEACHER: datareader, datawriter
Then I have create a stored procedure which will add a login account to access SQL Server:
ALTER PROC [dbo].[sp_CreateLogin]
#LGNAME VARCHAR(50),
#PASS VARCHAR(50),
#USERNAME VARCHAR(50),
#ROLE VARCHAR(50)
AS
BEGIN
DECLARE #RET INT
EXEC #RET = SP_ADDLOGIN #LGNAME, #PASS,'mydatabase'
IF (#RET =1) -- LOGIN NAME EXIST
RETURN 1
EXEC #RET = SP_GRANTDBACCESS #LGNAME, #USERNAME
IF (#RET =1) -- USER NAME EXIST
BEGIN
EXEC SP_DROPLOGIN #LGNAME
RETURN 2
END
EXEC sp_addrolemember #ROLE, #USERNAME
IF #ROLE = 'SCHOOL'
BEGIN
EXEC sp_addsrvrolemember #LGNAME, 'sysadmin'
EXEC sp_addsrvrolemember #LGNAME, 'SecurityAdmin'
EXEC sp_addsrvrolemember #LGNAME, 'ProcessAdmin'
END
IF #ROLE = 'BRAND'
BEGIN
EXEC sp_addsrvrolemember #LGNAME, 'sysadmin'
EXEC sp_addsrvrolemember #LGNAME, 'SecurityAdmin'
EXEC sp_addsrvrolemember #LGNAME, 'ProcessAdmin'
END
IF #ROLE= 'TEACHER'
BEGIN
EXEC sp_addsrvrolemember #LGNAME, 'ProcessAdmin'
END
END
Now, I want to create a stored procedure to check if LoginName (#LGNAME) already exists as a login account. If this is exists, return the user name (#USERNAME) and role (#ROLE).
I can check if LoginName exists, but I don't know how to get user name and role.
Your code is completely wrong.
Why do you think that IF (#RET =1) -- LOGIN NAME EXIST?
You can get any other error, for example you supply empty or weak password and get
Msg 15116, Level 16, State 1, Line 16 Password validation failed. The
password does not meet Windows policy requirements because it is too
short.
Then you don't understand server roles, at least, sysadmin role.
You said
I create a database and added 3 role in my database roles:
SCHOOL: datareader
BRAND: dataowner
TEACHER: datareader, datawriter
And then your code does:
IF #ROLE = 'SCHOOL'
BEGIN
EXEC sp_addsrvrolemember #LGNAME, 'sysadmin'
EXEC sp_addsrvrolemember #LGNAME, 'SecurityAdmin'
EXEC sp_addsrvrolemember #LGNAME, 'ProcessAdmin'
END
So you wanted the members of SCHOOL to be only db_datareader? But you gave them sysadmin server role.
This means that not only there is no need to add them as members to
SecurityAdmin and ProcessAdmin, there is no need to make them db_datareader since these logins already have all the possible permissions on the server.
I think you should start from studying SQL Server fixed server roles, at least you should understand that sysadmin not only does not need any permission, there is also no way to deny smth to sysadmin. So you should not give sysadmin to anyone, this login can drop your database and exclude you from sysadmin.
I can check if LoginName exists, but I don't know how to get user name
and role
To find the corresponding user you can use the following code:
select name
from sys.database_principals
where suser_sname(sid) = 'your_login';
To check whether it's role member check IS_ROLEMEMBER ('your_role','your_user')
if it returns 1 your_user is a member of your_role.
I have a tool which updates my sql table 'eptrack' with the following information
Server, Instance, userid, access, startdate and expirydate.
every time this table is updated a trigger will initiate a job which in turn connects to the respective server/Instance and grant the requested server roles.
I am able to grant the role via the following query.
exec sp_addsrvrolemember'na\admin_Test1', 'sysadmin'
However when i try to pick this info from the table via the below query,I get an error.
EXEC sp_addrolemember '(select userid from eptrack)' , '(select access from eptrack)'
Could I get a help for a query on granting the server role picked from the table for the userid in the same table
According to sp_addrolemember,this accepts variables as well
-- Syntax for SQL Server and Azure SQL Database
sp_addrolemember [ #rolename = ] 'role',
[ #membername = ] 'security_account'
so you can try something like below
declare #rolename sysname,
#membername sysname
select #rolename='db_Datareader',#membername='test'
EXEC sp_addrolemember #rolename,#membername
The above select can be from your table,if there are multiple results from select this will not work as expected, you may want to have a method which tracks/get only one row at a time
You should use a cursor for going through your table, get 1 row at a time, assign 2 variables and pass them to sp_addsrvrolemember like this:
declare #eptrack table (userid sysname, access sysname);
insert into #eptrack values ('na\admin_Test1', 'sysadmin');
declare #rolename sysname, #membername sysname;
select #rolename = access, #membername = userid
from #eptrack;
exec sp_addsrvrolemember #membername, #rolename;
Thank you for the assistance. I tried the above query as mentioned below
declare #rolename sysname,
#membername sysname
select #rolename=(select access from eptrack),#membername= (select userid from eptrack)
EXEC sp_addrolemember #rolename,#membername
I see an error the user id does not exist. I just inserted the information in the table and executed the query after that.
This worked, thank you. however we are entering the userid and access manually and we need those info picked from the table
declare #eptrack table (userid sysname, access sysname);
insert into #eptrack values ((select userid from eptrack), (select access from eptrack));
declare #rolename sysname, #membername sysname;
select #rolename = access, #membername = userid
from #eptrack;
exec sp_addsrvrolemember #membername, #rolename;
I tried the above one and see an error user id is not a valid login.
wanted to check if I am entering the select command right?
The table will have new rows inserted frequently and the query needs to pick the userid and access from the table every time a new value is inserted.
I have two logins login1 and login2. login1 has sysadmin server role and I want to grant login2 some permissions conditionally under the context of login1.
After some googling I found the function HAS_PERMS_BY_NAME can be used to test if a user (current user) has some permission. So I wrote the following code:
EXECUTE AS LOGIN = 'login2';
IF HAS_PERMS_BY_NAME(NULL, NULL, 'VIEW DEFINITION') != 1
GRANT VIEW ANY DEFINITION TO login2;
IF HAS_PERMS_BY_NAME(NULL, NULL, 'VIEW SERVER STATE') != 1
GRANT VIEW SERVER STATE TO login2;
REVERT;
But I got the following error when I executed it:
Cannot grant, deny, or revoke permissions to sa, dbo, entity owner, information_schema, sys, or yourself.
That's reasonable, because I'm granting permissions under the context of login2, which is not correct. So I modified my code as:
EXECUTE AS LOGIN = 'login2';
IF HAS_PERMS_BY_NAME(NULL, NULL, 'VIEW DEFINITION') != 1
BEGIN
REVERT;
GRANT VIEW ANY DEFINITION TO login2;
END
EXECUTE AS LOGIN = 'login2';
IF HAS_PERMS_BY_NAME(NULL, NULL, 'VIEW SERVER STATE') != 1
BEGIN
REVERT;
GRANT VIEW SERVER STATE TO login2;
END
But still I got the exactly same error message when I executed it. Anyone can help to point me the right direction? Thanks.
CREATE TABLE #temp (permission VARCHAR(20))
EXECUTE AS USER = 'login2';
INSERT INTO #temp SELECT permission_name FROM fn_my_permissions(NULL, 'SERVER') WHERE permission_name = 'VIEW DEFINITION';
REVERT
GO
IF NOT EXISTS (SELECT * FROM #temp)
GRANT VIEW ANY DEFINITION TO login2;
DROP TABLE #temp
GO
CREATE TABLE #temp (permission VARCHAR(20))
EXECUTE AS USER = 'login2';
INSERT INTO #temp SELECT permission_name FROM fn_my_permissions(NULL, 'SERVER') WHERE permission_name = 'VIEW SERVER STATE';
REVERT
GO
IF NOT EXISTS (SELECT * FROM #temp)
GRANT VIEW ANY DEFINITION TO login2;
DROP TABLE #temp
I have created Virtual Private Database (VPD) policy to oe.customers table that if the customer service department users login to the database as an Account Managers they only will be able to see information related to the customers attached to their account and not all customers information (i.e. if the account manager executes following SQL statement select * from customers; he/she will be able to see only customers who are attached to his/her account.
after completion of my VPD if I login as a account manager and search for
Select * from oe.customers
I am getting error message
OCI -22303: type not "OE". "CUST_ADDRESS_TYPE" not found.
however if I search like
select (column) from oe.customer
query successfully returns my values.
below I have given all my sql command which I used for creating my VPD.
Please help me how can I get result for
Select * from oe.customers
as a account manager such as Account1/passowrd
Connect sys/oracle as sysdba
GRANT CREATE SESSION, CREATE ANY CONTEXT, CREATE PROCEDURE, CREATE TRIGGER, ADMINISTER DATABASE TRIGGER TO sysadmin_vpd IDENTIFIED BY password;
GRANT EXECUTE ON DBMS_SESSION TO sysadmin_vpd;
GRANT EXECUTE ON DBMS_RLS TO sysadmin_vpd;
GRANT CREATE SESSION TO Account1 IDENTIFIED BY password;
GRANT CREATE SESSION TO Account2 IDENTIFIED BY password;
GRANT CREATE SESSION TO Account3 IDENTIFIED BY password;
GRANT CREATE SESSION TO Account4 IDENTIFIED BY password;
CONNECT oe/oe
CREATE TABLE oe.Account_mgr (
account_mgr_id NUMBER(6),
account_name VARCHAR2(20));
INSERT INTO oe.Account_mgr VALUES (145, 'Account1');
INSERT INTO oe.Account_mgr VALUES (147, 'Account2');
INSERT INTO oe.Account_mgr VALUES (148, 'Account3');
INSERT INTO oe.Account_mgr VALUES (149, 'Account4');
GRANT SELECT ON oe.customers TO sysadmin_vpd;
GRANT SELECT ON oe.customers TO Account1;
GRANT SELECT ON oe.customers TO Account2;
GRANT SELECT ON oe.customers TO Account3;
GRANT SELECT ON oe.customers TO Account4;
Connect sysadmin_vpd/password
CREATE OR REPLACE CONTEXT CUSTOMER_CTX USING CUSTOMER_CTX _PKG;
CREATE OR REPLACE PACKAGE CUSTOMER_CTX _PKG IS
PROCEDURE SET_ACCNUM;
END;
/
CREATE OR REPLACE PACKAGE BODY CUSTOMER_CTX _PKG IS
PROCEDURE SET_ACCNUM
AS
ACCNUM NUMBER;
BEGIN
SELECT ACCOUNT_MGR_ID INTO ACCNUM FROM OE.ACCOUNT_MGR
WHERE ACCOUNT_NAME = SYS_CONTEXT('USERENV', 'SESSION_USER');
DBMS_SESSION.SET_CONTEXT('CUSTOMER_CTX ', 'ACCOUNT_MGR_ID', ACCNUM);
EXCEPTION
WHEN NO_DATA_FOUND THEN NULL;
END SET_ACCNUM;
END;
/
CREATE TRIGGER SET_ACCNUM_CTX_TRIG AFTER LOGON ON DATABASE
BEGIN
SYSADMIN_VPD.CUSTOMER_CTX _PKG.SET_ACCNUM;
END;
/
SELECT SYS_CONTEXT('CUSTOMER_CTX ', 'ACCOUNT_MGR_ID') ACCNUM FROM DUAL;
CREATE OR REPLACE FUNCTION ACC_MGR (
schema_p IN VARCHAR2,
table_p IN VARCHAR2)
RETURN VARCHAR2
AS
CUSTOMER_PREDICATE VARCHAR2 (400);
BEGIN
CUSTOMER_PREDICATE:= 'account_mgr_id = SYS_CONTEXT(''CUSTOMER_CTX '', ''ACCOUNT_MGR_ID'')';
RETURN CUSTOMER_PREDICATE;
END;
/
BEGIN
DBMS_RLS.ADD_POLICY (
object_schema => 'OE',
object_name => 'CUSTOMERS',
policy_name => 'CUSTOMERS_POLICY',
function_schema => 'SYSADMIN_VPD',
policy_function => 'ACC_MGR',
statement_types => 'SELECT');
END;
/
The type CUST_ADDRESS_TYPE in customers table needs to be given grant separately
GRANT EXECUTE ON CUST_ADDRESS_TYPE TO Account1;
GRANT EXECUTE ON CUST_ADDRESS_TYPE TO Account2;
GRANT EXECUTE ON CUST_ADDRESS_TYPE TO Account3;
GRANT EXECUTE ON CUST_ADDRESS_TYPE TO Account4;
Update1:- In order for other accounts to view details of the oe.customer table we can use the predicate which if null returns all the rows in oe.customer
CREATE OR REPLACE FUNCTION ACC_MGR (
schema_p IN VARCHAR2,
table_p IN VARCHAR2)
RETURN VARCHAR2
AS
CUSTOMER_PREDICATE VARCHAR2 (400);
BEGIN
IF SYS_CONTEXT('CUSTOMER_CTX', 'ACCOUNT_MGR_ID') IS NOT NULL
THEN
CUSTOMER_PREDICATE:= 'account_mgr_id = SYS_CONTEXT(''CUSTOMER_CTX '', ''ACCOUNT_MGR_ID'')';
END IF;
RETURN CUSTOMER_PREDICATE;
END;
/