I have restored a SQL Server 2019 database from a .BAK file to a Google Cloud SQL instance.
I can connect to the instance using Visual Studio SQL Connection. I issue the following command to check the database owner, which should be: mydb_adm
USE master;
SELECT suser_sname(owner_sid) AS 'DB Owner' FROM sys.databases WHERE name = 'mydb';
DB Owner
--------
sqlserver
The above is expected, as the restore was done while using the sqlserver account which is the default user created when the SQL instance is provisioned by Google Cloud (according to the docs).
So, I need to change the DB owner; I execute the following command:
USE mydb
EXEC sp_changedbowner 'mydb_adm'
The system displays the following error message:
Msg 15151, Level 16, State 1, Line 1
Cannot find the principal 'mydb_adm', because it does not exist or you do not have permission.
The same message is displayed for:
ALTER AUTHORIZATION ON DATABASE::mydb TO mydb_adm;
However, the "mydb_adm" principal DOES exist, i.e.:
SELECT name, sid FROM sys.server_principals WHERE name = 'mydb_adm';
name sid
---- ---
mydb_adm 0xD81398C7DB0D724BB2738A2EC59BB554
.. so it must be a permission problem with the sqlserver account. When I query the DB, it appears the "sqlserver" user does NOT have ALTER permissions, i.e.:
UserName Permission Type Permission State
-------- --------------- ----------------
sqlserver ALTER DENY
... So how can I change the database owner or issue any ALTER commands using the "sqlserver" account? (There seems to be no way to grant the ALTER permission to the sqlserver user).
Any help / advice would be appreciated.
Thank-you to #DanGuzman for providing a "work-around", i.e.: while connected to the SQL instance using the "sqlserver" user, the following commands were used:
USE mydb;
CREATE USER mydb_adm;
ALTER ROLE db_owner ADD member mydb_adm;
After some additional digging, I also found the following in the Google Cloud docs at https://cloud.google.com/sql/docs/sqlserver/users, which states:
Cloud SQL for SQL Server is a managed service, so it restricts access
to certain system stored procedures and tables that require advanced
privileges. In Cloud SQL, you cannot create or have access to users
with superuser permissions.
Note: The sysadmin role is not supported. Therefore, you cannot run
system stored procedures that require the sysadmin role. As one of
the many examples, you cannot run the sp_OADestroy stored procedure
because it requires the sysadmin role.
As well as the following from the SQL Server docs at https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-authorization-transact-sql?view=sql-server-ver15#remarks, which state:
Requirements for the person executing the ALTER AUTHORIZATION
statement: If you are not a member of the sysadmin fixed server role,
you must have at least TAKE OWNERSHIP permission on the database, and
must have IMPERSONATE permission on the new owner login.
hence, commands such as EXEC sp_changedbowner ... or ALTER AUTHORIZATION ON DATABASE:: ... will raise the error (Msg 15151, ... you do not have permission.)
Hope that helps anyone else that may run into this type of issue.
I am working on a tool which is having restore command to restore MSSQL databases. Till now tool was restoring the databases with sysadmin privileges. However, as per new requirement I want to minimize the access privilege for this command i.e restore should be done with dbcreator role instead of sysadmin. With dbcreator I am able to restore the databases, however this command also does some post-operation on the restored databases i.e. updating some value in table. Post-operation is failing with lack of access, since db_owner is required for this user. How to grant db_owner to current user(dbcreator) on the restored databases who is not sysadmin at run time so that my restore command succeeds along with post-operation.
One way to follow security principles of least privilege and role-based access controls is to encapsulate the T-SQL commands that require elevated permissions in a stored procedure. One can then sign the proc with a certificate to confer additional permissions without granting those permissions directly to users. Only execute permissions on the signed stored procedure are required and authorized users are limited to the encapsulated functionality.
Below is an example script to create a stored procedure and DBRestore role using this technique. If your actual RESTORE command contains options that can't be parameterized (e.g. WITH MOVE file locations), you'll need to use dynamic SQL in the proc and take special care to ensure the values validated and/or obtained from a trusted source (e.g. configuration table instead of ad-hoc parameter value).
USE master
GO
--create certificate in master database
CREATE CERTIFICATE sysadmin_cert_login_cert
ENCRYPTION BY PASSWORD = '<cert-password>'
WITH SUBJECT = 'For sysadmin privileges';
GO
--create login from certificate
CREATE LOGIN sysadmin_cert_login FROM CERTIFICATE sysadmin_cert_login_cert;
--confer sysadmin permissions to certificate login
ALTER SERVER ROLE sysadmin
ADD MEMBER sysadmin_cert_login;
GO
--create role for restore user(s)
CREATE ROLE DBRestoreRole;
GO
--create restore proc in master database
CREATE PROC dbo.usp_RestoreDatabase
#DatabaseName sysname
, #BackupFilePath varchar(255)
AS
BEGIN TRY
RESTORE DATABASE #DatabaseName FROM DISK=#BackupFilePath WITH REPLACE;
--after restore, set database owner as desired
ALTER AUTHORIZATION ON DATABASE::RestoreTest TO sa;
--execute post restore DML
UPDATE RestoreTest.dbo.SomeTable
SET SomeColumn = 1;
END TRY
BEGIN CATCH
THROW;
END CATCH;
GO
--grant execute permission to DBRestoreRole
GRANT EXECUTE ON dbo.usp_RestoreDatabase TO DBRestoreRole;
--sign proc with sysadmin certificate
ADD SIGNATURE TO dbo.usp_RestoreDatabase BY CERTIFICATE sysadmin_cert_login_cert WITH PASSWORD='<cert-password>';
--optionally, remove ephemoral private key after signing
ALTER CERTIFICATE sysadmin_cert_login_cert REMOVE PRIVATE KEY;
GO
--create example DBRestoreRole login/user
CREATE LOGIN RestoreTestLogin WITH PASSWORD = '<login-password>';
CREATE USER RestoreTestLogin;
ALTER ROLE DBRestoreRole
ADD MEMBER RestoreTestLogin;
GO
--test execution
EXECUTE AS LOGIN = 'RestoreTestLogin';
GO
EXEC dbo.usp_RestoreDatabase
#DatabaseName = N'RestoreExample'
, #BackupFilePath = 'E:\BackupFiles\RestoreExample.bak';
GO
REVERT;
GO
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.
I have a question about security in SQL Server.
Is it possible to create a login with only authorization to attach or detach databases?
I don't want it as sysadmin.
If you check the documentation, you will see that for attaching a database we need:
Requires CREATE DATABASE, CREATE ANY DATABASE, or ALTER ANY DATABASE
permission.
and for detaching:
Requires membership in the db_owner fixed database role.
If you check the security hierarchy and download the Chart of SQL Server Permissions, you can check what other permission a user will have if he has any of the above permissions.
For example, if he has ALTER ANY DATABASE:
Basically, these rights are on the top of the security hierarchy and if you allow a user the ability to attach database, ze will be able to do other operations overt it, too.
If you don't want to make the user member of sysadmin role, that's OK. You can make the user member of dbcreator role:
I'm setting up a secure login system for a new version of my company's software directly on SQL Server 2017. I'm trying to have user account's CONNECT permission to any databases revoked until the users login to an initial authentication database with a set username/password, and use a login procedure to enable their account on a specific database, to which the client would then disconnect from the initial database and reconnect using their personal account, connecting to their target database.
A certificate was created in the initial database, and used to sign a procedure that calls the one shown below. The procedure shown below is not signed by any certificates. The certificate that was created was copied to the target database, and used to create a user, who was then added to a group, Certified, that has permissions to execute the procedure shown below, and has CONNECT WITH GRANT OPTION on the database. When the procedure below gets executed, the grant/revoke statement fails, saying that the grantor doesn't have the necessary permissions (or that the user/role was not found, depending on if I try to add an AS clause)
The granting/revoking of the connect permission is done through dynamic SQL in a stored procedure, in the target database (every database that's part of our software will have an identical procedure to do this job). It works if I run the code as a member of the sysadmin fixed role, but not when I am running it with my set authentication account. The procedure in the target database (ModifyUser) is called from another procedure in the initial database that is signed with a certificate, to which there is a user from the same certificate in the target database that is a member of the role Certified that has been granted CONNECT WITH GRANT OPTION, however whenever I run the procedure, the statement fails.
I've tried 3 versions, changing the content of the AS clause:
AS the certificate user, who is part of the database role, uCompCompID
AS the database role, that the certificate user is a part of, Certified
SQL Server documentation doesn't seem clear about which of these should be used when trying to grant a permission that a role has the grant option for
With no AS clause, so as to grant the permission as the user running the dynamic SQL
This seems to run the query as the authentication user, not the certificate user that is a member of the role that has the grant option for CONNECT
The ModifyUser procedure is short, so I'll include the whole thing below. This procedure is stored in the same database as the user I want to grant/revoke the CONNECT permission to, but is called for a different database signed by a certificate that matches a user created in this same database.
This is version 3, where there is no AS clause.
PROCEDURE [Authorization].[ModifyUser]
#user nvarchar(128),
#status bit
AS
BEGIN
SET NOCOUNT ON
IF #user IS NULL
THROW 50002, 'Invalid argument: #user', 0
IF LTRIM(RTRIM(#user)) = ''
THROW 50002, 'Invalid argument: #user', 1
IF #status IS NULL
THROW 50002, 'Invalid argument: #status', 0
DECLARE #statement nvarchar(200)
IF #status = 1
SET #statement = 'GRANT CONNECT TO ' + #user
ELSE
SET #statement = 'REVOKE CONNECT TO ' + #user
EXEC (#statement)
END
The expected result would be that the CONNECT permission on the target user is changed, but the received result is always an error. The exact error depends on the version used:
Msg 15151, Level 16, State 1, Line 2
Cannot find the user 'uCompCompID', because it does not exist or you do not have permission.
Msg 15151, Level 16, State 1, Line 2
Cannot find the user 'Certified', because it does not exist or you do not have permission.
Msg 4613, Level 16, State 1, Line 2
Grantor does not have GRANT permission.
This is not an issue if I grant the permissions directly on the certificate user. However, I would like to keep the permissions in a role so that when I inevitably need to recreate the certificate after modifying one of the procedures involved in this system, I only need to worry about adding the new certificate user to the appropriate group, instead of needing to grant permissions to the recreated user every time a change is made that requires resigning any of the certificates.
I've resolved this issue for my case. To go to the solution, skip ahead to the horizontal line break.
To clarify my setup if my original post didn't do a good job, I have a database, Initial, that created a certificate, cCompCompID, which was used to sign a procedure in that same database. The certificate was then copied to another database, Target, where it was used to create a user, uCompCompID, that was then added to the role Certified. The role was granted permission to execute ModifyUser, as well as CONNECT WITH GRANT OPTION so that ModifyUser could be used to change the connect permission on other users, but only when called from the signed procedure in Initial (this is a property of Module Signing, which I am using to keep the system as secure as possible from unintended access to these procedures).
I wanted to keep the permissions to the role Certified and not on the user, because I know I'll need to modify the procedures in the future, and that would require the procedure in Initial be resigned, and I would need to create a new certificate to sign the procedure, and then copy the new certificate to the Target database, drop and recreate the user, and then I'd need to worry about granting the right permissions again. Keeping the permissions on the role simplifies the process. The certificate needs to be recreated, because everywhere I've seen, the recommended course of action is t drop the private key from certificates once use of it is done so as to prevent unintended use of them later.
The correct way to grant a permission that a role has the GRANT OPTION for is to use AS <role name> at the end of the grant statement. The reason this was not working correctly in case 2 in my original post is explained by the documentation on granting permissions on a database with an AS clause. When granting, revoking, or denying a permission as a database role, the user executing the statement must have:
ALTER permission on the role, membership in the db_securityadmin fixed database role, membership in the db_owner fixed database role, or membership in the sysadmin fixed server role.
The resolution to my issue is to grant the certificate user, uCompCompID ALTER permission on the Certified role, so that it will be able to grant or revoke the permission by using its role membership. Granting the user this permission does not pose a security threat, because ModifyUser is only granted the permissions granted to uCompCompID when it is called from the procedure in Initial that was signed by the same certificate that uCompCompID was created from. It will not have these permissions if it were invoked directly, or by any procedure not signed by that certificate, thanks to Module Signing (And since the private key is dropped from the certificate once the procedure in Initial is signed, there is no risk of it being signed to anything else to create a security threat without a much bigger hole elsewhere)
As #sepupic points out, it is also necessary for me to sign ModifyUser with the same certificate for the permissions to work. ModifyUser originally has its permissions because it was called from a different procedure that was signed with the certificate. When ModifyUser executes dynamic SQL without being signed, these extra permissions are removed until the dynamic SQL is completed. If ModifyUser is signed with the same certificate, then the dynamic SQL executed by ModifyUser will retain the permissions that were expected and required.
Thank you #BenThul and #sepupic for your help.
First of all you should update your question because it's not clear in what databases did you create a cerificate and which procedure is signed an which is not:
The procedure in the target database (ModifyUser) is called from
another procedure in the initial database that is signed with a
certificate, to which there is a user from the same certificate in the
target database that is a member of the role Certified that has been
granted CONNECT WITH GRANT OPTION
From this it seems that only the procedure in the initial database is signed with a
certificate, but the inner procedure (in the target database) is not, in this case only the outer procedure has permissions granted to a user created from certificate, that's why you get error N3
Grantor does not have GRANT permission
In other cases you get your error because you use execute as clause (this clause admits only user, not login!). When you use it, your proc is sandboxed within the database where it the procedure created, i.e. you cannot do nothing in another database even if you are sysadmin, server just cannot use the corresponding login (to search the corresponding user in other database) unless the database is trustworthy and in't owner has authenticate in another database.