How to Grant Permission to IMPERSONATE any other user? - sql-server

In order to log the usage of an application I am developing I need every user using my application to execute queries against my SQL Server Database under their own credentials.
In order to not be storing their passwords in a retrievable fashion, I can't be creating a connection on a per-user basis (because that would entail knowing their password past the brief window when they log-in).
The, seemingly obvious, solution to this problem (which may be sub-optimal) is to run all sensitive queries as a generic "Application" user, impersonating the logged in user (requiring me to only associate the logged in user with a username...which is not a bad thing).
My problem is that I'm not sure how to grant impersonate to all users of a certain role, or all users in general (not the brightest idea, because I don't want the app impersonating a sysadmin, for instance).
grant impersonate on all to ApplicationLogin
doesn't work, and there's no documentation that I can find that suggests granting impersonation on members of a role would be doable...
Any ideas?

You can use Dynamic sql .
the code below fetches all users related to a specific role and then grant permission impersonate on a user.
You should create a user on application login to relate it with database , then grant permission to impersonate on all members of specific role.
This is the code:
CREATE TRIGGER S2
ON DATABASE
FOR CREATE_USER
AS
CREATE TABLE #T
(PRINCIPAL_NAME NVARCHAR(100),ROLE_NAME NVARCHAR(100));
WITH L AS (SELECT *
FROM (SELECT P.name AS 'PRINCIPAL_NAME',R.role_principal_id AS 'GH'
FROM SYS.database_principals P,sys.database_role_members R
WHERE P.principal_id=R.member_principal_id OR P.principal_id=R.role_principal_id
AND type<>'R') S INNER JOIN (SELECT P.name AS 'ROLE_NAME',P.principal_id AS 'GHA'
FROM SYS.database_principals P,sys.database_role_members R
WHERE P.principal_id=R.member_principal_id OR P.principal_id=R.role_principal_id
AND type='R') D
ON D.GHA=S.GH)
INSERT INTO #T
SELECT DISTINCT PRINCIPAL_NAME,ROLE_NAME
FROM L
------------ ENTER ROLE NAME HERE
WHERE ROLE_NAME LIKE '%%'
------------
DECLARE #P NVARCHAR(100),#TEXT NVARCHAR(MAX)=''
------------------------- CHANGE IT TO YOUR DESIRED NAME OF YOUR APPLICATION USER
DECLARE #APPUSER NVARCHAR(100)='APPLICATION_USER'
-------------------------
DECLARE C CURSOR FOR SELECT PRINCIPAL_NAME FROM #T
OPEN C
FETCH NEXT FROM C INTO #P
WHILE(##FETCH_STATUS=0)
BEGIN
SET #TEXT+='GRANT IMPERSONATE ON USER::['+#P+'] TO '+#APPUSER+' '
FETCH NEXT FROM C INTO #P
END
CLOSE C
DEALLOCATE C
DROP TABLE #T
EXEC(#TEXT)
I hope it work for You.

You could create your users via a stored procedure. The last line of your stored procedure would be to grant impersonation.
To set up the current users, you would have to cycle through them all and set the grant impersonation this one time.

Related

SQL Server: Is it possible to synchronize user GUIDs between two databases if the users are not tied to a login?

Our project uses SQL Server database users without logins to implement security and row-level filtering.
We have implemented a very basic form of mirroring in which the transactional database is backed up and restored nightly to a second “mirror” copy. A web service pulls data from the mirror.
However, we need to log web service requests in the transactional database so that they are not wiped out when the next mirror is restored.
We attempted to implement this by replacing the log tables in the mirror with synonyms pointing at the "real" tables in the transactional database.
However, attempts to write to the synonyms invariably fail with error messages such as:
The server principal "" is not able to access the database "" under the current security context
I’m guessing this happens because, during restore, the users are re-created and assigned new GUIDs?
I've found lots of answers that talk about re-connecting a database user to a SQL Server login using sp_change_users_login or ALTER USER, but these solutions don't seem applicable, since these database users don't have logins.
Is there some way to ensure that users in the mirrored database are recognized as the same user in the transactional database if their usernames are the same?
Thanks!
Yes, when you CREATE a USER (or LOGIN) you can define the SID when you create it:
USE DB1;
GO
CREATE USER SampleUser WITHOUT LOGIN WITH SID = 0x010500000000000903000000F759D99F7F71EC459908C0A30B39056C;
USE DB2;
GO
CREATE USER SampleUser WITHOUT LOGIN WITH SID = 0x010500000000000903000000F759D99F7F71EC459908C0A30B39056C;
Is there some way to ensure that users in the mirrored database are
recognized as the same user in the transactional database if their
usernames are the same?
The answer is:No
Users without logins are database principals only. For any users, in different dbs, to be "one" they need to "point" to the same server principal: login. It is the login that "connects" database users.
I’m guessing this happens because, during restore, the users are
re-created and assigned new GUIDs?
The SIDs (Security IDentifiers) of the database users do not change when the db is restored.
The fact that the sids do not change but remain the same, requires the actions you have found (sp_change_users_login or ALTER USER) when a database is restored to another server.
3 options for cross database actions:
cross database ownership chaining
impersonation
module signing
I cannot tell if crossdb chaining will work, since the loginless users do not have any security essence when they leave their database. Outside their database, loginless users have no security attributes, they lose their "identity".
On the other hand, impersonation and module signing can work, because the security attributes are warranted by another principal.
Following is a "simplistic" poc, for db users without login, performing cross db actions with sql-modules signed by an asymmetric key. There are plenty of examples online for module signing with certificates and the workings are the same. Most articles you'll find online, execute the signed modules in the scope of a server principal (exec as login, exec signed_module) while the loginless users have no server level existence.
Signed modules need to be executed by a trusted entity for the signature to take effect(a login is by definition trusted) and for loginless users their trustiness comes from their own db. For that, the db where the loginless users reside must be TRUSTWORTHY ON.
In the poc, there is a signed multiline table valued function in the mirrordb which is used for selecting data from the transactional db. The function is the base for a view (views cannot be signed). The view is the target of DML. The actual dml action(s) are performed by signed INSTEAD OF triggers.
You need to generate&adjust the path to the asymmetric key before executing the script.
Impersonation could work the same way(take away the signing related actions and create each module with EXECUTE AS)
create database transactionaldb
go
create database mirrordb
go
--target table in transactionaldb
use transactionaldb
go
create table targettable
(
id int identity,
username nvarchar(128) default(user_name()),
somevalue varchar(10),
thedate datetime default(getdate())
)
go
--mirror db, userwithout login
use mirrordb
go
create user userwithoutlogin without login
go
--synonym, just for testing
create synonym targettablesynonym for transactionaldb..targettable
go
--for testing, grant permissions on synonym to loginless user
grant select, insert, update, delete on targettablesynonym to userwithoutlogin
go
--switch to loginless user and select from synonym
execute as user = 'userwithoutlogin'
go
select * from targettablesynonym --The server principal "S-1-2-3-4..." is not able to access the database "transactionaldb" under the current security context.
go
--revert
revert
go
/*
1) cross db ownership chaining
2) impersonation
3) module signing (below)
*/
--module signing, asymmetric key
--create a strong key/name file using sn.exe : sn -k 2048 c:\testdata\asymkeytest.snk //use key size 2048 for sql2016 on.
--the same for transactionaldb
use transactionaldb
go
--master key transactionaldb
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'M#st3rkeyTransactional'
GO
CREATE ASYMMETRIC KEY asymkeytest
AUTHORIZATION dbo
FROM FILE = 'c:\testdata\asymkeytest.snk';
GO
create user asymkeytransactionaldbuser from asymmetric key asymkeytest
go
--grant permissions on targettable to asymkeytransactionaldbuser
grant select, insert, update, delete on targettable to asymkeytransactionaldbuser;
go
use mirrordb
go
--master key mirrordb
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'M#st3rkeyMirror'
GO
CREATE ASYMMETRIC KEY asymkeytest
AUTHORIZATION dbo
FROM FILE = 'c:\testdata\asymkeytest.snk';
GO
--a db user from the asymkey? not really needed
--create user asymkeymirrordbuser from asymmetric key asymkeytest
go
select is_master_key_encrypted_by_server, *
from sys.databases
where name in ('transactionaldb', 'mirrordb');
go
use mirrordb
go
--a function in mirror which reads the table from transactional
create or alter function dbo.fnreadtargettablefromtransactionaldb()
returns #result table
(
id int,
username nvarchar(128),
somevalue varchar(10),
thedate datetime
)
as
begin
insert into #result(id, username, somevalue, thedate)
select id, username, somevalue, thedate
from transactionaldb.dbo.targettable;
return;
end
go
--grant select on loginless user
grant select on fnreadtargettablefromtransactionaldb to userwithoutlogin;
go
--switch to loginless user and select from function
execute as user = 'userwithoutlogin'
go
select * from fnreadtargettablefromtransactionaldb(); --The server principal "S-1-2-3-4..." is not able to access the database "transactionaldb" under the current security context.
go
--revert
revert
go
--sign the function with the asymmetric key
add signature to fnreadtargettablefromtransactionaldb by ASYMMETRIC KEY asymkeytest;
--... after signing
execute as user = 'userwithoutlogin'
go
select * from fnreadtargettablefromtransactionaldb(); --The server principal "S-1-2-3-4..." is not able to access the database "transactionaldb" under the current security context.
go
--revert
revert
go
--the signed module/function is accessing the transactionaldb but it is NOT trusted
--it could be trusted if:
-- a. it was called in the context of a server principal (login, by definition it is trusted)
-- b. if the source db of the signed module is trustful
--make the source db of the signed module trustful
alter database mirrordb set trustworthy on;
--test again
execute as user = 'userwithoutlogin'
go
--synonym (needs permissions now, at the destination)
select * from targettablesynonym
--signed function, working, permissions from the asymmetric key
select * from fnreadtargettablefromtransactionaldb(); --works
go
--revert
revert
go
use mirrordb
go
--complete interface
create or alter view transactiontableview
as
select *
from fnreadtargettablefromtransactionaldb()
go
--view permissions
grant select, insert, update, delete on transactiontableview to userwithoutlogin;
go
--instead of insert trigger
create or alter trigger insteadofinsertonview on transactiontableview instead of insert
as
begin
set nocount on;
if not exists(select * from inserted)
begin
return;
end
insert into transactionaldb.dbo.targettable(username, somevalue, thedate)
select user_name(), somevalue, thedate
from inserted;
end
go
--sign
add signature to insteadofinsertonview by ASYMMETRIC KEY asymkeytest;
go
execute as user = 'userwithoutlogin'
go
insert into transactiontableview(somevalue)
select col
from (values('one'), ('2'), ('3')) as t(col);
select * from transactiontableview
go
--revert
revert
go
/***********************************************/
--check the security token of a signed module when db trustworthy is off
alter database mirrordb set trustworthy off;
go
--create a proc
create or alter procedure showsecuritytokens
as
begin
select 'mirror_db' as dbname, *
from mirrordb.sys.user_token
union all
select 'transactional_db' as dbname, *
from transactionaldb.sys.user_token;
end
go
--sign the proc for accessing transactionaldb info
add signature to showsecuritytokens by ASYMMETRIC KEY asymkeytest;
go
--permissions
grant execute on showsecuritytokens to userwithoutlogin
go
--enable guest for transactionaldb
use transactionaldb
go
grant connect to guest
go
use mirrordb
go
--switch to loginless user
execute as user = 'userwithoutlogin'
go
exec showsecuritytokens
--when from an untrusted source: the signed module does not get the GRANTs of the asymmetric key (at the destination)
/*
thedb_______________principal_id__name_________________________type___________________________usage
transactional_db____5_____________asymkeytransactionaldbuser___USER MAPPED TO ASYMMETRIC KEY__DENY ONLY
*/
go
--revert
revert
go
--cleanup
/*
use master
go
drop database transactionaldb
go
drop database mirrordb
go
*/

GRANT SELECT ON sys.server_principals to usersys role. How?

I can't find an answer to this. Problem is that I need to use master database and then I don't know how to specify that I am granting this select to a role in another database. When using "use master" it doesn't work because the principal is in another database and you can't add e.g. Database.dbo.role prefixes to principles. How do I do that? Even granting through SSMS UI doesn't work.
I need this: GRANT SELECT ON Syf.sys.server_principals to Syf.dbo.usersys
What am I missing? Am I thinking about this in a wrong way?
Even when I try it with the user on master database and call "execute as user = 'user'" and then select from sys.server_principals it still return only a few records. I apparently don't understand how these permissions work, it's beyond my logic. It seems there are some other objects that I need to grant permissions for.
I need to use "with execute as 'privilegedUser'", when I do that then we are in user context of that SP database and that user can't have permission to access sys.server_principals.? I need this because that SP deletes user and login if login with the same name exists. The reason I need to execute with the privileged user is because my database has multitenancy and every user is bound to one TenantId and when he or other user goes to delete the user, security policy complains that he has no right for that.
I found a way to do this, I have to grant select to sys.server_principals to guest user on master db, that gives guest some more privileges.
From the documentation on sys.server_principals, the needed permissions are:
Any login can see their own login name, the system logins, and the fixed server roles. To see other logins, requires ALTER ANY LOGIN, or a permission on the login. To see user-defined server roles, requires ALTER ANY SERVER ROLE, or membership in the role.
The visibility of the metadata in catalog views is limited to securables that a user either owns or on which the user has been granted some permission. For more information, see Metadata Visibility Configuration.
But all is not lost. We can use module signing to create a stored procedure that can allow you to do what you need to do.
use master;
create master key encryption by password = 'an unguessable password!'
alter master key add encryption by service master key
create certificate [CodeSigningCert]
with expiry_date = '2018-12-31',
subject = 'Code signing'
go
create login [CodeSigningLogin] from certificate [CodeSigningCert]
grant alter any login to [CodeSigningLogin]
go
SELECT 'CREATE CERTIFICATE ' + QUOTENAME([name])
+ ' AUTHORIZATION ' + USER_NAME([c].[principal_id])
+ ' FROM BINARY = ' + CONVERT(VARCHAR(MAX), CERTENCODED([c].[certificate_id]), 1)
+ ' WITH PRIVATE KEY (BINARY = '
+ CONVERT(VARCHAR(MAX), CERTPRIVATEKEY([c].[certificate_id], 'f00bar!23'), 1)
+ ', DECRYPTION BY PASSWORD = ''f00bar!23'')'
FROM [sys].[certificates] AS [c]
WHERE [name] = 'CodeSigningCert'
use tempdb
go
create master key encryption by password = 'foobar!23'
-- c/p the create certificate code generated above here
-- to create the same certificate in tempdb
create user CodeSigningUser from certificate CodeSigningCert
go
create login [foobar] with password = 'foobar!23'
create user [foobar]
go
create procedure dbo.listServerPrincipals
as
begin
select *
from sys.server_principals
end
go
grant execute on dbo.listServerPrincipals to foobar
go
execute as login = 'foobar'
go
exec dbo.listServerPrincipals
revert
go
add signature to dbo.listServerPrincipals by certificate [CodeSigningCert]
go
execute as login = 'foobar'
go
exec dbo.listServerPrincipals
revert
go
It seems like a lot, but in essence you're doing the following:
creating a certificate in master
creating a login from that certificate
creating that same certificate in your user database (I used tempdb as a standin here)
create a user for that certificate
create a login/user to represent your application user
create a procedure that does the select
try to execute it as the app login. it "works", but looks no different than if you'd done the select yourself
add a signature to the procedure
try to execute the procedure again. this time, it should return all the data
I've figured out a solution where I don't need to use "with execute as owner" in this first procedure, but in the second one that this first one calls. In the first one, I can select everything I need from sys tables and pass the info to a second one that has "with execute as owner" which is in the schema forbidden to a user.
Even better solution:
alter trigger [dbo].[AfterInsertUpdateTenant_Korisnici]
on [dbo].[Korisnici]
with execute as owner
for insert
as
execute as user = original_login();
declare #TenantId int = dbo.GetCurrentTenantId();
revert;
update dbo.Korisnici
set TenantId = #TenantId
from Inserted i
where dbo.Korisnici.Id = i.Id;

Sybase - How to create read-only user only can SELECT all tables?

I need to create an user which only can SELECT all tables in Sybase database.
The way is :
1. Create role which can SELECT all tables.
2. Grant role to this user.
My Sybase version is : Adaptive Server Enterprise/15.0.2
create a readonly role
grant only the select permission to the new role
add the user to that role
While the other answer is technically correct, it omits a crucial detail: you cannot simply grant the permissions for all tables to a single user or group - you have to specify all table (and procedure, view etc.) names yourself. Wildcards do not work and result in generic and not very helpful Error -131: Syntax error messages.
I've adapted a very good workaround by marc_s from SQL Server 2008 for Sybase (tested on SYL Anywhere, but should work on ASE too, if the documentation is correct).
It could of course be improved to be fully automatic and account for changed tables, but if you just want to quickly give some users read-only access to a relatively static database (be it the sales department or an application), then this just works well enough:
Create a user (you could also create a group with CREATE ROLE):
CREATE USER readonly IDENTIFIED BY 'secretpassword';
Check which tables you need to include (if you want to exclude the system tables, for example). To do that, look into systable for all tables/views/procedures and sysuserperm for user details:
SELECT
t.table_id,
t.table_name,
t.table_type,
t.creator,
u.user_name
FROM systable AS t
JOIN sysuserperm AS u ON t.creator = u.user_id
ORDER BY t.table_id ASC;
Depending on your data and needs, you can modify the query to just return the table_names that you need. In this example we get all views and tables that are owned by DBA (owner = schema). Now create the GRANT statements:
SELECT
t.table_name,
GrantCmd = 'GRANT SELECT ON dba.' + t.table_name + ' TO readonly;',
RevokeCmd = 'REVOKE SELECT ON dba.' + t.table_name + ' FROM readonly;'
FROM systable AS t
WHERE t.creator = 1
AND (t.table_type = 'BASE' OR t.table_type = 'VIEW');
Copy the resulting statements, wrap them in a transaction and execute it:
BEGIN TRANSACTION;
GRANT SELECT ON dba.table1 TO readonly;
GRANT SELECT ON dba.table2 TO readonly;
GRANT SELECT ON dba.table3 TO readonly;
<...>
COMMIT TRANSACTION;
Now you can execute SELECT statements. You have to manually add the context to the tables though:
SELECT * from table1; -- Error -141: table not found
SELECT * from dba.table1; -- works as expected
DELETE FROM dba.table1; -- Error -121: permission denied
If you want to revoke access, just use the revoke part from before.

How to get the Windows Group name when no user login exists

I have a scenario where a user can log in to a SQL Server through their Windows group. The user itself has no login nor a user in the database.
I would then like to get the name of the windows group through which the user is logged. However, suser_sname() returns the actual user name. suser_sid() does not help me either.
Does anyone know how to do this?
DECLARE #user SYSNAME;
SET #user = SUSER_SNAME();
EXEC xp_logininfo #user, 'all';
This will return rows for any group the user is in (the rows will have non-null values in the permission path column). But it will also return a row for their own username (in my case permission path was null for that row).
You can also see all the possible groups you might expect to see with the following query:
SELECT * FROM master.sys.server_principals
WHERE type = 'G'; -- type_desc = 'WINDOWS_GROUP'
The user may also be a member of other AD groups that haven't been registered with SQL Server. You'll need to go out to AD for that.

Determining the current security checks being made (SQL Server)

One thing that I've always hated more than just about anything in MS SQL Server is the way that security works. The security context constantly switches if you look at the server funny and it's often very hard (for me anyway) to predict or debug.
In dealing with an issue today, I though, "I wish I could just add a line to my code that would display the security context that SQL Server is using when this code runs." Does such a command exist? For example, SELECT security_context()
To be a little clearer... if I'm in a stored procedure and am therefor subject to the security context of the owner of the SP then I'd like to see that. If I'm in code that was called by sp_executesql and it's causing the security to be under the context of the SQL Server service account, then I would want to see that.
At least then I might be able to figure out why SQL Server thinks that I shouldn't have access to something.
Thanks!
EXAMPLE
-- Set up
CREATE USER Test_User WITHOUT LOGIN
CREATE TABLE Test_Security_Context (my_id INT)
INSERT INTO Test_Security_Context VALUES (1)
DENY SELECT ON Test_Security_Context TO Test_User
GO
CREATE PROCEDURE Test_Security_Context_SP
AS
SELECT SUSER_SNAME()
SELECT * FROM Test_Security_Context -- This will return ok
EXEC('SELECT SUSER_SNAME(); SELECT * FROM Test_Security_Context') -- SUSER_SNAME() will match above but select fails
GO
GRANT EXECUTE ON Test_Security_Context_SP TO Test_User
GO
-- Switch to the new user
SETUSER 'Test_User'
GO
-- Do the test
EXEC Test_Security_Context_SP
GO
-- Clean up
SETUSER
DROP PROCEDURE Test_Security_Context_SP
DROP TABLE Test_Security_Context
DROP USER Test_User
GO
Yes, there is such a pair of views that represents your current security context, considering all the details like EXECUTE AS or code signing:
sys.login_token for the server wide context
sys.user_token for the current database context
Every single access you get is ultimately derived from a row in the return of these results. Note that some access are implicit from hard coded role membership (like db_datareader database role or sysadmin server role).
Other that that:
ownership chaining is not related to security context: you are not under the 'context' of the SP owner. Ownership chaining simply states that access checks are skipped for objects owned by the same owner as current object (SP, View).
sp_executesql does not change the security context in any way
Not sure if this is what you mean by security context, but you can retrieve the user associated with your session like:
select SYSTEM_USER
This works for both a SQL Server login or a WIndows login. It even works inside stored procedures with execute as owner. For example,
create procedure dbo.Test
with execute as owner
as
select SYSTEM_USER
go
exec dbo.Test
select SYSTEM_USER
Prints:
sa
MyMachine\MyName
If you're looking for the Windows account that SQL Server is using to do things on your behalf, you could try to run whoami from the command like:
EXEC sp_configure 'show advanced options', 1
RECONFIGURE
EXEC sp_configure 'xp_cmdshell', 1
RECONFIGURE
EXEC master..xp_cmdshell 'whoami'
For me, that returns nt authority\network service.
I think you want to use CURRENT_USER to see the current security context. Here's an example:
SELECT CURRENT_USER AS 'Current User Name';
GO
EXECUTE AS LOGIN = 'junk'
GO
SELECT CURRENT_USER AS 'Current User Name';
GO
REVERT
SELECT CURRENT_USER AS 'Current User Name';
GO
with output (note: I'm admin on my SQL Server for this)
Current User Name
------------------
dbo
(1 row(s) affected)
Current User Name
------------------
Junk
(1 row(s) affected)
Current User Name
------------------
dbo
(1 row(s) affected)

Resources