C# CLR выдает исключение безопасности, если не помечено как небезопасное
У меня есть хранимая процедура C# CLR (.NET 4.5) в SQL Server 2012, которая вызывается из транзакции. CLR вызывает WebService, который требует TLS 1.2.
Все работает нормально, если я использую создать сборку с разрешением_set = UNSAFE, но я действительно хотел бы избежать этого и вместо этого использовать EXTERNAL_ACCESS. Тем не менее, это оказывается реальной проблемой.
При использовании EXTERNAL_ACCESS есть две проблемы:
1) Я записываю в файл журнала из CLR (для целей обслуживания и отладки), который не работает, так как блокировки не разрешены:
private static readonly object Locker = new object();
public static string LogMessage(string message)
{
message = String.Format("{0}: {1}" + Environment.NewLine, DateTime.Now, message);
lock (Locker)
{
var file = new FileStream(GetConfigValue("LogFile"), FileMode.Append, FileAccess.Write);
var sw = new StreamWriter(file);
sw.Write(message);
sw.Flush();
sw.Close();
}
return message;
}
Сообщение об ошибке:
System.Security.HostProtectionException: Попытка выполнить операцию, которая была запрещена хостом CLR.
Защищенные ресурсы (доступны только с полным доверием): Все требуемые ресурсы: Синхронизация, ExternalThreading
Вместо этого я мог бы войти в таблицу базы данных, но поскольку вызов CLR осуществляется из транзакции, ошибки могут вызвать откат, который также откатит запись журнала. Я мог бы использовать журнал событий, но я действительно не хочу делать это, если это вообще возможно.
2) Веб-сервис, который я вызываю, требует TLS 1.2, но настройка ServerCertificateValidationCallback
не допускается:
ServicePointManager.ServerCertificateValidationCallback = AcceptAllCertifications;
Сообщение об ошибке:
System.Security.SecurityException: сбой запроса разрешения типа "System.Security.Permissions.SecurityPermission, mscorlib, Version=4.0.0.0, Culture= нейтральный, PublicKeyToken=b77a5c561934e089".
System.Security.SecurityException:
в System.Security.CodeAccessSecurityEngine.Check(Требование объекта, StackCrawlMark& stackMark, Boolean isPermSet) в System.Security.CodeAccessPermission.Demand() в System.Net.ServicePointManager.set_ServerPointPointManagerCertificateValidationCaltificateValidationCaltificateValidation SqlString ticketNo, SqlString& resultCode, SqlString& resultText)
Есть ли как-то обойти эту проблему - есть ли способ сделать это без использования UNSAFE?
1 ответ
Для проблемы № 1 (использование блокировок для управления несколькими потоками / сеансами, записывающими в один и тот же файл журнала в одно и то же время), нет способа использовать блокировки ни в чем, кроме UNSAFE
Режим. Однако, хорошая новость заключается в том, что вам, вероятно, не нужно использовать блокировки в первую очередь. Все, что действительно нужно сделать, - это убедиться, что два LogMessage()
одновременные вызовы не конфликтуют, и один из них получает ошибку. Это должно быть решено с помощью другой перегрузки конструктора FileStream, которая позволяет передавать параметр FileShare / enum. Вы должны быть в состоянии пройти в FileShare.ReadWrite
чтобы предотвратить ошибку. Вы уже объединяетесь DateTime.Now
в message
так что это должно помочь решить проблемы с последовательностью, если более раннее сообщение написано после более позднего.
Но вне этого конкретного конфликта, даже с блокировкой, у вас все еще есть проблема двух сессий, выполняющих хранимую процедуру SQLCLR WebService примерно в одно и то же время, чередуя их сообщения. Как вы различаете сообщения? Я бы предложил создать новый Guid
в начале хранимой процедуры и объединяя это в каждое сообщение, чтобы вы могли соотнести сообщения определенного вызова с хранимой процедурой (я предполагаю, что ваш код имеет более одного места, где он вызывает LogMessage()
) и отличать эти сообщения от других вызовов (одновременно или нет).
Если по какой-либо причине две штуки выше (FileShare.ReadWrite
и объединяя Guid в message
) не предотвращайте ошибку, тогда вы можете забыть о FileShare
вариант (хотя я все еще предпочитаю установить его на Read
так что я могу легко проверить файл в процессе его написания) и вместо того, чтобы объединить Guid в message
, добавь его в конец GetConfigValue("LogFile")
значение, непосредственно перед расширением файла. Затем сообщения определенного вызова автоматически соотносятся друг с другом и отличаются от других одновременных вызовов. Это просто означает, что у вас есть куча файлов журналов.
Три другие мысли по этому поводу:
Имея
lock
вLogMessage()
метод, вы фактически увеличиваете блокировку, потому что параллельные вызовы этого метода в разных сеансах должны будут ждать, пока владелец блокировки не снимет блокировку. В конце концов, в этом и заключается смысл блокировки, верно? За исключением случаев, когда речь идет о транзакциях, вы действительно не хотите продлевать их больше, чем это абсолютно необходимо, и сама природа вызова веб-службы через SQLCLR заключается в том, что вы уже делаете транзакцию зависимой от задержки сети и скорости отклика этого внешнего система (даже если она внутренняя).Ты упомянул:
Вместо этого я мог бы войти в таблицу базы данных, но поскольку вызов CLR осуществляется из транзакции, ошибки могут вызвать откат, который также откатит запись журнала.
Не обязательно. Если вы используете внутрипроцессное "Контекстное соединение", то да, это правда. И это также верно, если вы используете обычное / внешнее соединение, ЕСЛИ вы не указали
Enlist
ключевое слово в строке подключения или, если вы укажетеEnlist = true;
, Однако, если вы укажетеEnlist = false;
тогда это должны быть отдельные / отключенные транзакции, которые не будут откатываться, если транзакция, вызвавшая хранимую процедуру, будет откатана.- И хотя это может не помочь в ситуации использования кода SQLCLR, я по крайней мере упомяну, что для чистого кода T-SQL, когда вы хотите обойти эту проблему потери записей журнала при откате, у вас есть возможность запись этих записей изначально в табличную переменную. Переменные таблицы не связаны с транзакциями, поэтому откат не влияет на них. Недостатком является то, что если процесс умирает таким образом, что он полностью завершается до перехода к той части, где после отката он вставляет записи переменной таблицы в реальную таблицу, вы теряете эти записи. Вот где иногда лучше записать файл журнала или использовать SQLCLR, чтобы установить обычное / внешнее соединение, используя
Enlist = false;
,
Для проблемы № 2 (настройка ServicePointManager.ServerCertificateValidationCallback
), к сожалению, вы ничего не можете с этим поделать. Я столкнулся с той же проблемой при попытке установить UseSSL
собственность на true
с FtpWebRequest
для того, чтобы поддержать FTPS. Я еще не нашел способ пометить сборку как PERMISSION_SET = UNSAFE
при этом
Примечания стороны:
Я согласен с желанием сохранить Ассамблею
EXTERNAL_ACCESS
вместоUNSAFE
, но учитывая, что это ваш код и вы не используетеUNSAFE
чтобы использовать любые сторонние библиотеки или неподдерживаемые библиотеки.NET Framework, фактический уровень риска довольно низок.Кроме того, мы надеемся, что вы используете Асимметричный ключ или логин на основе сертификата, чтобы позволить сборке иметь не
SAFE
разрешение и не установка базы данныхTRUSTWORTHY ON
,Если код, показанный в Вопросе, не был упрощен для публикации только того кода, который "необходим" для сообщения о немедленной проблеме, вам не хватает обработки ошибок вокруг внешних ресурсов. В случае сбоя процесса файл журнала будет заблокирован до тех пор, пока домен приложения не будет выгружен, если вы не сможете правильно утилизировать
StreamWriter
иFileStream
, Вы можете либо добавить структуру try / finally вокруг этих 5 строк, либо сделать так, чтобы компилятор сделал это за вас, используяusing()
конструкция / макрос.