Наверное, в любых проектах есть необходимость использования различных секретных данных - строки подключения к БД, АПИ-ключи внешних сервисов и т.д.. К сожалению, до сих пор далеко не всегда разработчики заботятся о соответствующей защите этих данных: на прошлой неделе Гитхаб даже выкатил функцию push protection, чтобы у пользователя был ещё один рубеж защиты от проникновения этих данных в репозиторий.
Несмотря на то, что на рынке довольно много облачных сервисов для хранения и управления секретами, ввиду их зарубежного происхождения с недавних пор их использование стало затруднительно. Но мы не унываем, потому что на наших просторах появился Яндекс.Облако Локбокс.
Давайте добавим работу с секретами из Яндекс.Облака в .Net Core приложение в виде одного из источников конфигурации.
Когда разработчик думает о развёртывании приложения и его секретах, то первыми приходят на ум переменные окружения (сегодня мы не рассматриваем Кубернетис, а будем говорить о классическом развёртывании на виртуальные машины или облачные сервисы). Действительно, у такого решения много плюсов - отсутствие внешней зависимости, простота конфигурирования и так далее. Но они перекрываются одним жирным минусом - это небезопасно. Переменные окружения хранятся в незашифрованном виде и доступны любому процессу в системе. К тому же любая библиотека вашего приложения сможет получить доступ к этим данным, поэтому вы должны быть очень уверены в поставщиках своих зависимостей.
Вынос секретов вовне предполагает внедрение дополнительной зависимости, но добавляет удобства уравления и однозначно добавляет очков безопасности вашему решению: малваря, которая может распространиться по среде вашего приложения, уже не сможет просто так забрать всё в укромный уголок.
Ввиду этого, в переменных окружения есть смысл хранить только те данные, с помощью которых нужно каким-либо образом авторизоваться во внешнем поставщике секретов.
Держа в уме это сокровенное знание, переходим к практике.
Создание секрета в Yandex.Cloud Lockbox
Не так давно в Яндекс.Облаке был анонсирован сервис Lockbox, который представляет из себя хранилище секретов в виде набора пар ключ-значение (до 32-х пар в одном секрете). На данный момент сервис всё ещё находится в предварительном виде и не тарифицируется, но вы можете запросить к нему доступ и уже сейчас воспользоваться этим функционалом.
Для начала откроем консоль Яндекс.Облака и создадим сервисную учётную запись с ролью "lockbox.payloadViewer". Её идентификатор нам понадобится в будущем.
Следующим шагом создадим новый авторизованный ключ для этой сервисной учётки. Нам необходимо записать идентификатор этого ключа, а также приватный ключ. Приватный ключ нам понадобится позже, когда наше приложение будет аутентифицироваться в Облаке с помощью JWT-токена.
Теперь можно создать первый секрет
Обратите внимание на ключ секрета. Стандартный построитель конфигурации дотнетного приложения IConfigurationBuilder (Microsoft.Extensions.Configuration) умеет работать с иерархией, поэтому мы в .Net можем строить сложные конфигурационные объекты. Разбор древовидной структуры происходит с помощью разделителя, по-умолчанию это символ ":", но, к сожалению, этот символ не поддерживается в поле Ключ, поэтому приходится заменить его другим.
Теперь мы создали всё на стороне Локбокса и можно приступать к настройке своего приложения.
Добавление секрета в .Net Core приложение
Для начала добавим нужные для работы и не секретные параметры в настройки приложения (appsettings.json):
{
"YC": {
"ConfigurationSecretId": "e6q3a82d6m2bkltjar6q",
"ServiceAccountId": "ajk2cdb9jq2mk4unqq13",
"ServiceAccountAuthorizedKeyId": "ak228rm0obcn5o90ij43"
}
}
Также добавьте приватный ключ авторизованного ключа в переменную окружения YC_PRIVATE_KEY.
Для того, чтобы ваше приложение могло взять секреты из Локбокса, ему необходимо аутентифицироваться в Облаке. Для аутентификации есть несколько вариантов:
OAuth-токен - не самый лучший вариант так как привязан непосредственно к вашему пользователю и имеет срок жизни 1 год.
JWT-токен - отлично подходит для аутентификации приложений. Привязан к сервисной учётке и аутентифицирует приложение с помощью короткоживущего IAM-токена.
API-ключ и Статический ключ - простая аутентификация бессрочным ключом, но поддерживаются не все сервисы Облака.
Из предложенного мы воспользуемся JWT-токеном как наиболее безопасным и универсальным вариантом.
Не так давно команда Яндекса начала работу над SDK для платформы .Net (спасибо им!), но, к сожалению, в нём пока не реализован провайдер авторизации с помощью JWT-токена, поэтому пришлось реализовать его самому.
Добавьте в ваше приложение (.Net 6) пакет Delobytes.Extensions.Configuration:
dotnet add package Delobytes.Extensions.Configuration
И добавьте вызов расширения AddYandexCloudLockboxConfiguration в его настройки конфигурации:
WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddYandexCloudLockboxConfiguration(config =>
{
IConfigurationRoot tempConfig = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json")
.Build();
config.PrivateKey = Environment.GetEnvironmentVariable("YC_PRIVATE_KEY");
config.ServiceAccountId = tempConfig.GetValue<string>("YC:ServiceAccountId");
config.ServiceAccountAuthorizedKeyId = tempConfig.GetValue<string>("YC:ServiceAccountAuthorizedKeyId");
config.SecretId = tempConfig.GetValue<string>("YC:ConfigurationSecretId");
config.Path = "MyPath";
config.PathSeparator = '-';
config.Optional = false;
config.ReloadPeriod = TimeSpan.FromDays(7);
config.LoadTimeout = TimeSpan.FromSeconds(20);
config.OnLoadException += exceptionContext =>
{
//log
};
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
WebApplication? app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Расширение создаст JWT-токен, обменяет его на IAM-токен и загрузит указанный SecretId. В вашу конфигурацию будут загружены все пары "ключ-значение" и они будут подгружаться снова через указанный промежуток времени.
В результате во всём приложении вы сможете получать секреты с помощью стандартных механизмов работы с конфигурацией. Например, вы можете создать соответствующий объект и замапить на него ваш секрет:
public class AppSecrets
{
public string SecretServiceToken { get; set; }
}
[Route("/")]
[ApiController]
public class HomeController : ControllerBase
{
public HomeController(IConfiguration config)
{
_config = config;
}
private readonly IConfiguration _config;
[HttpGet("")]
public IActionResult Get()
{
AppSecrets secrets = _config.GetSection(nameof(AppSecrets)).Get<AppSecrets>();
return Ok();
}
}
Итогo
В результате проделанного опыта можно в очередной раз порадоваться гибкости .Net - простота интеграции в существующую Asp Net Core среду (спасибо Майкрософт за наше счастливое детство) позволяет производить миграции с одной платформы на другую в рекордные сроки.
Надеюсь, что Яндекс.Облако и дальше продолжит радовать разработчиков годными сервисами и будет расширять свою поддержку .Net-сообщества, а сообщество будет работать над тем, чтобы не пополнять печальную статистику утёкших секретных данных.
Всем добра!
Комментарии (2)
alexalok
14.04.2022 14:23+1Честно говоря, так и не понял, почему при использовании этого сервиса безопасность системы станет выше. В статье сказано, что хранить секреты в переменных окружения опасно, поэтому предлагается вынести их в отдельное хранилище, данные для доступа к которому предлагается записать... в переменные окружения!
alexsandr0000
Интересно, какая вероятность того, что твои секреты в один прекрасный момент не утекут или их не дропнут? :)
Да и либа "Delobytes.Extensions.Configuration" выглядит сыроватой, можно было бы и не хардкодить адрес "iam.api.cloud.yandex.net" и тесты добавить.