SolarWinds is a large organization with software written in many programming languages, but the core language for our flagship Orion® Platform is C#, which runs on the Microsoft .NET Framework. The .NET Framework uses NuGet as a package manager. After the SUNBURST security incident in late 2020, we’ve focused extra attention on further strengthening countermeasures for our threat model. This includes initiatives unrelated to SUNBURST, like auditing third-party dependencies that could be compromised in a supply chain attack. If you’re a .NET developer, you may be interested in using Microsoft MSBuild SDKs to improve your build process and make it easier to audit your third party dependencies.
But what is MSBuild, you ask? MSBuild is a platform for building applications. You create XML project files that comply with the MSBuild schema, and msbuild.exe parses your project and solution files to build the software. Tools like Visual Studio make it easier to create and edit project files, but you can author them with any text editor. One of the great features of MSBuild is you can extend it by creating and importing .targets files to allow you to run custom tasks.
Microsoft provides several of their own extensions to MSBuild, including a GitHub repository for the MSBuild SDKs. These extensions provide useful capabilities for many projects. Here are some of the MSBuild SDKs we use at SolarWinds:
- Build.Artifacts allows you to specify which build outputs you’d like to capture in an artifacts folder. It provides simplicity and consistency when working with a hosted build system like GitHub Actions or AppVeyor.
- Build.Traversal is an alternative to Visual Studio solution files. You can create a simple dirs.proj file the specifies which projects you would like to build. However, you can't open these files with Visual Studio; they only support command line builds at present.
Both of these are useful, but today I'd like to focus on a different MSBuild SDK, Microsoft.Build.CentralPackageVersions.
.NET developers typically work with a solution containing several project files. Each project file produces an assembly—a single DLL or EXE file. Typically, each project file has a list of the third-party NuGet packages it depends on. This makes it difficult to compile a unified list of all dependencies across all projects, and it opens the possibility of mismatched versions of the same NuGet package in different projects. The NuGet Package Manager in Visual Studio can help, but there are better alternatives.
Microsoft.Build.CentralPackageVersions centralizes the specification of NuGet package dependency inputs for your solution in a single Packages.props file. This file lists the exact version number of each NuGet package.
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup> <PackageReference Update="Microsoft.Extensions.Logging" Version="5.0.0" /> <PackageReference Update="Newtonsoft.Json" Version="13.0.1" /> ... </ItemGroup> </Project>
In your project files, you can then reference the particular NuGet packages needed by that project, but without specifying the version.
<ItemGroup> <PackageReference Include="Newtonsoft.Json" /> </ItemGroup>
If necessary, you can override a version number for a particular project. This should be done with caution, but it may be a reasonable concession in certain cases, such as unit test projects that use a different version of a unit test or mocking library.
Although centralizing the list of your dependencies is valuable, I’d be remiss if I failed to highlight the importance of auditing your build outputs. One technique is to generate a Software Bill of Materials (SBOM) file. This evolving standard allows a build process to describe all the "ingredients" used to build the finished product. .NET developers can use the CycloneDX module for .NET to produce such files, provided they’ve migrated from the .NET Framework to .NET Core.
.NET developers can use Microsoft.Build.CentralPackageVersions to centralize the management of their NuGet package dependencies. This makes it easier to audit your risks and prevent supply chain attacks.