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") значение, непосредственно перед расширением файла. Затем сообщения определенного вызова автоматически соотносятся друг с другом и отличаются от других одновременных вызовов. Это просто означает, что у вас есть куча файлов журналов.

Три другие мысли по этому поводу:

  1. Имея lock в LogMessage() метод, вы фактически увеличиваете блокировку, потому что параллельные вызовы этого метода в разных сеансах должны будут ждать, пока владелец блокировки не снимет блокировку. В конце концов, в этом и заключается смысл блокировки, верно? За исключением случаев, когда речь идет о транзакциях, вы действительно не хотите продлевать их больше, чем это абсолютно необходимо, и сама природа вызова веб-службы через SQLCLR заключается в том, что вы уже делаете транзакцию зависимой от задержки сети и скорости отклика этого внешнего система (даже если она внутренняя).

  2. Ты упомянул:

    Вместо этого я мог бы войти в таблицу базы данных, но поскольку вызов CLR осуществляется из транзакции, ошибки могут вызвать откат, который также откатит запись журнала.

    Не обязательно. Если вы используете внутрипроцессное "Контекстное соединение", то да, это правда. И это также верно, если вы используете обычное / внешнее соединение, ЕСЛИ вы не указали Enlist ключевое слово в строке подключения или, если вы укажете Enlist = true;, Однако, если вы укажете Enlist = false; тогда это должны быть отдельные / отключенные транзакции, которые не будут откатываться, если транзакция, вызвавшая хранимую процедуру, будет откатана.

  3. И хотя это может не помочь в ситуации использования кода 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() конструкция / макрос.

Другие вопросы по тегам