Current transaction ID in an audit trigger - sql-server

I was looking at storing some form of transaction id from an audit trigger. The solution appeared to be to use sys.dm_tran_current_transaction as in this post SQL Server Triggers - grouping by transactions.
However, I cannot use this because the user account running sql statements will not have the "VIEW SERVER STATE" permission and results in the error:
Msg 297, Level 16, State 1, Line 3
The user does not have permission to perform this action.
Does anyone know of an alternative to this view that will provide a similar transaction id or a way to use "WITH EXECUTE AS" on the trigger to allow selecting from this view.
From my attempts at "WITH EXECUTE AS" it appears that server level permissions are not carried over, which is expected really since it is impersonating a database user.

You can resolve almost any security problem using code signing. Most granular and finely tuned access control, is just a bit on the hard side to understand.
Use EXECUTE AS OWNER on the trigger, create a certificate, sign the trigger, drop the private key (so that noone else can use it to ever sign anything again), export the certificate (public key only), import the certificate in master, create a login derived from the certificate, grant authenticate to this login (in order to extend the database execute as impersonation), then grant view server state to this login. This is bullet proof, perfectly controled priviledge control. If the trigger need to be changed, the signing process (including the cert derived login and grants) have to be done again. From a security point of view, this is desired (you are signing a specific variant of the trigger), from operational point of view is rather a pita, but is manageable.
create table t (i int);
create table audit (transaction_id int);
go
create trigger t_audit_trigger
on t
with execute as owner
after insert, update, delete
as
begin
set nocount on;
insert into audit (transaction_id)
select transaction_id from sys.dm_tran_current_transaction;
if (##ROWCOUNT != 1)
raiserror(N'Failed to audit transaction', 16, 1);
end
go
create certificate t_audit_view_server
encryption by password = 'Password#123'
with subject = N't_audit_view_server'
, start_date = '08/10/2009';
go
add signature to t_audit_trigger
by certificate t_audit_view_server
with password = 'Password#123';
go
alter certificate t_audit_view_server
remove private key;
backup certificate t_audit_view_server
to file = 'c:\temp\t_audit_view_server.cer';
go
use master;
go
create certificate t_audit_view_server
from file = 'c:\temp\t_audit_view_server.cer';
go
create login t_audit_view_server_login
from certificate t_audit_view_server;
go
grant authenticate server to t_audit_view_server_login;
grant view server state to t_audit_view_server_login;
go

From SQL Server 2008, Microsoft introduced sys.dm_exec_requests, which is to deprecate sys.sysprocesses. This view returns the transaction_id, and can be called without granting VIEW SERVER STATE. Like sys.sysprocesses, it returns details for the current process if VIEW SERVER STATE is not granted, and all processes if it is.

Although not directly answering your question, rather than using a custom built auditing framework, in SQL Server 2008 you could make use of the Change Data Capture Technology.
See the following reference from Books Online: http://msdn.microsoft.com/en-us/library/bb522489.aspx
EDIT (Solution, added): Here is a walkthrough of how to create a stored procedure to access the system view, making use of the execute as clause and using impersonation.
USE MASTER;
select * from sys.dm_tran_current_transaction
--Create a login with view server state permissions
CREATE LOGIN ViewServerStateLogin
WITH password = 'Hello123';
CREATE user ViewServerStateLogin;
--Create a login to test the permissions assignment
CREATE LOGIN TestViewServerState
WITH password = 'Hello123';
CREATE user TestViewServerState;
--Test with Login
EXECUTE AS LOGIN = 'TestViewServerState';
--This obviously does not work.
select * from sys.dm_tran_current_transaction
revert;
--Grant view server state permission to the ViewServerStateLogin
GRANT VIEW SERVER state TO ViewServerStateLogin;
--Create a procedure to wrap the call to the system view
CREATE PROCEDURE proc_TestViewServerState
AS
SET NOCOUNT ON;
EXECUTE AS LOGIN='ViewServerStateLogin'
select * from sys.dm_tran_current_transaction
revert;
RETURN(0);
--Assign execute permission to the test accounts
GRANT EXECUTE ON proc_TestViewServerState TO TestViewServerState
--Grant impersonation rights to the test login
GRANT IMPERSONATE ON LOGIN::ViewServerStateLogin TO TestViewServerState
--Test with Procedure
EXECUTE AS LOGIN = 'TestViewServerState';
EXEC proc_TestViewServerState
revert;

Starting with SQL Server 2016, you could use CURRENT_TRANSACTION_ID. According to the docs:
Any user can return the transaction ID of the current session.

Encrypt the stored procedure and don't share the pw for ViewServerStateLogin. Then you get a black box of sufficient density to satisfy the auditors.

Related

SQL Server Permissions to Script Server Logins (But Not Alter Them)

I have a script that tries to recreate part of a database. Part of that is to script out the logins that are used with that database.
I am trying to find a "cannot do harm" level of permission for this to run as.
Basically it needs to be able to see all the server logins to script them out (except passwords of course). But it needs to not have permissions to add, alter or delete anything on the server. Just script.
I looked at the roles and permissions for the server level, and I am not finding anything like that.
Does SQL Server have server level read only permissions for logins?
Does SQL Server have server level read only permissions for logins?
Yes, but it is not for ANY login, it is on a granular level, for each login:
https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-server-principal-permissions-transact-sql
USE master;
GRANT VIEW DEFINITION ON LOGIN::EricKurjan TO RMeyyappan
GO
The downside is that you have to grant view definition for each login and every time a new login is created. For new logins, you could create a server DDL trigger which grants VIEW DEFINITION permission for the newly created login to your login (or better create a custom server role for this)
CREATE TRIGGER ddl_trig_for_create_login
ON ALL SERVER
FOR CREATE_LOGIN
AS
BEGIN
declare #newlogin sysname = quotename(EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','sysname'));
declare #sql nvarchar(200) = 'GRANT VIEW DEFINITION ON LOGIN::'+ #newlogin +' TO public'; --<-- public just for testing.. change public to a custom server role
exec(#sql);
END
GO
Another option (not tested but logically it should work) would be to create a login from a certificate or asymmetric key, add the cert/asym login to the securityadmin role and then sign your script (the one which reads logins) with the cert/asym key.

Service broker - Cross-database SELECTs to insert into local DB?

I'm using Remus' article http://rusanu.com/2006/03/07/call-a-procedure-in-another-database-from-an-activated-procedure/ as a guide.
What I'm trying to do:
I have a Activated Stored Procedure that (within the Activated SP) calls 2 different stored procedures. One of those stored procedures needs to access tables in another database to lookup some data, after which it writes the record locally with the extra info. Since I'm crossing databases, my options are
"trustworthy" (pass, thanks)
"replicate lookup tables to the same database service broker is in"
"get certificates working" (this approach)
So I did the following, but it's still failing with "lock time out exceeded" and "not able to access database... under the current security context.
In the database service broker is running in:
USE database_with_service_broker_and_queue
GO
CREATE CERTIFICATE mysp_Auditing ENCRYPTION
BY PASSWORD = '123457'
with subject = 'god_i_hope_this_works'
ADD SIGNATURE TO OBJECT::myschema.mystoredprocedure
BY CERTIFICATE mysp_Auditing
WITH PASSWORD = '123457'
ALTER CERTIFICATE mysp_Auditing REMOVE PRIVATE KEY
BACKUP CERTIFICATE mysp_Auditing to file = 'c:\mysp_auditing.CER'
Now in the database with the additional tables:
USE db_with_tables_I_need
GO
CREATE CERTIFICATE mysp_Auditing from file = 'c:\mysp_auditing.CER'
CREATE USER mysp_Auditing FROM CERTIFICATE mysp_Auditing
GRANT AUTHENTICATE to mysp_Auditing
GRANT SELECT ON TABLE1 to mysp_Auditing
GRANT SELECT on TABLE2 to mysp_Auditing
I then ENABLE the queue and watch alllll the error messages scroll by. I even tried changing the stored procedure to add EXECUTE AS OWNER, but still doesn't work.
Any help appreciated.
too long to post as comment
not able to access database... under the current security context.
This means the certificate business is not set up correct (I don't blame you, is ungodly hard to pull it off on first try). I would look at these:
make sure 'dbo' is a valid user. Try running EXECUTE AS USER='dbo'; on both DBs involved. If one fails (it means the DB was created by a Windows SID that is no longer valid) run ALTER AUTHORIZATION ON DATABASE:<dbname> TO sa.
make sure you do not change the procedure after you sign it. Any ALTER will silently drop the signature and invalidate your trust chain.
add EXECUTE AS OWNER
The procedure must have an EXECUTE AS clause, but it should not matter much what it is, as long as there is one. You are granting the SELECT permission to the certificate (hence to the signature) so it should work no matter who the procedure impersonates.
For debugging, I recommend you do it by simply running the stored proc manually, from SSMS query window:
USE myactivateddb;
GO
EXECUTE AS USER='dbo'; -- does not matter who, is important to be AS USER
GO
EXEC myotherdb.dbo.myotherproc ...;
GO
REVERT;
GO
This is easier to debug that trying to do it from activated procedure. If this works, then try the same but instead of executing the other DB stored proc, execute the activated procedure from the SSMS query window (w/o truning on activation on the queue).
Looks like metadata lock. If you do any DDL in your activation procedure, like creating an index or manipulating columns, you can expect lock timeouts if multiple instances of activation procedure are in service. Remember that activation procedure is typically a long running transaction, and if it takes a lock on any object, it keeps it locked until the end of transaction.

Is it possible to get "WITH EXECUTE AS " working under "dbo user/Administrator login" permissions (VIEW SERVER STATE needed)?

The problem can be emulated this way:
DECLARE #C1 BIGINT,#C2 BIGINT;
PRINT SUSER_NAME()+ ' '+ USER_NAME();
SELECT #C1=transaction_id from sys.dm_tran_current_transaction (nolock)
PRINT #C1
EXECUTE AS USER = 'dbo';
PRINT SUSER_NAME()+ ' '+ USER_NAME();
SELECT #C2=transaction_id from sys.dm_tran_current_transaction (nolock)
PRINT #C2
REVERT;
Called from 'Administrator' login (db owner, linked to dbo user) returns:
Administrator dbo
2209599
Administrator dbo
Msg 297, Level 16, State 1, Line 7
The user does not have permission to perform this action.
The same user but different results. Why?
Details:
I have a SP that contains select from sys.dm_os_sys_info and therefore this SP's caller should have VIEW SERVER STATE permissions.
I have modified SP header with "WITH EXECUTE AS OWNER". Owner is standard 'dbo' database user linked to 'Administrator' server login. Administrator is a member of sysadmin and have effective permission 'VIEW SERVER STATE', but execution of my modified procedure brings the 'The user does not have permission to perform this action' error... I see there logic since DBO database user by itself doesn't have server permissions (even if Administrator login has). But what next?
I have tried:
GRANT VIEW SERVER STATE TO DBO - doesn't work because of 'dbo is
not a user-defined server role' error.
Create new user 'ServerStateViewer' linked to 'Administrator' - doesn't work becuase of 'the login already has an account under a different user name' (as I understand it - dbo user exits in the dabatabse)
Create new user 'ServerStateViewer' not linked to any login - doesn't work because I can't to add any server permissions to this user.
WITH EXECUTE AS 'Administrator' - doesn't work because of 'Administrator is not a database user' error.
So it seems I'm forced to create new server login. I am interesting, may be there are still other ways to get WITH EXECUTE AS working under dbo user / Administrator login permissions?
You'll need to use code signing. This is not a problem, since code signing is what you should be using to start with to grant permission to procedures... The proper sequence is this:
inspect the procedure code to ensure that you trust it
change the procedure to have an EXECUTE AS OWNER clause
create a certificate with a private key in the SP's database
sign the procedure with the private key of the certificate you created
drop the private key of the certificate (to prevent it from ever being used again)
copy the certificate into the master database
create a login from the certificate
grant AUTHENTICATE SERVER to the certificate derived login
grant any additional priviledge required by the procedure (e.g. VIEW SERVER STATE) to the certificate derived login
For an example see Signing an activate procedure.
Note that during development code signing can be a pain because you will have to re-sign the procedure after every change to it. For dev purposes, do not drop the private key right after signing, so it can be reused.

How do I create a new user in a SQL Azure database?

I am trying to use the following template:
-- =================================================
-- Create User as DBO template for SQL Azure Database
-- =================================================
-- For login <login_name, sysname, login_name>, create a user in the database
CREATE USER <user_name, sysname, user_name>
FOR LOGIN <login_name, sysname, login_name>
WITH DEFAULT_SCHEMA = <default_schema, sysname, dbo>
GO
-- Add user to the database owner role
EXEC sp_addrolemember N'db_owner', N'<user_name, sysname, user_name>'
GO
I would like to create a user called user1 with a password of 'user1pass'. I connected with my default database 'authentication' and I have a query window open.
But the template does not make sense for me. For example what's sysname, where do I supply the password and what should I use as the default_schema?
The particular user needs to have the power to do everything. But how do I set it up so he can do everything, is that done if I make the user a database owner?
So far I have tried:
CREATE USER user1, sysname, user1
FOR LOGIN user1, sysname, user1
WITH DEFAULT_SCHEMA = dbo, sysname, dbo
GO
Giving:
Msg 102, Level 15, State 1, Line 1 Incorrect syntax near ','.
and:
CREATE USER user1
FOR LOGIN user1
WITH DEFAULT_SCHEMA = dbo
GO
Giving:
Msg 15007, Level 16, State 1, Line 1 'user1' i
s not a valid login or you do not have permission.
Edit - Contained User (v12 and later)
As of Sql Azure 12, databases will be created as Contained Databases which will allow users to be created directly in your database, without the need for a server login via master.
Sql (standard) User
CREATE USER [MyUser] WITH PASSWORD = 'Secret';
ALTER ROLE [db_datareader] ADD MEMBER [MyUser]; -- or sp_addrolemember
AAD linked User
CREATE USER [SomeUser#mydomain.com] FROM EXTERNAL PROVIDER;
EXEC sp_addrolemember 'db_datareader' , N'SomeUser#mydomain.com'
AAD linked Group
CREATE USER [SomeGroup] FROM EXTERNAL PROVIDER;
EXEC sp_addrolemember 'db_datareader' , N'SomeGroup'
NB! when connecting to the database when using a contained user that you must always specify the database in the connection string.
Traditional Server Login - Database User (Pre v 12)
Just to add to #Igorek's answer, you can do the following in Sql Server Management Studio:
Create the new Login on the server
In master (via the Available databases drop down in SSMS - this is because USE master doesn't work in Azure):
create the login:
CREATE LOGIN username WITH password=N'password';
Create the new User in the database
Switch to the actual database (again via the available databases drop down, or a new connection)
CREATE USER username FROM LOGIN username;
(I've assumed that you want the user and logins to tie up as username, but change if this isn't the case.)
Now add the user to the relevant security roles
EXEC sp_addrolemember N'db_owner', N'username'
GO
(Obviously an app user should have less privileges than dbo.)
Check out this link for all of the information : https://azure.microsoft.com/en-us/blog/adding-users-to-your-sql-azure-database/
First you need to create a login for SQL Azure, its syntax is as follows:
CREATE LOGIN username WITH password='password';
This command needs to run in master db. Only afterwards can you run commands to create a user in the database. The way SQL Azure or SQL Server works is that there is a login created first at the server level and then it is mapped to a user in every database.
HTH
I followed the answers here but when I tried to connect with my new user, I got an error message stating "The server principal 'newuser' is not able to access the database 'master' under the current security context".
I had to also create a new user in the master table to successfully log in with SSMS.
USE [master]
GO
CREATE LOGIN [newuser] WITH PASSWORD=N'blahpw'
GO
CREATE USER [newuser] FOR LOGIN [newuser] WITH DEFAULT_SCHEMA=[dbo]
GO
USE [MyDatabase]
CREATE USER newuser FOR LOGIN newuser WITH DEFAULT_SCHEMA = dbo
GO
EXEC sp_addrolemember N'db_owner', N'newuser'
GO
You can simply create a contained user in SQL DB V12.
Create user containeduser with password = 'Password'
Contained user login is more efficient than login to the database using the login created by master. You can find more details # http://www.sqlindepth.com/contained-users-in-sql-azure-db-v12/
I use the Azure Management console tool of CodePlex, with a very useful GUI, try it. You can save type some code.
1 Create login while connecting to the master db
(in your databaseclient open a connection to the master db)
CREATE LOGIN 'testUserLogin' WITH password='1231!#ASDF!a';
2 Create a user while connecting to your db (in your db client open a connection to your database)
CREATE USER testUserLoginFROM LOGIN testUserLogin;
Please, note, user name is the same as login. It did not work for me when I had a different username and login.
3 Add required permissions
EXEC sp_addrolemember db_datawriter, 'testUser';
You may want to add 'db_datareader' as well.
list of the roles:
https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/database-level-roles?view=sql-server-ver15
I was inspired by #nthpixel answer, but it did not work for my db client DBeaver.
It did not allow me to run USE [master] and use [my-db] statements.
https://azure.microsoft.com/en-us/blog/adding-users-to-your-sql-azure-database/
How to test your user?
Run the query bellow in the master database connection.
SELECT A.name as userName, B.name as login, B.Type_desc, default_database_name, B.*
FROM sys.sysusers A
FULL OUTER JOIN sys.sql_logins B
ON A.sid = B.sid
WHERE islogin = 1 and A.sid is not null
List of all users in Azure SQL
create a user and then add user to a specific role:
CREATE USER [test] WITH PASSWORD=N'<strong password>'
go
ALTER ROLE [db_datareader] ADD MEMBER [test]
go
I found this link very helpful:
https://azure.microsoft.com/en-gb/documentation/articles/sql-database-manage-logins/
It details things like:
- Azure SQL Database subscriber account
- Using Azure Active Directory users to access the database
- Server-level principal accounts (unrestricted access)
- Adding users to the dbmanager database role
I used this and Stuart's answer to do the following:
On the master database (see link as to who has permissions on this):
CREATE LOGIN [MyAdmin] with password='ReallySecurePassword'
And then on the database in question:
CREATE USER [MyAdmin] FROM LOGIN [MyAdmin]
ALTER ROLE db_owner ADD MEMBER [MyAdmin]
You can also create users like this, according to the link:
CREATE USER [mike#contoso.com] FROM EXTERNAL PROVIDER;
I think the templates use the following notation: variable name, variable type, default value.
Sysname is a built-in data type which can hold the names of system objects.
It is limited to 128 Unicode character.
-- same as sysname type
declare #my_sysname nvarchar(128);

Sql server execute permission; failure to apply permissions

I've just migrated from SQL2000 to SQL2008 and I have started getting an execute permission issue on a stored proc which uses sp_OACreate.
The rest of the system works fine with the db login which has been setup and added to the database.
I've tried:
USE master
GO
GRANT EXEC ON sp_OACreate TO [dbuser]
GO
But this fails with the following error:
Msg 15151, Level 16, State 1, Line 1
Cannot find the user 'dbuser', because
it does not exist or you do not have
permission.
I'm logged into the server as sa with full permissions. I can execute a similar sql statement and apply the permissions to a server role, however not a login/user.
How do I apply the changes to the specific user/login?
I can apply the permissions to the public role and it resolves my issue; however this seems to be a security issue to me which I don't really want to apply to the live server.
Leading on from John's answer I checked the user listings on the Master database and my user wasn't there. Whether it had been deleted or lost some how I don't know. Something may have gone crazy with the migration of the dbs to the new server instance.
Anyway; re-creating the user and associating it to the specific login enabled me to run the following statements on the master database to allow for the execution of the stored procs.
USE MASTER
GO
GRANT EXECUTE ON [sys].[sp_OADestroy] TO [dbuser]
GO
GRANT EXECUTE ON [sys].[sp_OACreate] TO [dbuser]
GO
GRANT EXECUTE ON [sys].[sp_OAMethod] TO [dbuser]
GO
GRANT EXECUTE ON [sys].[sp_OASetProperty] TO [dbuser]
GO
Thanks for all the help and pointers. Hope this helps other people in the future.
The error suggests that the User "dbuser" does not exist in the master database.
I assume the user exists within the master database?
You can check by using the following T-SQL
USE MASTER;
GO
SELECT *
FROM sys.sysusers
WHERE name = 'dbuser'
If the user turns out not to exist, simply use the CREATE USER statement and create a user called "dbuser". The user will automatically be mapped to a Login of the same name, provided one exists.
Your problem could be related to orphaned users.
Try
USE MASTER
GO
EXEC sp_change_users_login 'Report'
This will return one row per orphaned user name. Then,
EXEC sp_change_users_login 'Update_One', 'dbuser', 'dbuser'
Here is some code I'm using the verify that (current user) has EXECUTE permission on sp_OACreate etc:
use master;
select state_desc,name from
sys.database_permissions a
left join
sys.all_objects b
on a.major_id = b.object_id
where name like 'sp_OA%';
As pointed out by #John Sansom and #WestDiscGolf the user has to exist in the Master database and the execution rights must granted in the Master database, hence use Master is required. The query above will return records if the user has execute permissions and empty set if they do not. (Execution in the user database will also return empty set.)
I couldn't figure out a way check these permissions using fn_my_permissions, which is supposedly the right tool for jobs like this.
Check if your user has permissions for the database you use. You can do this by Security -> Logins -> Select User and open the properties window. Then select "User Mapping" from the right menu. Now check the databases that you want the given user to have access to. After that select from the bottom part of the window "Database role membership" and check "db_owner". Now the user will be the owner of the database and will be able to execute queries, store procedures and so on.
UPDATE:
Add user for the database by selecting your database -> security -> users -> right click "New User"
Or you can use this query
CREATE LOGIN AbolrousHazem
WITH PASSWORD = '340$Uuxwp7Mcxo7Khy';
USE AdventureWorks2008R2;
CREATE USER AbolrousHazem FOR LOGIN AbolrousHazem;
GO
Here are more details http://msdn.microsoft.com/en-us/library/ms173463.aspx

Resources