The words "securely", "exposing", "outside" and "database" do not belong together.
Under no circumstance should you ever expose a SQL server to the internet. It's a very bad idea.
So your question really should be: How do we give access to our SQL server to remote locations?
The answer: VPN. The remote users should establish a VPN connection to your network. From there you can give them the appropriate access to the database.
The restore completes, but as soon as it's done, the domain login can no longer access the database because the database ownership is lost. (I believe because it had a different owner on the vendor's system).
Correct. While it's possible (I believe) to have the same SID (i.e. Security ID) between their system and yours (if so, it would require you creating their domain account in your domain with the SID that they provide that is the SID of the owner account on their side), most likely it's a different SID, and that is retained in the database itself.
After the restore is complete, I have to re-create the user within the database that matches up to the domain login, and make that user the owner again.
Now this doesn't sound right. Usually, if you add an SID to a database as a User, and then later try to make a Login (having that same SID) the owner of the database, you will get an error stating that the account / SID is already present in the database:
Msg 15110, Level 16, State 1, Line XXXXX
The proposed new database owner is already a user or aliased in the database.
It's possible that something is misstated here, or that I am misunderstanding something, but as we will see in a moment, it doesn't really matter.
This vendor is trusted enough that we have granted the login the "dbcreator" server role.
It's nice that you can trust them, but you don't need to do anything special for them. You can allow them to restore their database, and automatically set the owner to their domain account, and anything else that needs to happen in this process, all while removing them from the dbcreator
fixed Server Role. All their login needs is the ability to connect to the instance (and they already have that), and permission to execute a single stored procedure. What stored procedure? The one I will now describe :-)
Using the wonderful awesomeness of Module Signing, you can set up a stored procedure that will a) execute the RESTORE DATABASE
command, b) set their login as the owner of that database, and c) perform any other administrative steps needed for this operation, no matter what level of permissions are needed. You will never give the vendor's login any permissions except for the ability to execute this single stored procedure. The necessary permission(s) will instead be given to the stored procedure.
Assuming that you have a [Utility]
(or whatever name) database for general administrative functionality, do the following:
- In the
[Utility]
database:
- Add the DOMAIN\VendorLogin to the DB using
CREATE USER
- Create a
RestoreVendorDB
stored procedure that does the following:
- Executes
RESTORE DATABASE [VendorDB] ...
- Executes
ALTER AUTHORIZATION ON DATABASE::[VendorDB] TO [DOMAIN\VendorLogin];
- Allow the vendor to execute the stored proc:
GRANT EXECUTE ON [dbo].[RestoreVendorDB] TO [DOMAIN\VendorLogin];
- Create a certificate using
CREATE CERTIFICATE...
- Sign the stored procedure with that certificate using
ADD SIGNATURE...
- Copy certificate (only the public key) to
[master]
database using Dynamic SQL to execute CREATE CERTIFICATE
with result of CERTENCODED(CERT_ID(...))
- In the
[master]
DB, do the following:
- Create a login from the certificate using
CREATE LOGIN [Permission$CreateVendorDB] FROM CERTIFICATE [...];
- Add the certificate-based login to the
dbcreator
role using ALTER SERVER ROLE [dbcreator] ADD MEMBER [Permission$CreateVendorDB];
- If you need an additional role and/or permission in order for the stored procedure to change the database's owner, then repeat the previous step with the appropriate role and/or
GRANT
the certificate-based login the appropriate permission(s).
- Remove the vendor's login from the
dbcreator
role (they no longer need that permission) using ALTER SERVER ROLE [dbcreator] DROP MEMBER [DOMAIN\VendorLogin];
If you ever alter the code in the RestoreVendorDB
stored procedure, just re-sign the stored procedure (after the ALTER
) using ADD SIGNATURE...
.
Using this approach, you not only get exactly what you were wanting, but you also end up with a more secure system ? .
For more explanation and a working example (of this concept), please see:
Safely and Easily Use High-Level Permissions Without Granting Them to Anyone: Server-level
Best Answer
There's no need to bother with certificate signing of stored procedures.
If I'm reading your question correctly, your requirements are essentially this:
The last requirement is the change you're looking to implement, for which you are considering cert-signed stored procedures.
The answer is in stored procedures, but there should be no need to bother with cert-signing.
Ownership chaining!
SQL Server allows for ownership chaining, which essentially means if you
GRANT EXECUTE
on a stored procedure, the procedure will be able to access the data it needs, and return an accurate result, even if the user doesn't have direct access to the data.An example
First, lets set up a database, a SQL Login (though, in your case, you'd just use your domain group), and some data.
Now, rather than adding the user to the
db_datareader
fixed role, we'll grant data explicitly to the table(s) with data they can access directly:And when we query that data as the login we created. We'll get results when we query the public table that we granted
SELECT
on, and we'll get an error for the private table we have no permissions on.Now, we'll create a stored procedure which does that second query within the procedure, and we'll grant
EXECUTE
on that procedure to the limited access login.Now,
ReadOnlyUser
hasEXECUTE
permission on the procedure that accessdbo.PrivateData
, but doesn't have access to querydbo.PrivateData
directly.Cross-Database problems
Ownership chaining won't cross database boundaries, so in the above example, if
dbo.PrivateData
were in a separate database, thatEXEC dbo.GetMyData
would actually return a permissions error. Cross-database ownership chaining can be enabled, but brings with it some security risks, but we can still avoid it.A more complicated example
Continuing where the last example left off, lets move
dbo.PrivateData
into a different databaseAnd we can create a stored procedure that gives access to just that aggregated data, without accessing the raw data directly. We grant execute to this procedure. In real life, this might be a bit more complicated, and probably would accept some parameters to limit the data.
And we can alter the original
GetMyData
procedure to call thisGetPrivateData
procedure, rather than grabbing the data directly. Now, insideGetPrivateData
, I'm doing anINSERT..EXEC...
to put the data into a temp table, and then join to the temp table.And when we test it as our user, we'll see that the procedure gives access, and querying the data directly gives an error.
Views work too.
Ownership chaining can work with views, too. In all the places where I used a procedure to "cover" direct data access, I could have used a view similarly. There are differences & trade-offs. In general, views give users more access to go after the data in new & interesting ways, where stored procedures give you more control. This also means that stored procedures can give more protection to ensure data is only accessed in "approved" ways. Depending on what your controls are, and how you need to balance those things, you might find that the right thing for you could be a view, or a stored procedure.
Here's an alternate example using a view:
No cert signing necessary
It's just a matter of making your grants granular enough that you specifically grant access to the things people should have access to, and don't grant access directly if the user shouldn't have direct access. Stored procedures, views, and functions can be used as the abstraction layer, to give access indirectly to data.