mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Add support for detecting javascript assets in .NET projects using libman (#3825)
* add support for .NET libman files Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix when no libman detected Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add libman.json docs Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
43a85dfb85
commit
5c6c6aa123
@ -1008,6 +1008,18 @@ func TestCataloger(t *testing.T) {
|
|||||||
},
|
},
|
||||||
assertion: assertAccurateNetRuntimePackage,
|
assertion: assertAccurateNetRuntimePackage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "libman support",
|
||||||
|
fixture: "image-net6-asp-libman",
|
||||||
|
cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()),
|
||||||
|
expectedPkgs: []string{
|
||||||
|
"LibManSample @ 1.0.0 (/app/LibManSample.deps.json)",
|
||||||
|
"jquery @ 3.3.1 (/app/libman.json)",
|
||||||
|
},
|
||||||
|
expectedRels: []string{
|
||||||
|
"jquery @ 3.3.1 (/app/libman.json) [dependency-of] LibManSample @ 1.0.0 (/app/LibManSample.deps.json)",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
@ -1134,6 +1146,20 @@ func TestDotnetDepsCataloger_regressions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "libman support",
|
||||||
|
fixture: "image-net6-asp-libman",
|
||||||
|
cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()),
|
||||||
|
assertion: assertPackages(
|
||||||
|
[]string{
|
||||||
|
"jquery", // a javascript package, not the nuget package
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"vendor", // this is the string reference for a filesystem provider
|
||||||
|
"lodash", // this is from a filesystem provider, which is not supported
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|||||||
@ -78,6 +78,9 @@ func (c depsBinaryCataloger) Catalog(_ context.Context, resolver file.Resolver)
|
|||||||
var runtimePkgs []*pkg.Package
|
var runtimePkgs []*pkg.Package
|
||||||
for i := range pkgs {
|
for i := range pkgs {
|
||||||
p := &pkgs[i]
|
p := &pkgs[i]
|
||||||
|
if p.Type != pkg.DotnetPkg {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if isRuntime(p.Name) {
|
if isRuntime(p.Name) {
|
||||||
existingRuntimeVersions.Add(p.Version)
|
existingRuntimeVersions.Add(p.Version)
|
||||||
runtimePkgs = append(runtimePkgs, p)
|
runtimePkgs = append(runtimePkgs, p)
|
||||||
@ -290,8 +293,22 @@ func packagesFromLogicalDepsJSON(doc logicalDepsJSON, config CatalogerConfig) (*
|
|||||||
pkgMap[nameVersion] = *dotnetPkg
|
pkgMap[nameVersion] = *dotnetPkg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rels := relationshipsFromLogicalDepsJSON(doc, pkgMap, skippedDepPkgs)
|
||||||
|
|
||||||
return rootPkg, pkgs, relationshipsFromLogicalDepsJSON(doc, pkgMap, skippedDepPkgs)
|
// ensure that any libman packages are associated with the all root packages
|
||||||
|
for _, libmanPkg := range doc.LibmanPackages {
|
||||||
|
pkgs = append(pkgs, libmanPkg)
|
||||||
|
if rootPkg == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rels = append(rels, artifact.Relationship{
|
||||||
|
From: libmanPkg,
|
||||||
|
To: *rootPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootPkg, pkgs, rels
|
||||||
}
|
}
|
||||||
|
|
||||||
// relationshipsFromLogicalDepsJSON creates relationships from a logicalDepsJSON document for only the given syft packages.
|
// relationshipsFromLogicalDepsJSON creates relationships from a logicalDepsJSON document for only the given syft packages.
|
||||||
@ -389,7 +406,13 @@ func findDepsJSON(resolver file.Resolver) ([]logicalDepsJSON, error, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
depsJSONs = append(depsJSONs, getLogicalDepsJSON(*dj))
|
libman, err := findLibmanJSON(resolver, loc)
|
||||||
|
if err != nil {
|
||||||
|
unknownErr = unknown.Append(unknownErr, loc, err)
|
||||||
|
libman = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
depsJSONs = append(depsJSONs, getLogicalDepsJSON(*dj, libman))
|
||||||
}
|
}
|
||||||
|
|
||||||
return depsJSONs, unknownErr, nil
|
return depsJSONs, unknownErr, nil
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/scylladb/go-set/strset"
|
"github.com/scylladb/go-set/strset"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type depsJSON struct {
|
type depsJSON struct {
|
||||||
@ -147,6 +148,7 @@ type logicalDepsJSON struct {
|
|||||||
PackagesByNameVersion map[string]logicalDepsJSONPackage
|
PackagesByNameVersion map[string]logicalDepsJSONPackage
|
||||||
PackageNameVersions *strset.Set
|
PackageNameVersions *strset.Set
|
||||||
BundlingDetected bool
|
BundlingDetected bool
|
||||||
|
LibmanPackages []pkg.Package
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l logicalDepsJSON) RootPackage() (logicalDepsJSONPackage, bool) {
|
func (l logicalDepsJSON) RootPackage() (logicalDepsJSONPackage, bool) {
|
||||||
@ -196,7 +198,7 @@ var knownBundlers = strset.New(
|
|||||||
"Fody", // IL weaving framework
|
"Fody", // IL weaving framework
|
||||||
)
|
)
|
||||||
|
|
||||||
func getLogicalDepsJSON(deps depsJSON) logicalDepsJSON {
|
func getLogicalDepsJSON(deps depsJSON, lm *libmanJSON) logicalDepsJSON {
|
||||||
packageMap := make(map[string]*logicalDepsJSONPackage)
|
packageMap := make(map[string]*logicalDepsJSONPackage)
|
||||||
nameVersions := strset.New()
|
nameVersions := strset.New()
|
||||||
|
|
||||||
@ -244,6 +246,7 @@ func getLogicalDepsJSON(deps depsJSON) logicalDepsJSON {
|
|||||||
PackagesByNameVersion: packages,
|
PackagesByNameVersion: packages,
|
||||||
PackageNameVersions: nameVersions,
|
PackageNameVersions: nameVersions,
|
||||||
BundlingDetected: bundlingDetected,
|
BundlingDetected: bundlingDetected,
|
||||||
|
LibmanPackages: lm.packages(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
syft/pkg/cataloger/dotnet/libman_json.go
Normal file
109
syft/pkg/cataloger/dotnet/libman_json.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package dotnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/packageurl-go"
|
||||||
|
"github.com/anchore/syft/internal"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// libmanJSON represents the libman.json file format in ASP.NET projects for describing javascript assets to be downloaded and bundled
|
||||||
|
// see https://github.com/aspnet/LibraryManager/wiki/libman.json-reference
|
||||||
|
type libmanJSON struct {
|
||||||
|
Location file.Location `json:"-"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
DefaultProvider string `json:"defaultProvider"`
|
||||||
|
Libraries []struct {
|
||||||
|
Library string `json:"library"`
|
||||||
|
Files []string `json:"files"`
|
||||||
|
Destination string `json:"destination"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
} `json:"libraries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *libmanJSON) packages() []pkg.Package {
|
||||||
|
if l == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkgs []pkg.Package
|
||||||
|
for _, lib := range l.Libraries {
|
||||||
|
if lib.Provider == "filesystem" {
|
||||||
|
// there is no name and version with filesystem providers
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields := strings.Split(lib.Library, "@")
|
||||||
|
if len(fields) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fields[0]
|
||||||
|
version := fields[1]
|
||||||
|
|
||||||
|
p := pkg.Package{
|
||||||
|
Name: name,
|
||||||
|
Version: version,
|
||||||
|
Locations: file.NewLocationSet(l.Location),
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
PURL: packageurl.NewPackageURL(
|
||||||
|
packageurl.TypeNPM,
|
||||||
|
"",
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
).ToString(),
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
}
|
||||||
|
|
||||||
|
p.SetID()
|
||||||
|
pkgs = append(pkgs, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLibmanJSON(reader file.LocationReadCloser) (*libmanJSON, error) {
|
||||||
|
var doc libmanJSON
|
||||||
|
dec := json.NewDecoder(reader)
|
||||||
|
if err := dec.Decode(&doc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range doc.Libraries {
|
||||||
|
l := &doc.Libraries[i]
|
||||||
|
if l.Provider == "" {
|
||||||
|
l.Provider = doc.DefaultProvider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.Location = reader.Location
|
||||||
|
|
||||||
|
return &doc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findLibmanJSON(resolver file.Resolver, depsJSON file.Location) (*libmanJSON, error) {
|
||||||
|
parent := path.Dir(depsJSON.RealPath)
|
||||||
|
loc := resolver.RelativeFileByPath(depsJSON, path.Join(parent, "libman.json"))
|
||||||
|
if loc == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := resolver.FileContentsByLocation(*loc)
|
||||||
|
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
internal.CloseAndLogError(reader, loc.RealPath)
|
||||||
|
|
||||||
|
lj, err := newLibmanJSON(file.NewLocationReadCloser(*loc, reader))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lj, nil
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
.gitignore
|
||||||
|
Dockerfile
|
||||||
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/.gitignore
vendored
Normal file
2
syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/app
|
||||||
|
/extract.sh
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
|
ARG RUNTIME=win-x64
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
WORKDIR /src
|
||||||
|
RUN dotnet restore -r $RUNTIME
|
||||||
|
RUN dotnet publish -c Release --no-restore -o /app
|
||||||
|
|
||||||
|
FROM busybox:latest
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app .
|
||||||
|
|
||||||
|
|
||||||
1
syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/.gitignore
vendored
Normal file
1
syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
wwwroot/lib/
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- A PackageReference to Microsoft.AspNetCore.App is not necessary when targeting .NET Core 3.0 or higher. If Microsoft.NET.Sdk.Web is used, the shared framework will be referenced automatically (or override with FrameworkReference) -->
|
||||||
|
<!-- <PackageReference Include="Microsoft.AspNetCore.App" /> -->
|
||||||
|
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace LibManSample
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
CreateWebHostBuilder(args).Build().Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||||
|
WebHost.CreateDefaultBuilder(args)
|
||||||
|
.UseStartup<Startup>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace LibManSample
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(async (context) =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("Hello World!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"defaultProvider": "cdnjs",
|
||||||
|
"libraries": [
|
||||||
|
{
|
||||||
|
"library": "jquery@3.3.1",
|
||||||
|
"files": [
|
||||||
|
"jquery.min.js",
|
||||||
|
"jquery.js",
|
||||||
|
"jquery.min.map"
|
||||||
|
],
|
||||||
|
"destination": "wwwroot/lib/jquery/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"provider": "filesystem",
|
||||||
|
"library": "vendor",
|
||||||
|
"files": [
|
||||||
|
"lodash.js",
|
||||||
|
"lodash.min.js"
|
||||||
|
],
|
||||||
|
"destination": "wwwroot/lib/lodash/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/vendor/lodash.js
vendored
Normal file
1
syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/vendor/lodash.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
// jk! nothing to see here!
|
||||||
1
syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/vendor/lodash.min.js
vendored
Normal file
1
syft/pkg/cataloger/dotnet/test-fixtures/image-net6-asp-libman/src/vendor/lodash.min.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
// still nothing here...
|
||||||
Loading…
x
Reference in New Issue
Block a user