Почти девять лет назад миру был представлен новый опенсорсный проект под названием NuGet (www.NuGet.org). Спустя два года после своего дебюта NuGet начал поставляться в Microsoft Visual Studio, что актуально и по сей день. NuGet — это один из нескольких пакетных менеджеров (диспетчеров пакетов), таких как Node Package Manager (NPM) для JavaScript и Maven для Java. Пакетные менеджеры упрощают и автоматизируют использование библиотек. Например, если вам нужна библиотека для реализации JavaScript Object Notation (JSON) в вашем .NET-приложении, потребуется всего несколько кликов мышью, и ваше приложение получит мощные возможности, которые вам не нужно реализовывать самим, совершенно бесплатно.
Когда-то разработчики создавали и поддерживали свои собственные библиотеки. Если вам нужна была какая-либо библиотека, скорее всего, вы вежливо спрашивали о ней коллег-разработчиков в онлайн-сообществах где-нибудь на CompuServe, что было принято в подобных сообществах, и были все шансы, что вы могли получить библиотеку, отвечающую вашим потребностям или, по крайней мере, рекомендации, как самому ее создать.
Сегодня программное обеспечение с открытым исходным кодом (Open Source Software - OSS) создало беспрецедентную доступность для систем управления кодом и пакетами, которые делают внедрение этого кода в ваши приложения невероятно легким процессом. Однако, этот прогресс принес не только многочисленные преимущества, но и новые риски и проблемы. Одним из недавних примеров является инцидент с event-stream в ноябре 2018 года, связанный с NPM. Эта статья посвящена тому, как ответственно использовать NuGet в Visual Studio, чтобы снизить подобные риски.
Если вы работаете в публичной компании, регулируемой SOX, или подпадаете под действие HIPAA или PCI, и ваши приложения напрямую зависят от какого-либо публичного NuGet-источника, то есть все шансы, что ваша компания может нарушать вышеупомянутые стандарты, несмотря на отсутствие каких-либо неблагоприятных инцидентов.
Ни одно приложение, работающее в продакшене, или процесс сборки никогда не должны напрямую зависеть от каких-либо пакетов из публичных источников.
Если вы не очень хорошо знакомы с NuGet
В случае, если вы не знакомы с NuGet, не знаете что это такое и как он работает в целом, то я рекомендую вам почитать официальную документацию, где вы можете найти всю интересующую вас информацию. А если вы являетесь подписчиком Pluralsight, вы можете посмотреть мой курс “Введение в NuGet”.
Представленные здесь концепции не требуют глубокого понимания NuGet. Целевая аудитория включает в себя как опытных разработчиков, так и директоров с менеджерами, отвечающих за реализацию политик компании в области безопасности и снижения рисков.
Пакетные менеджеры и источники пакетов
Прежде чем углубляться в базовые концепции пакетных менеджеров в .NET/Visual Studio с NuGet, давайте скажем пару слов о пакетных менеджерах и пакетах в целом. Ниже приведены основные понятия, которые вам нужно знать:
Пакет: Архивный файл (например, zip или tar), который содержит артефакты кода и дополнительные метаданные для пакетного менеджера, которые, в свою очередь, используются средой разработки для добавления содержимого пакета в проект.
Пакетный менеджер: Инструмент, который среда разработки приложений (например, Visual Studio, Eclipse и т. д.) использует для получения доступа к пакетам, содержащимся в источнике пакетов. Распространенными пакетными менеджерами являются NuGet, Maven и Node Package Manager (NPM). Пакетный менеджер берет на себя не только управление доступом к конкретному пакету, он также заботится о доступе к другим пакетам, от которых зависит загруженный пакет (т. е. занимается управлением зависимостями).
Источник пакетов: набор пакетов, где для каждого пакета содержатся метаданные о нем. Эти метаданные включают номер текущей версии, историю релизов, ссылки на репозиторий с исходным кодом (например, на GitHub), документацию, информацию о лицензии. Среди самых популярных источников пакетов можно выделить NuGet.org, MyGet и npmjs.com.
Компаниям следует самим создавать собственные пакеты и заниматься управлением ими и их зависимостями, а также стремиться к созданию и использованию своих собственных внутренних источников пакетов.
Отношения между этими тремя элементами довольно просты: среда разработки приложений использует пакетный менеджер для подключения к источнику пакетов и получения от него пакетов, которые будут использованы в проекте разрабатываемого приложения.
Каковы риски?
Из трех элементов, приведенных в списке выше, риск возникает из-за двух: пакетов и источников пакетов. Источники пакетов, такие как npmjs.com и NuGet.org, являются открытыми до такой степени, что любой желающий может создать учетную запись и загрузить туда пакет, который впоследствии будет загружаться другими. Из-за одной только этой причины такие открытые источники пакетов ненадежны по своей сути. Означает ли это, что вам следует избегать открытых источников? Конечно нет. Это означает, что при получении пакетов из таких источников вы должны проявлять должную осмотрительность, проверяя содержимое этих пакетов. Если вы не можете с уверенностью определить происхождение пакета и его содержимое, вы подвергаете свою фирму риску, которого в противном случае можно было бы избежать. Реальным примером такого риска и его последствий стал инцидент с event-stream, обнаруженный в ноябре 2018 года. Этот инцидент был связан с вредоносным кодом в пакете, который собирал данные об учетных записях со счетов, имеющих баланс биткоинов выше определенного уровня. The Register сообщил, что код являлся частью популярной NPM-библиотеки, которая в среднем загружалась до двух миллионов раз в неделю.
Если вы не можете с уверенностью определить происхождение пакета и его содержимое, вы подвергаете свою фирму риску, которого в противном случае можно было бы избежать.
С одной стороны, открытые источники пакетов делают код легко доступным. С другой стороны, эти открытые источники пакетов НЕ ОСУЩЕСТВЛЯЮТ и, возможно, НЕ МОГУТ В ПРИНЦИПЕ контролировать наличие вредоносного кода в пакетах. Так кто должен следить за этим? Ответ очень прост: ВЫ! Если вы привносите пакет в свою организацию, вы обязаны проверить не только содержимое пакета, но и содержимое всех остальных пакетов, от которых зависит загруженный пакет.
Управление зависимостями — еще одна приятная функция, осуществляемая пакетным менеджером. Если вам показалось, что внедрение вредоносного пакета в вашу организацию похоже на распространение заразного вируса, вы все правильно поняли.
Дело в том, что ни одно приложение, работающее в продакшене, или процесс сборки никогда не должны напрямую зависеть от каких-либо пакетов из публичных источников. Помимо злоумышленников, есть много других менее опасных причин не доверять публичным источникам пакетов:
Вы оставляете за владельцем пакета все, что касается управления зависимостями и версиями. Что, если владелец пакета введет зависимость, которая необходима для работы пакета, но полностью несовместима с вашим приложением?
Что, если владелец пакета загрузит новую версию пакета, которая будет работать, но тем не менее станет причиной бага в вашем приложении? Если вы настроили процесс сборки на автоматическое обновление пакетов, то это может стать причиной дорогостоящей ошибкой, на исправление которой вам придется потратить реальные деньги.
Компаниям следует самим создавать собственные пакеты и заниматься управлением ими и их зависимостями, а также стремиться к созданию и использованию своих собственных внутренних источников пакетов. Если вы используете пакет из публичного источника, вы следует сначала открыть пакет и оценить его содержимое, и только после этого добавлять этот пакет в свой проект или его содержимое в свой собственный пакет.
Разве подписывание пакетов не снижает риск?
Одним словом, да, но это “да” с оговорками. Подписывание снижает некоторые риски, но не все. Подписывание не предотвратило бы инцидент с event-stream. Единственное, что делает подписывание пакета, — это удостоверяет автора/контрибьютора пакета. Безусловно, в большинстве сред вы можете ограничить пакеты, которые вы можете использовать, по определенным авторам. Если у вас есть публичный ключ, то брать можно только те пакеты, которые подписаны авторским сертификатом. Однако это не означает, что вы можете расслабиться и не глядя брать любой пакет от этого автора. Что, если авторский сертификат был скомпрометирован? Что, если автор допустил невинную ошибку, в результате которой ваша компания может понести ущерб?
Теперь, когда у вас есть какое-никакое представление о пакетах, пакетных менеджерах и источниках пакетов, а также о связанных с ними рисках, давайте применим эти знания к NuGet.
Краткий обзор NuGet: создание собственного источника NuGet
Как я уже говорил ранее, эта статья не является подробным практическим руководством по NuGet. Если вас интересует таковое, то вам следует обратиться к материалам, представленным в начале этой статьи. В предыдущем разделе я описывал как взаимосвязаны пакеты, пакетные менеджеры и источники пакетов — NuGet использует тот же подход. Для Visual Studio NuGet является стандартным встроенным пакетным менеджером, и найти его можно как показано на рисунке 1.
Если вы используете пакет из публичного источника, вам следует сначала открыть пакет и оценить его содержимое, и только после этого добавлять этот пакет в свой проект или его содержимое в свой собственный пакет.
Также на рисунке 1 показан источник пакета. Скорее всего, вашим активным источником пакетов является NuGet.org. В моем случае это нечто с именем Local Package Source. Рисунок 2 иллюстрирует, что это такое:
Как видите, локальный источник NuGet — это просто каталог на моем рабочем компьютере. Это может слегка шокировать, но создать источник NuGet так же просто, как создать каталог! На рисунке 3 показаны пакеты NuGet в этом каталоге:
Анатомия пакета NuGet
Пакет NuGet — это просто ZIP-архив с другим расширением (.nupkg). На рисунке 4 показано, как открыть его содержимое.
На рисунке 5 показано содержимое пакета. Давайте посмотрим, что находится внутри одного из самых популярных и широко используемых пакетов NuGet: NewtonSoft.Json.
Глядя на рисунок 5, представляющие особый интерес элементы — это папка lib и файлы подписи, лицензии и nuspec:
Папка lib: эта папка содержит одну или несколько вложенных папок, названных в соответствии с соглашением об именовании для каждой поддерживаемой версии .NET. Вы можете узнать больше о поддержке сразу нескольких версий .NET здесь.
Файл .signature.p7s: как следует из названия, это файл подписи, подписанный авторским сертификатом. Дополнительную информацию о том, как подписывать пакеты NuGet, можно найти здесь. Вы также можете узнать, как требовать, чтобы были доступны только подписанные пакеты, и ограничить пакеты определенными авторами здесь.
License.md: это разметочный файл, содержащий условия лицензии для вашего пакета. Как правило, это лицензия с открытым исходным кодом, такая как MIT, GNU или Apache 2.0.
Nuspec: Nuspec является файлом-манифестом. Это XML-файл, который используется для создания пакета NuGet. Про этот файл мы поговорим в следующем разделе.
Создание собственного пакета NuGet
Теперь вы понимаете, что такое пакеты, пакетные менеджеры и источники пакетов, и имеете общее представление о том, как NuGet вписывается в это пространство. Вы также понимаете, как создавать и ссылаться на свой собственный источник пакетов, используя не что иное, как простейшую паку с файлам. Осталось только научиться создавать собственный пакет NuGet. Чтобы проиллюстрировать это, я собираюсь использовать свою библиотеку неизменяемых классов, о которой я уже писал в прошлом.
Существует несколько подходов, которые можно использовать для создания пакетов NuGet. Я собираюсь показать вам метод, который я считаю самым понятным и простым в использовании. Есть также много других вариантов, которые вы можете применить, но я не буду здесь их описывать. Для полного охвата всего, что вы можете делать в рамках создания пакетов, обратитесь к документации на NuGet.org.
Шаг 1: Создайте структуру каталогов пакета (Package Directory Structure) и добавьте свои бинарники
Рисунок 6 иллюстрирует структуру каталогов.
Я добавил icon.png, который будет отображаться в пакетном менеджере, как показано на рисунке 1. Текстовый файл License содержит лицензию MIT. И, наконец, nuspec, показанный на рисунке 7:
Шаг 2: Создайте файл Nuspec
Файл nuspec, показанный на рисунке 7, очень прост.
Полную справку по nuspec можно найти здесь. Идентификатор (ID), который вы выбираете для своего пакета, должен быть уникальным в контексте источника, в котором он размещен. Соответственно, если вы решите сделать свой пакет NuGet доступным на NuGet.org, то идентификатор должен быть уникальным для этого источника. На рисунке 8 показано, как пакет отображается в пакетном менеджере NuGet:
Шаг 3: Создайте пакет NuGet
Чтобы создать пакет NuGet из командной строки, вам потребуются инструменты командной строки NuGet. На рисунке 9 показано, где можно скачать NuGet.exe.
На рисунке 10 показано, как создать пакет NuGet:
Шаг 4: Опубликуйте свой пакет
В зависимости от типа используемого источника пакетов ваши действия могут немного отличаться. Для источника-каталога процесс заключается в копировании файла в каталог. Если вы хостите свой собственный сервер NuGet, вам придется использовать один из методов, описанных здесь.
Другие варианты хостинга
Вместо самостоятельного хостинга или использования публичного NuGet.org вы можете выбрать стороннюю службу. Для NuGet существуют платные сервисы, такие как myget (myget.org) и Chocolatey (chocolatey.org). Если свой собственный источник так просто создать, то зачем вам может понадобиться платный сервис? Эти платные сервисы имеют собственную инфраструктуру аварийного восстановления (Disaster Recovery). Если вы хостите свой собственный источник, вам необходимо думать о том, как будет выполняться резервное копирование и репликация вашего сервера, а также как вы будете восстанавливаться в случае какого-либо катастрофического события.
Заключение
Опенсорс упростил добавление фич в ваши приложения. Частью этой простоты является скорость. Скорость и простота означают меньше “трения” (friction). Давным-давно, до опенсорса, каким мы его знаем сегодня, до интернета и до управления пакетами, в системе существовало неявное трение, которое давало нам время для оценки и анализа. Разработчики прошлого поколения, на мой взгляд, лучше разбирались в управлении изменениями. Они осознавали дисциплину и строгость, необходимые для снижения риска. При всех преимуществах современных технологий, а также скорости и простоте, которые мы получаем с ними, как никогда важно использовать методы снижения рисков, такие как те, что мы обсуждали в этой статье, потому что, если нам легче делать хорошие вещи, то также и злоумышленникам легче проворачивать их грязные делишки. Надежная защита и снижение рисков не бесплатны. Один из самых коварных негативных побочных эффектов бесплатного опенсорса — это ожидание того, что вещи, которые до сих пор имели цену, больше не имеют никакой цены. Учитывайте это в следующий раз, когда будете вводить пакет в вашу среду. Если ваша организация регулируется SOX, HIPAA, FINRA, PCI и т. д., и вы соответствуете этим требованиям, то вы не допустите такой ситуации.
Сегодня вечером в OTUS состоится открытый урок «Магические слова async / await», на котором разберем механизм, скрытый под ключевыми словами async/await. Также рассмотрим правильное использование этих ключевых слов и некоторые другие аспекты асинхронного программирования на C#. Регистрация по ссылке.
Комментарии (18)
Nbx
30.06.2022 16:22Каковы риски?
В определённый момент начнут покупать у владельцев популярные нугет пакеты, также как сейчас делают с расширениями браузеров. Вот тогда и будет веселуха.
С учётом того что при установки пакетов вызываются скрипты из под учётки разработчика, то можно не только в приложение всякое встроить (или например в build скрипт повесить на pre-build event), но и получить доступ к компьютеру(ам) и как входной хоп к сетки предприятия.
А видя как некоторые устанавливают пакеты десятками, то остаётся только ждать.
Хотя по сравнению с nodejs и гошниками, ситуация с c# ещё детский лепет. Там вообще оторопь берёт.
Моя мечта сделать нечто похожее на nuget, но для исходников. Чтобы встраивать в проект на уровне исходников и при желании не всё, а только нужное. Со всеми прелестями nuget, типа версионностью, рейтингами и т.п.falconandy
30.06.2022 17:13Хотя по сравнению с nodejs и гошниками, ситуация с c# ещё детский лепет. Там вообще оторопь берёт.
Пожалуйста, про гошников поконкретнее.Nbx
30.06.2022 17:16Я про количество внешних пакетов которое тянет к себе более-менее большое приложение. Наверное потому что стандартных системных библиотек не очень много по сравнению с .net
Недавно видел довольно ответсвенное приложение, делающе не очень много. Так там наверное под сотню пакетов, хрен пойми от каких сторонних разработчиков. Плюс зависимости, от зависимостей и так далее по рекурсии.
Как такому доверять — не очень понятно. Наверное закрыть глаза и надеяться на лучшее.
Почитать про потенциал habr.com/ru/company/ruvds/blog/346442falconandy
30.06.2022 18:23Вот, например, зависимости более-менее большого приложения — github.com/mattermost/mattermost-server/blob/master/go.mod
Не знаю, есть ли аналог такого приложения на C# (чтобы сравнить кол-во), но если пробежаться по зависимостям, то наверно что-то и окажется в стандарном .Net, но скорее всего в разы количество внешних пакетов не сократится.
В Go все пакеты распространяются в виде исходников — нет такой проблемы, что в репозитории один код, а в nuget/npm выложено что-то другое. Это же упрощает навигацию по внешним исходникам, их отладку и модификацию. Можно стандартным тулингом все внешние пакеты завендорить (все внешние исходники будут в папке vendor), провести их полный аудит, а затем при обновлении зависимостей проверять уже только диффы в папке vendor.Deosis
01.07.2022 06:58Для го это нормально, когда зависимости тянут 5 разных версий одного пакета?
github.com/blevesearch/zapx/v11 v11.3.4 // indirect github.com/blevesearch/zapx/v12 v12.3.4 // indirect github.com/blevesearch/zapx/v13 v13.3.4 // indirect github.com/blevesearch/zapx/v14 v14.3.4 // indirect github.com/blevesearch/zapx/v15v15.3.4// indirect
falconandy
01.07.2022 08:15Нормально. Есть соглашение, что при нарушении обратной совместимости мажорная версия пакета должна быть увеличена. Мажорные версии присутствуют в полном имени пакета как последний элемент (за исключением версий 0 и 1) — с т.з. тулинга это просто разные пакеты т.к. имена разные. Подробнее и точнее — go.dev/doc/modules/version-numbers
deitry
30.06.2022 17:32Моя мечта сделать нечто похожее на nuget, но для исходников
Теоретически в нугеты можно класть что угодно, и сорцы в том числе. Например, https://stackoverflow.com/a/52885223/7468485
при желании не всё, а только нужное
Теоретически в нугет можно положить хитро настроенный .targets с
блекджекомпропертями и кондишионами. Вот пример с условием по$(Platform)
. Ну или разбить на несколько пакетов и инклюдить каждый по необходимостиNbx
30.06.2022 17:40+1Я имею в виду что-то погибче. Например в середине .cs файла указываем нечто подобное
#suget SomePackage:SomeFunction:v.2.5
И в результате у меня в коде будет автоматом добавлена эта функция#suget SomePackage:SomeFunction:v.2.5 void SomeFunction() { Console.Out.WriteLine("Hello world!"); }
Если появится новая версия этой функции, я получу уведомление. И она будет обновлена только после моего код ревью и т.п.
Сделать подобное довольно просто, несколько способов есть. Но тут встаёт проблема курицы и яйца, пока нету достаточного количества пользователей, это всё не очень нужно. Ну и надо быть реалистами, лёгкость и удобство nuget тут не переплюнуть, а на безопасность большинству положить с пробором.Deosis
01.07.2022 07:01nuget рассчитан на пакеты покрупнее, за однострочниками в npm
А меленькие фрагменты кода - это сниппеты.
HavenDV
02.07.2022 10:19Когда то пытался сделать что-то похожее, с указаний версий и зависимостей методов в CDATA. Добавление методов происходило через авто-дополнение с помощью расширения.
https://github.com/HavenDV/OneCode
https://github.com/HavenDV/CSharpUtilities/blob/master/NetStandard20/Utilities/ResourcesUtilities.cs
Но в итоге понял, что это того не стоит. Сейчас многие из этих extensions использую в генераторах, которые добавляют его при необходимости, например: https://github.com/HavenDV/H.Resources.Generator
almaximort
30.06.2022 18:02Если серьезно подходить, то одним пакетным менеджером даже с собственными пакетами ограничиваться недостаточно. Один пакет тащит другой, а тот следующий. По-хорошему надо SBOM генерировать, а на выявленные компоненты анализатор уязвимостей, а потом смотреть, что с тем ужасом, который получится делать.
HavenDV
02.07.2022 10:34+2Вариант с ручным созданием .nuspec файла устарел, сейчас SDK-like проекты генерируют его автоматически на основе MSBuild свойств:
https://docs.microsoft.com/ru-ru/nuget/create-packages/creating-a-package-msbuild
AlexZaharow
Постоянно пользуюсь этим продуктом и есть даже внутренний репозиторий. Для меня есть существенный недостаток, который немного напрягает - это автоматическое инкрементирование версий пакетов в соответствии с версиями сборок. Нет ли информации как можно автоматизировать сборку пакета в соответствии с версией сборки?
deitry
Простите, а этим - это каким? В смысле, как именно собираете пакет? Насколько я понял, версия берётся из
.nuspec
, а туда при желании можно вписать вообще что угодно.Играл давеча с созданием нугетов и заливкой в GitHub Packages, вот пример минималистичного воркфлоу, версия подставляется из конфига, при этом там можно написать что угодно:
Simple GitHub action to build NuGet package
или альтернативный вариант, в котором пакет собирается по пушу тега, а версия берётся из названия этого тега (но это оказалось менее удобно)
Simple GitHub action to build NuGet package on tags push
AlexZaharow
Речь про nuget, конечно. Я имею в виду возможность задавать версию при локальной сборке по событию после успешного завершения сборки проекта. У меня происходит автоматический инкремент версии сборки в Visual Studio. Хочу видеть автоматический update номера версии в новом пакете nuget. Т.е.:
Изменения в проекте
Запуск сборки с автоматическим изменением версии
Создание локального пакета Nuget с обновлением версии пакета в п.2
Нужен п.3
deitry
Добавить в проект кастомный таргет с запуском команды пересборки пакета? В
.csproj
(в предположении, что версия доступна через пропертю)AlexZaharow
Прям мистика какая-то:
В справке самого пакета nuget нет параметра -Version. Однако он показывает его только если попросить справку по команде "pack":
Вы себе не предствляете, сколько я перечитал в своё время документации по поиску этой информации! И только как я увидел ваш пример, так это и помогло решить мою проблему! Только у меня в проекте на $(PackageVersion) , а@(VersionNumber) и использую я его в Build Events\Post build
Спасибо! Заработало.
HavenDV
Я в итоге для себя решил проблему с версионированием с помощью Conventional Commits спецификации. Сделал пакет, который автоматически присваивает версии, основываясь на feat:/fix:/BREAKING CHANGE префиксах к коммитам и вашей истории в git. Работает в том числе локально.
Вот здесь можно подсмотреть target, который переопределяет версию пакета во время сборки, его можно переписать на ваши условия:
https://github.com/HavenDV/ConventionalCommitsGitInfo/blob/main/src/libs/ConventionalCommitsGitInfo/build/ConventionalCommitsGitInfo.targets