Недавно на youtube появилась запись доклада "10 Things I Do On Every .NET App - Scott Sauber"
Добавлю свои 5 копеек
1. BootstrapLogger pattern
public static async Task Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console(new JsonFormatter())
.CreateBootstrapLogger();
try
{
Log.Information("Starting BookLibrary");
var host = CreateHostBuilder(args).Build();
await host.MigrateAsync();
await host.RunAsync();
}
catch (Exception ex)
{
Log.Fatal(ex, "BookLibrary terminated unexpectedly");
}
finally
{
await Log.CloseAndFlushAsync();
}
}
Позволяет отправить в логи отладочную информацию о падении сервиса или ошибке её запуска.
Без обработчика ошибки такая информация чаще всего теряется.
2. Глобальный таймаут на Regex
На одном проекте много времени убили на поиск причины почему сервис вдруг перестает потреблять сообщения из шины и ребут не помогает.
После полного воспроизведения ситуации локально на продовых данных удалось выяснить, что Regex.IsMatch вызывался без заданного таймаута
и иногда попадался такой текст из-за которого сервис зависал намертво.
Было неприятно, но это легко пофиксилось переписыванием регулярки с обработкой возможного таймаута и добавлением глобального таймаута для защиты от такой ситуации.
С тех пор стараюсь в Program.cs везде добавлять настройку:
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromSeconds(1));
3. Ограничение размера тела HTTP запроса в Kestrel
builder.Services.Configure(options =>
{
options.Limits.MaxRequestBodySize = 20 * 1024; // 20 кб при превышении вернет 413 Payload Too Large
});
По-умолчанию лимит стоит 28.6 мегабайт, что очень много.
Если мы знаем что наш API не будет или не должен принимать большой объём данных, то можно снизить это дефолтное значение для уменьшения возможных атак на сервис.
Для отдельных запросов типа загрузки файлов этот лимит можно точечно настроить атрибутом [RequestSizeLimit]
Обычно такие лимиты могут настраиваться на стороне reverse proxy, но приложение всегда лучше знает о своих потребностях и ограничениях
и нужно помнить, что далеко не всегда приложение будет хоститься именно за этим reverse proxy.
4. Глобальный таймаут на запросы
services.AddRequestTimeouts(o => o.DefaultPolicy = new RequestTimeoutPolicy
{
Timeout = TimeSpan.FromSeconds(30)
});
app.UseRequestTimeouts();
По-умолчанию никакого таймаута нет. Таймауты обычно проставляются по-умолчанию на reverse proxy, но лучше это сделать и в приложении.
Добавление этих трех строк выше не гарантирует что запросы начнут отменяться. Оно лишь будет инициировать отмену запроса не дожидаясь когда отменит запрос клиент (если вообще отменит).
Для того чтобы запросы отменялись и расходовали лишние ресурсы нужно использовать CancellationToken.
5. Сокрытие деталей исключений JSON сериализатора (и не только)
По-умолчанию, если в API передать кривой JSON (например null, там где ожидается Guid), то AspNetCore выдаст отладочную информацию,
раскрывая внутренние типы и детали работы сериализатора. Также могут протечь какие-нибудь более чувствительные стектрейсы, что откроет еще больше зоны для атаки.
{
"type": "https://httpstatuses.io/400",
"title": "Некорректные параметры запроса",
"status": 400,
"errors": {
"$.id": [
"The JSON value could not be converted to System.Guid. Path: $.id | LineNumber: 2 | BytePositionInLine: 8."
]
}
}
Для прода рекомендуется отключить:
services
.AddControllers()
.AddJsonOptions(o => o.AllowInputFormatterExceptionMessages = false);
Тогда ошибка уже в будет в таком виде
{
"type": "https://httpstatuses.io/400",
"title": "Некорректные параметры запроса",
"status": 400,
"errors": {
"$.id": [
"The input was not valid."
]
}
}
Добавлю свои 5 копеек
1. BootstrapLogger pattern
public static async Task Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console(new JsonFormatter())
.CreateBootstrapLogger();
try
{
Log.Information("Starting BookLibrary");
var host = CreateHostBuilder(args).Build();
await host.MigrateAsync();
await host.RunAsync();
}
catch (Exception ex)
{
Log.Fatal(ex, "BookLibrary terminated unexpectedly");
}
finally
{
await Log.CloseAndFlushAsync();
}
}
Позволяет отправить в логи отладочную информацию о падении сервиса или ошибке её запуска.
Без обработчика ошибки такая информация чаще всего теряется.
2. Глобальный таймаут на Regex
На одном проекте много времени убили на поиск причины почему сервис вдруг перестает потреблять сообщения из шины и ребут не помогает.
После полного воспроизведения ситуации локально на продовых данных удалось выяснить, что Regex.IsMatch вызывался без заданного таймаута
и иногда попадался такой текст из-за которого сервис зависал намертво.
Было неприятно, но это легко пофиксилось переписыванием регулярки с обработкой возможного таймаута и добавлением глобального таймаута для защиты от такой ситуации.
С тех пор стараюсь в Program.cs везде добавлять настройку:
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromSeconds(1));
3. Ограничение размера тела HTTP запроса в Kestrel
builder.Services.Configure(options =>
{
options.Limits.MaxRequestBodySize = 20 * 1024; // 20 кб при превышении вернет 413 Payload Too Large
});
По-умолчанию лимит стоит 28.6 мегабайт, что очень много.
Если мы знаем что наш API не будет или не должен принимать большой объём данных, то можно снизить это дефолтное значение для уменьшения возможных атак на сервис.
Для отдельных запросов типа загрузки файлов этот лимит можно точечно настроить атрибутом [RequestSizeLimit]
Обычно такие лимиты могут настраиваться на стороне reverse proxy, но приложение всегда лучше знает о своих потребностях и ограничениях
и нужно помнить, что далеко не всегда приложение будет хоститься именно за этим reverse proxy.
4. Глобальный таймаут на запросы
services.AddRequestTimeouts(o => o.DefaultPolicy = new RequestTimeoutPolicy
{
Timeout = TimeSpan.FromSeconds(30)
});
app.UseRequestTimeouts();
По-умолчанию никакого таймаута нет. Таймауты обычно проставляются по-умолчанию на reverse proxy, но лучше это сделать и в приложении.
Добавление этих трех строк выше не гарантирует что запросы начнут отменяться. Оно лишь будет инициировать отмену запроса не дожидаясь когда отменит запрос клиент (если вообще отменит).
Для того чтобы запросы отменялись и расходовали лишние ресурсы нужно использовать CancellationToken.
5. Сокрытие деталей исключений JSON сериализатора (и не только)
По-умолчанию, если в API передать кривой JSON (например null, там где ожидается Guid), то AspNetCore выдаст отладочную информацию,
раскрывая внутренние типы и детали работы сериализатора. Также могут протечь какие-нибудь более чувствительные стектрейсы, что откроет еще больше зоны для атаки.
{
"type": "https://httpstatuses.io/400",
"title": "Некорректные параметры запроса",
"status": 400,
"errors": {
"$.id": [
"The JSON value could not be converted to System.Guid. Path: $.id | LineNumber: 2 | BytePositionInLine: 8."
]
}
}
Для прода рекомендуется отключить:
services
.AddControllers()
.AddJsonOptions(o => o.AllowInputFormatterExceptionMessages = false);
Тогда ошибка уже в будет в таком виде
{
"type": "https://httpstatuses.io/400",
"title": "Некорректные параметры запроса",
"status": 400,
"errors": {
"$.id": [
"The input was not valid."
]
}
}