diff --git a/Src/NuGetDefense/Configuration/Settings.cs b/Src/NuGetDefense/Configuration/Settings.cs index 145c6eec..e3ce0cae 100644 --- a/Src/NuGetDefense/Configuration/Settings.cs +++ b/Src/NuGetDefense/Configuration/Settings.cs @@ -7,6 +7,7 @@ namespace NuGetDefense.Configuration public class Settings { public bool WarnOnly { get; set; } = false; + public bool CheckTransitiveDependencies { get; set; } = true; public BuildErrorSettings ErrorSettings { get; set; } = new BuildErrorSettings(); diff --git a/Src/NuGetDefense/NuGetDefense.csproj b/Src/NuGetDefense/NuGetDefense.csproj index 597a9add..301f2c60 100644 --- a/Src/NuGetDefense/NuGetDefense.csproj +++ b/Src/NuGetDefense/NuGetDefense.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/Src/NuGetDefense/NuGetDefense.nuspec b/Src/NuGetDefense/NuGetDefense.nuspec index d8c8d6b2..21bd8cd8 100644 --- a/Src/NuGetDefense/NuGetDefense.nuspec +++ b/Src/NuGetDefense/NuGetDefense.nuspec @@ -3,7 +3,7 @@ NuGetDefense NuGetDefense - 1.0.7.2 + 1.0.8.0-beta Curtis Carter Curtis Carter https://github.com/DigitalCoyote/NuGetDefense diff --git a/Src/NuGetDefense/Program.cs b/Src/NuGetDefense/Program.cs index 2d7eb599..65ff7a41 100644 --- a/Src/NuGetDefense/Program.cs +++ b/Src/NuGetDefense/Program.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Xml; using System.Xml.Linq; using NuGet.Versioning; @@ -14,7 +16,7 @@ namespace NuGetDefense internal class Program { private static string _nuGetFile; - private const string UserAgentString = @"NuGetDefense/1.0.7.2 (https://github.com/digitalcoyote/NuGetDefense/blob/master/README.md)"; + private const string UserAgentString = @"NuGetDefense/1.0.8.0-beta (https://github.com/digitalcoyote/NuGetDefense/blob/master/README.md)"; private static NuGetPackage[] _pkgs; private static Settings _settings; @@ -25,10 +27,7 @@ internal class Program private static void Main(string[] args) { _settings = Settings.LoadSettings(Path.GetDirectoryName(args[0])); - var pkgConfig = Path.Combine(Path.GetDirectoryName(args[0]), "packages.config"); - _nuGetFile = File.Exists(pkgConfig) ? pkgConfig : args[0]; - - _pkgs = LoadPackages(_nuGetFile); + _pkgs = LoadPackages(args[0], _settings.CheckTransitiveDependencies).Values.ToArray(); if (_settings.ErrorSettings.BlackListedPackages.Length > 0) CheckForBlacklistedPackages(); if (_settings.ErrorSettings.WhiteListedPackages.Length > 0) foreach (var pkg in _pkgs.Where(p => !_settings.ErrorSettings.WhiteListedPackages.Any(b => @@ -64,6 +63,10 @@ private static void CheckForBlacklistedPackages() } } + /// + /// Removes CVE's from the vulnerablity reports if they ar ein the ignoredCVEs list + /// + /// private static void IgnoreCVEs(Dictionary> vulnDict) { foreach (var vuln in vulnDict.Values) @@ -76,42 +79,117 @@ private static void IgnoreCVEs(Dictionary /// Array of Packages used as the source /// Filtered list of packages - private static NuGetPackage[] IgnorePackages(IEnumerable nuGetPackages) + private static Dictionary IgnorePackages(Dictionary nuGetPackages) { - return nuGetPackages.Where(nuget => !_settings.ErrorSettings.IgnoredPackages - .Where(ignoredNupkg => ignoredNupkg.Id == nuget.Id) + return (Dictionary) nuGetPackages.Where(nuget => !_settings.ErrorSettings.IgnoredPackages + .Where(ignoredNupkg => ignoredNupkg.Id == nuget.Value.Id) .Any(ignoredNupkg => !VersionRange.TryParse(ignoredNupkg.Version, out var versionRange) || - versionRange.Satisfies(new NuGetVersion(nuget.Version)))).ToArray(); + versionRange.Satisfies(new NuGetVersion(nuget.Value.Version)))) + .ToDictionary(kv => kv.Key, kv => kv.Value); } /// /// Loads NuGet packages in use form packages.config or PackageReferences in the project file /// /// - public static NuGetPackage[] LoadPackages(string packageSource) + public static Dictionary LoadPackages(string projectFile, bool checkTransitiveDependencies = true) { - IEnumerable pkgs; - if (Path.GetFileName(packageSource) == "packages.config") - pkgs = XElement.Load(packageSource, LoadOptions.SetLineInfo).DescendantsAndSelf("package") - .Where(x => RemoveInvalidVersions(x)) - .Select(x => new NuGetPackage - { - Id = (x.AttributeIgnoreCase("id")).Value, Version = x.AttributeIgnoreCase("version").Value, - LineNumber = ((IXmlLineInfo) x).LineNumber, LinePosition = ((IXmlLineInfo) x).LinePosition - }); - else - pkgs = XElement.Load(packageSource, LoadOptions.SetLineInfo).DescendantsAndSelf("PackageReference") - .Where(x => RemoveInvalidVersions(x)) - .Select( - x => new NuGetPackage + var pkgConfig = Path.Combine(Path.GetDirectoryName(projectFile), "packages.config"); + var legacy = File.Exists(pkgConfig); + _nuGetFile = legacy ? pkgConfig : projectFile; + Dictionary pkgs = new Dictionary(); + + if (Path.GetFileName(projectFile) == "packages.config") + pkgs = XElement.Load(projectFile, LoadOptions.SetLineInfo).DescendantsAndSelf("package") + .Where(x => RemoveInvalidVersions(x)) + .Select(x => new NuGetPackage { - Id = x.AttributeIgnoreCase("Include").Value, Version = x.AttributeIgnoreCase("Version").Value, + Id = (x.AttributeIgnoreCase("id")).Value, Version = x.AttributeIgnoreCase("version").Value, LineNumber = ((IXmlLineInfo) x).LineNumber, LinePosition = ((IXmlLineInfo) x).LinePosition - }); + }).ToDictionary(p => p.Id); + else + pkgs = XElement.Load(projectFile, LoadOptions.SetLineInfo).DescendantsAndSelf("PackageReference") + .Where(x => RemoveInvalidVersions(x)) + .Select( + x => new NuGetPackage + { + Id = x.AttributeIgnoreCase("Include").Value, + Version = x.AttributeIgnoreCase("Version").Value, + LineNumber = ((IXmlLineInfo) x).LineNumber, + LinePosition = ((IXmlLineInfo) x).LinePosition + }).ToDictionary(p => p.Id);; + if(!legacy) + { + var resolvedPackages = dotnetListPackages(projectFile); + + if (checkTransitiveDependencies) + { + foreach (var (key, value) in pkgs.Where(package => resolvedPackages.ContainsKey(package.Key))) + { + resolvedPackages[key].LineNumber = value.LineNumber; + resolvedPackages[key].LinePosition = value.LinePosition; + } + + pkgs = resolvedPackages; + } + else + { + foreach (var (key, _) in pkgs) + { + pkgs[key].Version = resolvedPackages[key].Version; + } + } + } + else if (checkTransitiveDependencies) + { + Console.WriteLine( + $"{_nuGetFile} : Warning : Transitive depency checking skipped. 'dotnet list package --include-transitive' only supports SDK style NuGet Package References"); + } + + if (_settings.ErrorSettings.IgnoredPackages.Length > 0) pkgs = IgnorePackages(pkgs); + + return pkgs; + } + + /// + /// Uses 'dotnet list' to get a list of resolved versions and dependencies + /// + /// + /// + private static Dictionary dotnetListPackages(string projectFile) + { + Dictionary pkgs; + var startInfo = new ProcessStartInfo("dotnet") + { + Arguments = $"list {projectFile} package --include-transitive", + CreateNoWindow = true, + RedirectStandardOutput = true, + UseShellExecute = false, + }; + var dotnet = new Process() {StartInfo = startInfo}; + dotnet.Start(); + dotnet.WaitForExit(); + var output = dotnet.StandardOutput.ReadToEnd(); + + var lines = output.Split(Environment.NewLine); + var topLevelPackageResolvedIndex = lines[2].IndexOf("Resolved") - 8; + var transitiveHeaderIndex = Array.FindIndex(lines, l => l.Contains("Transitive Package")); + pkgs = lines.Skip(3).Take(transitiveHeaderIndex - 4).Select(l => new NuGetPackage + { + Id = l.Substring(l.IndexOf(">") + 2, topLevelPackageResolvedIndex - l.IndexOf(">") + 3).Trim(), + Version = l.Substring(topLevelPackageResolvedIndex).Trim() + }).ToDictionary(p => p.Id);; + + var transitiveResolvedColumnStart = output.Split(Environment.NewLine)[transitiveHeaderIndex].IndexOf("Resolved") - 8; - if (_settings.ErrorSettings.IgnoredPackages.Length > 0) pkgs = IgnorePackages(pkgs); + pkgs.Concat(lines.Skip(transitiveHeaderIndex + 1).SkipLast(2).Where(s => !string.IsNullOrWhiteSpace(s)) + .Select(l => new NuGetPackage + { + Id = l.Substring(l.IndexOf(">") + 2, transitiveResolvedColumnStart - l.IndexOf(">") + 3).Trim(), + Version = l.Substring(transitiveResolvedColumnStart).Trim() + }).ToDictionary(p => p.Id)); - return pkgs as NuGetPackage[] ?? pkgs.ToArray(); + return pkgs; } private static bool RemoveInvalidVersions(XElement x)