Verify sign requests (#531)
- Ensure every artifact info has a matching FilesToSign or FilesToExcludeFromSigning item - Add a NuGet package verifier rule to ensure all files in a nupkg are accouned for in the sign request - Add test project for NuGetPackageVerifier
This commit is contained in:
Родитель
3e0fb6dc52
Коммит
f85a36ecd0
|
@ -92,6 +92,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiCheckForwardDestination"
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Internal.AspNetCore.SiteExtension.Sdk", "src\Internal.AspNetCore.SiteExtension.Sdk\Internal.AspNetCore.SiteExtension.Sdk.csproj", "{418F99A5-5EC4-4895-B8EB-7F8BBA241DB2}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGetPackageVerifier.Tests", "test\NuGetPackageVerifier.Tests\NuGetPackageVerifier.Tests.csproj", "{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -166,6 +168,10 @@ Global
|
|||
{418F99A5-5EC4-4895-B8EB-7F8BBA241DB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{418F99A5-5EC4-4895-B8EB-7F8BBA241DB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{418F99A5-5EC4-4895-B8EB-7F8BBA241DB2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -189,6 +195,7 @@ Global
|
|||
{85C30B96-BCEA-4C9C-99AA-6DAEB448EEF3} = {BD3545FB-5520-43DF-B4F9-83BEA3A38ECA}
|
||||
{605F0478-A9D2-4A8A-BB38-9D5DC132FBB5} = {60A938B2-D95A-403C-AA7A-3683AD64DFA0}
|
||||
{418F99A5-5EC4-4895-B8EB-7F8BBA241DB2} = {A4F4353B-C3D2-40B0-909A-5B48A748EA76}
|
||||
{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2} = {60A938B2-D95A-403C-AA7A-3683AD64DFA0}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {1B8809C8-A6C3-4761-BC91-B12841F49AE1}
|
||||
|
|
|
@ -54,5 +54,4 @@ extending the *DependsOn property
|
|||
|
||||
<!-- For external analysis of inputs/outputs. -->
|
||||
<Target Name="GetArtifactInfo" DependsOnTargets="$(GetArtifactInfoDependsOn)" Returns="@(ArtifactInfo)" />
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -27,7 +27,8 @@ that matches "$(RepositoryRoot)/shared/*.Sources".
|
|||
Condition="@(SharedSourceDirectories->Count()) != 0"
|
||||
BuildInParallel="true">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="ArtifactInfo" />
|
||||
<Output TaskParameter="TargetOutputs" ItemName="ExcludeFromSigning" Condition="'$(SignSourcesPackages)' != 'true'" />
|
||||
<Output TaskParameter="TargetOutputs" ItemName="FilesToExcludeFromSigning" Condition="'$(SignSourcesPackages)' != 'true'" />
|
||||
<Output TaskParameter="TargetOutputs" ItemName="FilesToSign" Condition="'$(SignSourcesPackages)' == 'true'" />
|
||||
</MSBuild>
|
||||
</Target>
|
||||
|
||||
|
|
|
@ -69,6 +69,11 @@
|
|||
<Version>$(NormalizedPackageVersion)</Version>
|
||||
<TargetFramework>$(TargetFramework)</TargetFramework>
|
||||
<RepositoryRoot>$(RepositoryRoot)</RepositoryRoot>
|
||||
<RepositoryUrl>$(RepositoryUrl)</RepositoryUrl>
|
||||
<Category>$(PackageArtifactCategory)</Category>
|
||||
<IsContainer>true</IsContainer>
|
||||
<Certificate>$(PackageSigningCertName)</Certificate>
|
||||
<ShouldBeSigned Condition=" '$(PackageSigningCertName)' != '' ">true</ShouldBeSigned>
|
||||
</ArtifactInfo>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<TargetFrameworks>$([MSBuild]::Escape($(TargetFrameworks)))</TargetFrameworks>
|
||||
<PackageType>$(PackageType)</PackageType>
|
||||
<RepositoryRoot>$(RepositoryRoot)</RepositoryRoot>
|
||||
<RepositoryUrl>$(RepositoryUrl)</RepositoryUrl>
|
||||
<Category>$(PackageArtifactCategory)</Category>
|
||||
<Certificate>$(PackageSigningCertName)</Certificate>
|
||||
<ShouldBeSigned Condition="'$(PackageSigningCertName)' != '' OR @(SignedPackageFile->Count()) != 0 ">true</ShouldBeSigned>
|
||||
|
@ -36,6 +37,7 @@
|
|||
<SourceIncluded>$(IncludeSource)</SourceIncluded>
|
||||
<PackageType>$(PackageType)</PackageType>
|
||||
<RepositoryRoot>$(RepositoryRoot)</RepositoryRoot>
|
||||
<RepositoryUrl>$(RepositoryUrl)</RepositoryUrl>
|
||||
<Category>$(PackageArtifactCategory)</Category>
|
||||
<Certificate>$(PackageSigningCertName)</Certificate>
|
||||
<ShouldBeSigned Condition="'$(PackageSigningCertName)' != '' OR @(SignedPackageFile->Count()) != 0 ">true</ShouldBeSigned>
|
||||
|
@ -47,19 +49,30 @@
|
|||
<Container>$(FullPackageOutputPath)</Container>
|
||||
</ArtifactInfo>
|
||||
|
||||
<ArtifactInfo Include="@(ExcludePackageFileFromSigning)">
|
||||
<ShouldBeSigned>false</ShouldBeSigned>
|
||||
<Container>$(FullPackageOutputPath)</Container>
|
||||
</ArtifactInfo>
|
||||
|
||||
<ArtifactInfo Include="@(SignedPackageFile)" Condition="'$(IncludeSymbols)' == 'true' AND '$(NuspecFile)' == ''">
|
||||
<ShouldBeSigned>true</ShouldBeSigned>
|
||||
<Container>$(SymbolsPackageOutputPath)</Container>
|
||||
</ArtifactInfo>
|
||||
|
||||
<ArtifactInfo Include="@(ExcludePackageFileFromSigning)" Condition="'$(IncludeSymbols)' == 'true' AND '$(NuspecFile)' == ''">
|
||||
<ShouldBeSigned>false</ShouldBeSigned>
|
||||
<Container>$(SymbolsPackageOutputPath)</Container>
|
||||
</ArtifactInfo>
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
####################################################################################
|
||||
Target: GetSignedPackageFiles
|
||||
|
||||
Gets itesm for built assemblies that will be in the package.
|
||||
Also supports projects that explicitly set SignedPackageFile.
|
||||
Gets items for built assemblies that will be in the NuGet package.
|
||||
Also supports projects that explicitly set items in the SignedPackageFile group.
|
||||
|
||||
Items:
|
||||
[out] SignedPackageFile
|
||||
|
|
|
@ -155,8 +155,14 @@ Executes /t:Pack on all projects matching src/*/*.csproj.
|
|||
</MSBuild>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Output from this target may include items representing assemblies inside the nupkg. -->
|
||||
<ArtifactInfo Include="@(_Temp)" Condition="'%(_Temp.Container)' == ''" />
|
||||
<Sign Include="@(_Temp)" Condition="'%(_Temp.Container)' != ''" />
|
||||
|
||||
<!-- Nupkgs or assemblies in the nupkg that should be signed -->
|
||||
<FilesToSign Include="@(_Temp)" Condition=" '%(_Temp.ShouldBeSigned)' == 'true' " />
|
||||
|
||||
<!-- Assemblies inside a nupkg that should not be signed -->
|
||||
<FilesToExcludeFromSigning Include="@(_Temp)" Condition=" '%(_Temp.ShouldBeSigned)' == 'false' AND '%(_Temp.Container)' != ''" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
|
|
|
@ -125,28 +125,31 @@ namespace KoreBuild.Tasks
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var item in Exclusions)
|
||||
if (Exclusions != null)
|
||||
{
|
||||
var normalizedPath = NormalizePath(BasePath, item.ItemSpec);
|
||||
|
||||
var containerPath = item.GetMetadata("Container");
|
||||
if (!string.IsNullOrEmpty(containerPath))
|
||||
foreach (var item in Exclusions)
|
||||
{
|
||||
if (!containers.TryGetValue(containerPath, out var container))
|
||||
var normalizedPath = NormalizePath(BasePath, item.ItemSpec);
|
||||
|
||||
var containerPath = item.GetMetadata("Container");
|
||||
if (!string.IsNullOrEmpty(containerPath))
|
||||
{
|
||||
Log.LogError($"Exclusion item '{item.ItemSpec}' specifies an unknown container '{containerPath}'.");
|
||||
continue;
|
||||
}
|
||||
if (!containers.TryGetValue(containerPath, out var container))
|
||||
{
|
||||
Log.LogError($"Exclusion item '{item.ItemSpec}' specifies an unknown container '{containerPath}'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var packagePath = item.GetMetadata("PackagePath");
|
||||
normalizedPath = string.IsNullOrEmpty(packagePath) ? normalizedPath : packagePath.Replace('\\', '/');
|
||||
var file = new SignRequestItem.Exclusion(normalizedPath);
|
||||
container.AddItem(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
var file = new SignRequestItem.Exclusion(normalizedPath);
|
||||
signRequestCollection.Add(file);
|
||||
var packagePath = item.GetMetadata("PackagePath");
|
||||
normalizedPath = string.IsNullOrEmpty(packagePath) ? normalizedPath : packagePath.Replace('\\', '/');
|
||||
var file = new SignRequestItem.Exclusion(normalizedPath);
|
||||
container.AddItem(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
var file = new SignRequestItem.Exclusion(normalizedPath);
|
||||
signRequestCollection.Add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,5 +30,7 @@ namespace KoreBuild.Tasks
|
|||
|
||||
// not used in code, but reserved for MSBuild targets
|
||||
public const int ArtifactInfoMismatch = 5002;
|
||||
public const int FilesToSignMismatchedWithArtifactInfo = 5003;
|
||||
public const int FilesToSignMissingCertInfo = 5004;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,35 +148,75 @@ and NodeJS.
|
|||
</Target>
|
||||
|
||||
|
||||
<!--
|
||||
####################################################################################
|
||||
Target: VerifySignRequestItems
|
||||
|
||||
Verifies all artifact items have a corresponding sign item.
|
||||
####################################################################################
|
||||
-->
|
||||
<Target Name="VerifySignRequestItems"
|
||||
DependsOnTargets="GetArtifactInfo"
|
||||
Condition="'$(GenerateSignRequest)' == 'true' AND '$(SkipArtifactVerification)' != 'true'">
|
||||
|
||||
<ItemGroup>
|
||||
<_ExpectedFileToSign Remove="@(_ExpectedFileToSign)" />
|
||||
<_ExpectedFileToSign Include="@(ArtifactInfo)" />
|
||||
<_ExpectedFileToSign Remove="@(FilesToSign);@(FilesToExcludeFromSigning);$(SignRequestOutputPath)" />
|
||||
<_FilesToSignMissingConfig Remove="@(_FilesToSignMissingConfig)" />
|
||||
<_FilesToSignMissingConfig Include="@(FilesToSign)" Condition=" '%(FilesToSign.Certificate)' == '' AND '%(FilesToSign.StrongName)' == '' AND '%(FilesToSign.IsContainer)' != 'true' " />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_SigningErrorMessage Condition=" @(_ExpectedFileToSign->Count()) != 0 ">
|
||||
Could not determine signing information for all ArtifactInfo items.
|
||||
Fix this error by adding these items to FilesToSign or FilesToExcludeFromSigning:
|
||||
- @(_ExpectedFileToSign, '%0A - ')
|
||||
</_SigningErrorMessage>
|
||||
</PropertyGroup>
|
||||
|
||||
<Error Text="$(_SigningErrorMessage.Trim())"
|
||||
Code="KRB5003"
|
||||
Condition=" @(_ExpectedFileToSign->Count()) != 0 " />
|
||||
|
||||
<PropertyGroup>
|
||||
<_SigningErrorMessage Condition=" @(_FilesToSignMissingConfig->Count()) != 0 ">
|
||||
The following FilesToSign did not specify a Certificate or StrongName to use.
|
||||
- @(_FilesToSignMissingConfig, '%0A - ')
|
||||
</_SigningErrorMessage>
|
||||
</PropertyGroup>
|
||||
|
||||
<Error Text="$(_SigningErrorMessage.Trim())"
|
||||
Code="KRB5004"
|
||||
Condition=" @(_FilesToSignMissingConfig->Count()) != 0 " />
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
####################################################################################
|
||||
Target: GenerateSignRequest
|
||||
|
||||
Generates a manifest that contains signin requests for files.
|
||||
|
||||
[in] (items) ArtifactInfo
|
||||
[in] (items) Sign
|
||||
[in] (prop) Artifacts
|
||||
[in] (items) FilesToSign
|
||||
[in] (items) FilesToExcludeFromSigning
|
||||
|
||||
[out] SignRequestOutputPath - the bom file
|
||||
####################################################################################
|
||||
-->
|
||||
<ItemGroup Condition=" '$(GenerateSignRequest)' == 'true' ">
|
||||
<ArtifactInfo Include="$(SignRequestOutputPath)">
|
||||
<ArtifactType>XmlFile</ArtifactType>
|
||||
<Category>noship</Category>
|
||||
</ArtifactInfo>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="GenerateSignRequest"
|
||||
DependsOnTargets="GetArtifactInfo"
|
||||
DependsOnTargets="GetArtifactInfo;VerifySignRequestItems"
|
||||
Condition=" '$(GenerateSignRequest)' == 'true' ">
|
||||
|
||||
<ItemGroup>
|
||||
<ArtifactInfo Include="$(SignRequestOutputPath)">
|
||||
<ArtifactType>XmlFile</ArtifactType>
|
||||
<Category>noship</Category>
|
||||
</ArtifactInfo>
|
||||
|
||||
<Sign Include="@(ArtifactInfo)" Condition=" '%(ArtifactInfo.ShouldBeSigned)' == 'true' " />
|
||||
</ItemGroup>
|
||||
|
||||
<GenerateSignRequest
|
||||
Requests="@(Sign)"
|
||||
Exclusions="@(ExcludeFromSigning)"
|
||||
Requests="@(FilesToSign)"
|
||||
Exclusions="@(FilesToExcludeFromSigning)"
|
||||
BasePath="$(ArtifactsDir)"
|
||||
OutputPath="$(SignRequestOutputPath)" />
|
||||
</Target>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
@ -28,6 +28,7 @@ namespace NuGetPackageVerifier.Rules
|
|||
new PackageTypesRule(),
|
||||
new PackageVersionMatchesAssemblyVersionRule(),
|
||||
new BuildItemsRule(),
|
||||
new SignRequestListsAllSignableFiles(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NuGetPackageVerifier.Manifests
|
||||
{
|
||||
public class PackageSignRequest
|
||||
{
|
||||
public ISet<string> FilesExcludedFromSigning { get; set; }
|
||||
public ISet<string> FilesToSign { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace NuGetPackageVerifier.Manifests
|
||||
{
|
||||
public class SignRequestManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents all signing requests in the sign request manifest that are for nupkg files.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, PackageSignRequest> PackageSignRequests { get; private set; }
|
||||
|
||||
public static SignRequestManifest Parse(string filePath)
|
||||
{
|
||||
using (var reader = File.OpenText(filePath))
|
||||
{
|
||||
return Parse(reader, Path.GetDirectoryName(filePath));
|
||||
}
|
||||
}
|
||||
|
||||
public static SignRequestManifest Parse(TextReader reader, string manifestBasePath)
|
||||
{
|
||||
var doc = XDocument.Load(reader);
|
||||
var requests = new Dictionary<string, PackageSignRequest>(StringComparer.OrdinalIgnoreCase);
|
||||
var manifest = new SignRequestManifest { PackageSignRequests = requests };
|
||||
|
||||
var nupkgContainers = doc.Root
|
||||
.Elements("Container")
|
||||
.Where(c => "nupkg".Equals(c.Attribute("Type")?.Value, StringComparison.Ordinal));
|
||||
|
||||
foreach (var container in nupkgContainers)
|
||||
{
|
||||
var request = new PackageSignRequest
|
||||
{
|
||||
FilesToSign = container.Elements("File").Select(GetPath).ToHashSet(StringComparer.Ordinal),
|
||||
FilesExcludedFromSigning = container.Elements("ExcludedFile").Select(GetPath).ToHashSet(StringComparer.Ordinal),
|
||||
};
|
||||
|
||||
var path = new FileInfo(Path.Combine(manifestBasePath, GetPath(container))).FullName;
|
||||
|
||||
requests.Add(path, request);
|
||||
}
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private static string GetPath(XElement element) => element.Attribute("Path")?.Value;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using NuGet.Packaging;
|
||||
using NuGetPackageVerifier.Logging;
|
||||
using NuGetPackageVerifier.Manifests;
|
||||
|
||||
namespace NuGetPackageVerifier
|
||||
{
|
||||
|
@ -14,6 +15,7 @@ namespace NuGetPackageVerifier
|
|||
private PackageArchiveReader _reader;
|
||||
|
||||
public FileInfo PackageFileInfo { get; set; }
|
||||
public PackageSignRequest SignRequest { get; set; }
|
||||
public IPackageMetadata Metadata { get; set; }
|
||||
public PackageVerifierOptions Options { get; set; }
|
||||
public IPackageVerifierLogger Logger { get; set; }
|
||||
|
@ -21,7 +23,7 @@ namespace NuGetPackageVerifier
|
|||
|
||||
public IDictionary<string, AssemblyAttributesData> AssemblyData { get; } = new Dictionary<string, AssemblyAttributesData>();
|
||||
|
||||
public void Dispose()
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_reader?.Dispose();
|
||||
}
|
||||
|
|
|
@ -37,6 +37,15 @@ namespace NuGetPackageVerifier
|
|||
);
|
||||
}
|
||||
|
||||
public static PackageVerifierIssue SignRequestMissingPackageFile(string id, string filePath)
|
||||
{
|
||||
return new PackageVerifierIssue(
|
||||
"FILE_MISSING_FROM_SIGN_REQUEST",
|
||||
filePath,
|
||||
string.Format("The sign request for package {0} does not specify what to do with signable file {1}", id, filePath),
|
||||
PackageIssueLevel.Error);
|
||||
}
|
||||
|
||||
public static PackageVerifierIssue PackageTypeMissing(string packageType)
|
||||
{
|
||||
return new PackageVerifierIssue(
|
||||
|
|
|
@ -10,6 +10,7 @@ using Microsoft.Extensions.CommandLineUtils;
|
|||
using Newtonsoft.Json;
|
||||
using NuGet.Packaging;
|
||||
using NuGetPackageVerifier.Logging;
|
||||
using NuGetPackageVerifier.Manifests;
|
||||
|
||||
namespace NuGetPackageVerifier
|
||||
{
|
||||
|
@ -25,6 +26,7 @@ namespace NuGetPackageVerifier
|
|||
var verbose = application.Option("--verbose", "Verbose output and assistance", CommandOptionType.NoValue);
|
||||
var ruleFile = application.Option("--rule-file", "Path to NPV.json", CommandOptionType.SingleValue);
|
||||
var excludedRules = application.Option("--excluded-rule", "Rules to exclude. Calculcated after composite rules are evaluated.", CommandOptionType.MultipleValue);
|
||||
var signRequest = application.Option("--sign-request", "Sign request manifest file.", CommandOptionType.SingleValue);
|
||||
var packageDirectory = application.Argument("Package directory", "Package directory to scan for nupkgs");
|
||||
|
||||
application.OnExecute(() =>
|
||||
|
@ -37,7 +39,7 @@ namespace NuGetPackageVerifier
|
|||
return ReturnBadArgs;
|
||||
}
|
||||
|
||||
if (!ruleFile.HasValue())
|
||||
if (!ruleFile.HasValue())
|
||||
{
|
||||
application.Error.WriteAsync($"Missing required option {ruleFile.Template}.");
|
||||
application.ShowHelp();
|
||||
|
@ -58,7 +60,7 @@ namespace NuGetPackageVerifier
|
|||
|
||||
// TODO: Show extraneous packages, exclusions, etc.
|
||||
var ignoreAssistanceMode = verbose.HasValue() ? IgnoreAssistanceMode.ShowAll : IgnoreAssistanceMode.ShowNew;
|
||||
|
||||
|
||||
var ruleFileContent = File.ReadAllText(ruleFile.Value());
|
||||
var packageSets = JsonConvert.DeserializeObject<IDictionary<string, PackageSet>>(
|
||||
ruleFileContent,
|
||||
|
@ -67,12 +69,17 @@ namespace NuGetPackageVerifier
|
|||
MissingMemberHandling = MissingMemberHandling.Error
|
||||
});
|
||||
|
||||
|
||||
var signRequestManifest = signRequest.HasValue()
|
||||
? SignRequestManifest.Parse(signRequest.Value())
|
||||
: default;
|
||||
|
||||
logger.LogNormal("Read {0} package set(s) from {1}", packageSets.Count, ruleFile.Value());
|
||||
var nupkgs = new DirectoryInfo(packageDirectory.Value).EnumerateFiles("*.nupkg", SearchOption.TopDirectoryOnly)
|
||||
.Where(p => !p.Name.EndsWith(".symbols.nupkg"))
|
||||
.ToArray();
|
||||
logger.LogNormal("Found {0} packages in {1}", nupkgs.Length, packageDirectory.Value);
|
||||
var exitCode = Execute(packageSets, nupkgs, excludedRules.Values, logger, ignoreAssistanceMode);
|
||||
var exitCode = Execute(packageSets, nupkgs, signRequestManifest, excludedRules.Values, logger, ignoreAssistanceMode);
|
||||
totalTimeStopWatch.Stop();
|
||||
logger.LogNormal("Total took {0}ms", totalTimeStopWatch.ElapsedMilliseconds);
|
||||
|
||||
|
@ -85,6 +92,7 @@ namespace NuGetPackageVerifier
|
|||
private static int Execute(
|
||||
IDictionary<string, PackageSet> packageSets,
|
||||
IEnumerable<FileInfo> nupkgs,
|
||||
SignRequestManifest signRequestManifest,
|
||||
List<string> excludedRuleNames,
|
||||
IPackageVerifierLogger logger,
|
||||
IgnoreAssistanceMode ignoreAssistanceMode)
|
||||
|
@ -94,7 +102,7 @@ namespace NuGetPackageVerifier
|
|||
typeof(IPackageVerifierRule).IsAssignableFrom(t) && !t.IsAbstract)
|
||||
.ToDictionary(
|
||||
t => t.Name,
|
||||
t =>
|
||||
t =>
|
||||
{
|
||||
var rule = (IPackageVerifierRule)Activator.CreateInstance(t);
|
||||
if (rule is CompositeRule compositeRule)
|
||||
|
@ -176,13 +184,17 @@ namespace NuGetPackageVerifier
|
|||
var package = packagePair.Key;
|
||||
logger.LogInfo("Analyzing {0} ({1})", package.Id, package.Version);
|
||||
|
||||
PackageSignRequest signRequest = null;
|
||||
signRequestManifest?.PackageSignRequests.TryGetValue(packagePair.Value.FullName, out signRequest);
|
||||
|
||||
List<PackageVerifierIssue> issues;
|
||||
using (var context = new PackageAnalysisContext
|
||||
{
|
||||
PackageFileInfo = packagePair.Value,
|
||||
Metadata = package,
|
||||
Logger = logger,
|
||||
Options = packageInfo.Value
|
||||
Options = packageInfo.Value,
|
||||
SignRequest = signRequest,
|
||||
})
|
||||
{
|
||||
issues = analyzer.AnalyzePackage(context).ToList();
|
||||
|
@ -223,9 +235,9 @@ namespace NuGetPackageVerifier
|
|||
// For unlisted packages we run the rules from 'Default' package set if present
|
||||
// or we run all rules (because we have no idea what exactly to run)
|
||||
var analyzer = new PackageAnalyzer();
|
||||
var unlistedPackageRules = defaultRuleSet ??
|
||||
var unlistedPackageRules = defaultRuleSet ??
|
||||
allRules.Values.SelectMany(f => f).Where(r => !excludedRuleNames.Contains(r.GetType().Name));
|
||||
|
||||
|
||||
foreach (var ruleInstance in unlistedPackageRules)
|
||||
{
|
||||
analyzer.Rules.Add(ruleInstance);
|
||||
|
@ -237,6 +249,9 @@ namespace NuGetPackageVerifier
|
|||
{
|
||||
logger.LogInfo("Analyzing {0} ({1})", unlistedPackage.Id, unlistedPackage.Version);
|
||||
|
||||
PackageSignRequest signRequest = null;
|
||||
signRequestManifest?.PackageSignRequests.TryGetValue(packages[unlistedPackage].FullName, out signRequest);
|
||||
|
||||
List<PackageVerifierIssue> issues;
|
||||
PackageVerifierOptions packageOptions = null;
|
||||
defaultPackageSet?.Packages?.TryGetValue(unlistedPackage.Id, out packageOptions);
|
||||
|
@ -246,6 +261,7 @@ namespace NuGetPackageVerifier
|
|||
PackageFileInfo = packages[unlistedPackage],
|
||||
Metadata = unlistedPackage,
|
||||
Logger = logger,
|
||||
SignRequest = signRequest,
|
||||
Options = packageOptions,
|
||||
})
|
||||
{
|
||||
|
@ -302,7 +318,7 @@ namespace NuGetPackageVerifier
|
|||
"SUMMARY: {0} error(s) and {1} warning(s) found",
|
||||
totalErrors, totalWarnings);
|
||||
|
||||
|
||||
|
||||
|
||||
return (totalErrors + totalWarnings > 0) ? ReturnErrorsOrWarnings : ReturnOk;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NuGetPackageVerifier.Logging;
|
||||
|
||||
namespace NuGetPackageVerifier.Rules
|
||||
{
|
||||
public class SignRequestListsAllSignableFiles : IPackageVerifierRule
|
||||
{
|
||||
private static readonly HashSet<string> SignableExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".dll",
|
||||
".exe",
|
||||
".ps1",
|
||||
".psd1",
|
||||
".psm1",
|
||||
".psc1",
|
||||
".ps1xml",
|
||||
};
|
||||
|
||||
public IEnumerable<PackageVerifierIssue> Validate(PackageAnalysisContext context)
|
||||
{
|
||||
if (context.SignRequest == null)
|
||||
{
|
||||
context.Logger.Log(LogLevel.Info, "Skipping signing rule request verification for " + context.PackageFileInfo.FullName);
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var file in context.PackageReader.GetFiles())
|
||||
{
|
||||
var ext = Path.GetExtension(file);
|
||||
if (!SignableExtensions.Contains(ext))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!context.SignRequest.FilesToSign.Contains(file) && !context.SignRequest.FilesExcludedFromSigning.Contains(file))
|
||||
{
|
||||
yield return PackageIssueFactory.SignRequestMissingPackageFile(context.Metadata.Id, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,12 +25,18 @@ repository root.
|
|||
<Packages Include="$(BuildDir)*.nupkg" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_VerifierSignRequestPath />
|
||||
<_VerifierSignRequestPath Condition=" '$(GenerateSignRequest)' == 'true' ">$(SignRequestOutputPath)</_VerifierSignRequestPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Warning Text="No nupkg found in '$(BuildDir)'." Condition="$(Packages -> Count()) == 0" />
|
||||
<Warning Text="Skipping nuget package verification because artifacts directory could not be found"
|
||||
Condition="!Exists('$(BuildDir)')" />
|
||||
|
||||
<VerifyPackages ArtifactDirectory="$(BuildDir)"
|
||||
RuleFile="$(NuGetVerifierRuleFile)"
|
||||
SignRequestManifest="$(_VerifierSignRequestPath)"
|
||||
Condition="Exists('$(BuildDir)')" />
|
||||
</Target>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
@ -27,6 +27,8 @@ namespace NuGetPackagerVerifier
|
|||
|
||||
public string[] ExcludedRules { get; set; }
|
||||
|
||||
public string SignRequestManifest { get; set; }
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
if (string.IsNullOrEmpty(RuleFile) || !File.Exists(RuleFile))
|
||||
|
@ -57,6 +59,18 @@ namespace NuGetPackagerVerifier
|
|||
ArtifactDirectory,
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(SignRequestManifest))
|
||||
{
|
||||
if (!File.Exists(SignRequestManifest))
|
||||
{
|
||||
Log.LogError($"SignRequestManifest file {SignRequestManifest} does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
arguments.Add("--sign-request");
|
||||
arguments.Add(SignRequestManifest);
|
||||
}
|
||||
|
||||
foreach (var rule in ExcludedRules ?? Enumerable.Empty<string>())
|
||||
{
|
||||
arguments.Add("--excluded-rule");
|
||||
|
@ -69,7 +83,8 @@ namespace NuGetPackagerVerifier
|
|||
Arguments = ArgumentEscaper.EscapeAndConcatenate(arguments),
|
||||
};
|
||||
|
||||
Log.LogMessage($"Executing '{psi.FileName} {psi.Arguments}'");
|
||||
Log.LogCommandLine($"Executing '{psi.FileName} {psi.Arguments}'");
|
||||
|
||||
var process = Process.Start(psi);
|
||||
process.WaitForExit();
|
||||
return process.ExitCode == 0;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\modules\NuGetPackageVerifier\console\NuGetPackageVerifier.Console.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using NuGetPackageVerifier.Rules;
|
||||
using NuGetPackageVerifier.Tests.Utilities;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace NuGetPackageVerifier.Tests
|
||||
{
|
||||
public class SignRequestListsAllSignableFilesRuleTests
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public SignRequestListsAllSignableFilesRuleTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItFailsWhenPackageContainsUnlistedFiles()
|
||||
{
|
||||
var signRequest = @"
|
||||
<SignRequest>
|
||||
<Container Path=""TestPackage.1.0.0.nupkg"" Type=""nupkg"">
|
||||
</Container>
|
||||
</SignRequest>";
|
||||
|
||||
var context = TestHelper.CreateAnalysisContext(_output,
|
||||
new[] { "lib/netstandard2.0/Test.dll", "tools/MyScript.psd1" },
|
||||
signRequest: signRequest);
|
||||
|
||||
var rule = new SignRequestListsAllSignableFiles();
|
||||
|
||||
var errors = rule.Validate(context);
|
||||
|
||||
Assert.NotEmpty(errors);
|
||||
|
||||
Assert.Contains(errors, e =>
|
||||
e.Instance.Equals("lib/netstandard2.0/Test.dll", StringComparison.Ordinal)
|
||||
&& e.IssueId.Equals("FILE_MISSING_FROM_SIGN_REQUEST", StringComparison.Ordinal));
|
||||
|
||||
Assert.Contains(errors, e =>
|
||||
e.Instance.Equals("tools/MyScript.psd1", StringComparison.Ordinal)
|
||||
&& e.IssueId.Equals("FILE_MISSING_FROM_SIGN_REQUEST", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotFailWhenSignRequestIncludesAllFiles()
|
||||
{
|
||||
var signRequest = @"
|
||||
<SignRequest>
|
||||
<Container Path=""TestPackage.1.0.0.nupkg"" Type=""nupkg"">
|
||||
<File Path=""lib/netstandard2.0/Test.dll"" />
|
||||
<File Path=""tools/MyScript.psd1"" />
|
||||
</Container>
|
||||
</SignRequest>";
|
||||
|
||||
var context = TestHelper.CreateAnalysisContext(_output,
|
||||
new[] { "lib/netstandard2.0/Test.dll", "tools/MyScript.psd1" },
|
||||
signRequest: signRequest);
|
||||
|
||||
var rule = new SignRequestListsAllSignableFiles();
|
||||
|
||||
var errors = rule.Validate(context);
|
||||
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoesNotFailWhenSignRequestListsAllFiles()
|
||||
{
|
||||
var signRequest = @"
|
||||
<SignRequest>
|
||||
<Container Path=""TestPackage.1.0.0.nupkg"" Type=""nupkg"">
|
||||
<ExcludedFile Path=""lib/netstandard2.0/Test.dll"" />
|
||||
<ExcludedFile Path=""tools/MyScript.psd1"" />
|
||||
</Container>
|
||||
</SignRequest>";
|
||||
|
||||
var context = TestHelper.CreateAnalysisContext(_output,
|
||||
new[] { "lib/netstandard2.0/Test.dll", "tools/MyScript.psd1" },
|
||||
signRequest: signRequest);
|
||||
|
||||
var rule = new SignRequestListsAllSignableFiles();
|
||||
|
||||
var errors = rule.Validate(context);
|
||||
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using NuGetPackageVerifier.Logging;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace NuGetPackageVerifier.Tests.Utilities
|
||||
{
|
||||
internal class TestLogger : IPackageVerifierLogger
|
||||
{
|
||||
private ITestOutputHelper _output;
|
||||
|
||||
public TestLogger(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
public void Log(LogLevel logLevel, string message)
|
||||
{
|
||||
_output.WriteLine($"{logLevel}: {message}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using NuGet.Packaging;
|
||||
using NuGet.Versioning;
|
||||
using NuGetPackageVerifier.Manifests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace NuGetPackageVerifier.Tests.Utilities
|
||||
{
|
||||
public class TestHelper
|
||||
{
|
||||
public static PackageAnalysisContext CreateAnalysisContext(ITestOutputHelper output, string[] emptyFiles, string version = "1.0.0", string signRequest = null)
|
||||
{
|
||||
const string packageId = "TestPackage";
|
||||
var basePath = Path.Combine(AppContext.BaseDirectory, Path.GetRandomFileName());
|
||||
var nupkgFileName = $"{packageId}.{version}.nupkg";
|
||||
var nupkgPath = Path.Combine(basePath, nupkgFileName);
|
||||
|
||||
Directory.CreateDirectory(basePath);
|
||||
|
||||
|
||||
var builder = new PackageBuilder();
|
||||
|
||||
builder.Populate(new ManifestMetadata
|
||||
{
|
||||
Id = packageId,
|
||||
Version = new NuGetVersion(version),
|
||||
Authors = new[] { "Test" },
|
||||
Description = "Test",
|
||||
});
|
||||
|
||||
using (var nupkg = File.Create(nupkgPath))
|
||||
{
|
||||
|
||||
foreach (var dest in emptyFiles)
|
||||
{
|
||||
var fileName = Path.GetFileName(dest);
|
||||
File.WriteAllText(Path.Combine(basePath, fileName), "");
|
||||
builder.AddFiles(basePath, fileName, dest);
|
||||
}
|
||||
|
||||
builder.Save(nupkg);
|
||||
}
|
||||
|
||||
PackageSignRequest packageSignRequest = null;
|
||||
|
||||
if (signRequest != null)
|
||||
{
|
||||
var reader = new StringReader(signRequest);
|
||||
var signManifest = SignRequestManifest.Parse(reader, basePath);
|
||||
packageSignRequest = signManifest.PackageSignRequests[nupkgPath];
|
||||
}
|
||||
|
||||
var context = new TempPackageAnalysisContext(basePath)
|
||||
{
|
||||
Logger = new TestLogger(output),
|
||||
PackageFileInfo = new FileInfo(nupkgPath),
|
||||
SignRequest = packageSignRequest,
|
||||
Metadata = builder,
|
||||
};
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private class TempPackageAnalysisContext : PackageAnalysisContext
|
||||
{
|
||||
private string _tempPath;
|
||||
|
||||
public TempPackageAnalysisContext(string tempPath)
|
||||
{
|
||||
this._tempPath = tempPath;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
if (Directory.Exists(_tempPath))
|
||||
{
|
||||
Directory.Delete(_tempPath, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче