Granting Cross-Assembly Privileges
The examples in the
preceding sections were simplified a bit in order to focus the text on a
single issue at a time. There are two other issues you need to be
concerned with when working with cross-assembly calls: database
trustworthiness and strong naming.
The idea of a "trustworthy"
database is new to SQL Server 2005, and is a direct offshoot of
Microsoft's heightened awareness of security issues in recent years.
Marking a database as trustworthy or not is a simple matter of setting
an option using ALTER DATABASE:
ALTER DATABASE AdventureWorks
SET TRUSTWORTHY ON;
GO
Unfortunately, as
simple as enabling this option is, the repercussions of this setting are
far from it. Effectively, it comes down to the fact that code running
in the context of a trustworthy database can access resources outside of
the database more easily than code running in a database not marked as
such. This means access to the file system, remote database servers, and
even other databases on the same server—all of this access is
controlled by this one option, so be careful.
Turning off the TRUSTWORTHY
option means that rogue code will have a much harder time accessing
resources outside of the database, but it also means that you as a
developer will have to spend more time dealing with security issues.
That said, I highly recommend leaving this turned off unless you really
have a great reason to enable it.
In the SQLCLR world, you'll see a deploy-time exception if you catalog an assembly using the EXTERNAL_ACCESS or UNSAFE
permission sets and try to catalog a referencing assembly. Following is
the exception I get when trying to catalog the assembly I created that
contains the GetConvertedAmount method, after setting my database to nontrustworthy mode:
CREATE ASSEMBLY for assembly 'CurrencyConversion' failed because
assembly 'SafeDictionary' is not authorized for PERMISSION_SET = UNSAFE.
The assembly is authorized when either of the following is true: the database
owner (DBO) has UNSAFE ASSEMBLY permission and the database has the TRUSTWORTHY
database property on; or the assembly is signed with a certificate or an asymmetric
key that has a corresponding login with UNSAFE ASSEMBLY permission.
If you have restored or attached this database, make sure the database owner is
mapped to the correct login on this server. If not, use sp_changedbowner to fix the
problem.
This rather verbose
exception is rare and to be treasured: it describes exactly how to solve
the problem! You can grant the UNSAFE ASSEMBLY permission by using certificates. To begin, create a certificate and a corresponding login in the master database, and grant the login UNSAFE ASSEMBLY permission:
USE master
GO
CREATE CERTIFICATE Assembly_Permissions_Certificate
ENCRYPTION BY PASSWORD = 'uSe_a STr()nG PaSSW0rD!'
WITH SUBJECT = 'Certificate used to grant assembly permission'
GO
CREATE LOGIN Assembly_Permissions_Login
FROM CERTIFICATE Assembly_Permissions_Certificate
GO
GRANT UNSAFE ASSEMBLY TO Assembly_Permissions_Login
GO
Next, back up the certificate to a file:
BACKUP CERTIFICATE Assembly_Permissions_Certificate
TO FILE = 'C:\assembly_permissions.cer'
WITH PRIVATE KEY
(
FILE = 'C:\assembly_permissions.pvk',
ENCRYPTION BY PASSWORD = 'is?tHiS_a_VeRySTronGP4ssWoR|)?',
DECRYPTION BY PASSWORD = 'uSe_a STr()nG PaSSW0rD!'
)
GO
Now, in the database in
which you're working—AdventureWorks, in my case—restore the certificate
and create a local database user from it:
USE AdventureWorks
GO
CREATE CERTIFICATE Assembly_Permissions_Certificate
FROM FILE = 'C:\assembly_permissions.cer'
WITH PRIVATE KEY
(
FILE = 'C:\assembly_permissions.pvk',
DECRYPTION BY PASSWORD = 'is?tHiS_a_VeRySTronGP4ssWoR|)?',
ENCRYPTION BY PASSWORD = 'uSe_a STr()nG PaSSW0rD!'
)
GO
CREATE USER Assembly_Permissions_User
FOR CERTIFICATE Assembly_Permissions_Certificate
GO
Finally, sign the assembly with the certificate, thereby granting access and allowing the assembly to be referenced:
ADD SIGNATURE TO ASSEMBLY::SafeDictionary
BY CERTIFICATE Assembly_Permissions_Certificate
WITH PASSWORD='uSe_a STr()nG PaSSW0rD!'
GO
The other issue you
might encounter has to do with strong-named assemblies. Strong naming
is a .NET security feature that allows you to digitally sign your
assembly in order to more carefully version it and ensure its validity
to users. For most SQLCLR code, strong naming is probably overkill—code
running in secured, managed databases probably doesn't need the
additional assurances that strong naming provides. However, vendors
looking at distributing applications that include SQLCLR components will
definitely want to look at strong naming.
After signing the assembly that contains the ReadFileLines method and redeploying both it and the assembly containing the CAS_Exception stored procedure, I receive the following error when I call the procedure:
Msg 6522, Level 16, State 1, Procedure CAS_Exception, Line 0
A .NET Framework error occurred during execution of user-defined routine or
aggregate "CAS_Exception":
System.Security.SecurityException: That assembly does not allow partially trusted
callers.
System.Security.SecurityException:
at System.Security.CodeAccessSecurityEngine.ThrowSecurityException(Assembly asm,
PermissionSet granted, PermissionSet refused, RuntimeMethodHandle rmh,
SecurityAction action, Object demand, IPermission permThatFailed)
at udf_part2.CAS_Exception()
.
The solution is to add an AllowPartiallyTrustedCallersAttribute
(often seen referred to merely as APTCA in articles) to the code. This
attribute should be added to a single file in the assembly, after the using declarations and before definition of any classes or namespaces. In the case of the FileLines assembly, the file looks like the following after adding the attribute:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections.Generic;
using System.Security.Permissions;
[assembly: System.Security.AllowPartiallyTrustedCallers]
public partial class FileLines
{
Once this attribute has been added, any caller can use the methods in the FileLines
class, without receiving an exception. Keep in mind that this attribute
must be specified for a reason, and by using it you may be allowing
callers to circumvent security. If the assembly does operations that not
everyone should have access to, make sure to secure things another way,
such as by creating groups of assemblies with different owners in order
to ensure that nongrouped assemblies cannot reference the sensitive
methods.