From 282c3248b95369fbbe6d0dadbddc00e5a8bc1f2a Mon Sep 17 00:00:00 2001 From: Avishay Balter Date: Fri, 7 Jun 2024 07:58:03 +0000 Subject: [PATCH] Nuget lock file support Signed-off-by: balteraivshay --- checks/raw/pinned_dependencies_test.go | 4 +- checks/raw/shell_download_validate.go | 113 +++++++++++-- checks/raw/shell_download_validate_test.go | 149 +++++++++++++++++- .../github-workflow-pkg-managers.yaml | 10 ++ checks/raw/testdata/Dockerfile-pkg-managers | 6 + checks/raw/testdata/script-pkg-managers | 4 +- 6 files changed, 272 insertions(+), 14 deletions(-) diff --git a/checks/raw/pinned_dependencies_test.go b/checks/raw/pinned_dependencies_test.go index c86ed60a6db3..ec95026546bc 100644 --- a/checks/raw/pinned_dependencies_test.go +++ b/checks/raw/pinned_dependencies_test.go @@ -281,7 +281,7 @@ func TestGithubWorkflowPkgManagerPinning(t *testing.T) { { name: "npm packages without verification", filename: "./testdata/.github/workflows/github-workflow-pkg-managers.yaml", - unpinned: 49, + unpinned: 52, }, { name: "Can't identify OS but doesn't crash", @@ -1409,7 +1409,7 @@ func TestDockerfileScriptDownload(t *testing.T) { { name: "pkg managers", filename: "./testdata/Dockerfile-pkg-managers", - unpinned: 60, + unpinned: 63, }, { name: "download with some python", diff --git a/checks/raw/shell_download_validate.go b/checks/raw/shell_download_validate.go index ee962f3ea4f1..8f59aba04e54 100644 --- a/checks/raw/shell_download_validate.go +++ b/checks/raw/shell_download_validate.go @@ -764,19 +764,19 @@ func isUnpinnedNugetCliInstall(cmd []string) bool { return unpinnedDependency } -func isDotNetCliInstall(cmd []string) bool { +func isDotNetCliAdd(cmd []string) bool { // Search for command of type dotnet add package if len(cmd) < 4 { return false } - // Search for dotnet add package + // Search for dotnet add [PROJECT] package // where package command can be either the second or the third word return (isBinaryName("dotnet", cmd[0]) || isBinaryName("dotnet.exe", cmd[0])) && strings.EqualFold(cmd[1], "add") && (strings.EqualFold(cmd[2], "package") || strings.EqualFold(cmd[3], "package")) } -func isUnpinnedDotNetCliInstall(cmd []string) bool { +func isUnpinnedDotNetCliAdd(cmd []string) bool { unpinnedDependency := true for i := 3; i < len(cmd); i++ { // look for version flag @@ -789,12 +789,12 @@ func isUnpinnedDotNetCliInstall(cmd []string) bool { return unpinnedDependency } -func isNugetDownload(cmd []string) bool { - return isDotNetCliInstall(cmd) || isNugetCliInstall(cmd) +func isNuget(cmd []string) bool { + return isDotNetCliAdd(cmd) || isNugetCliInstall(cmd) || isDotNetCliRestore(cmd) || isNugetCliRestore(cmd) || isMsBuildRestore(cmd) } -func isNugetUnpinnedDownload(cmd []string) bool { - if isDotNetCliInstall(cmd) && isUnpinnedDotNetCliInstall(cmd) { +func isNugetUnpinned(cmd []string) bool { + if isDotNetCliAdd(cmd) && isUnpinnedDotNetCliAdd(cmd) { return true } @@ -802,9 +802,101 @@ func isNugetUnpinnedDownload(cmd []string) bool { return true } + if isDotNetCliRestore(cmd) && isUnpinnedDotNetCliRestore(cmd) { + return true + } + + if isNugetCliRestore(cmd) && isUnpinnedNugetCliRestore(cmd) { + return true + } + + if isMsBuildRestore(cmd) && isUnpinnedMsBuildCliRestore(cmd) { + return true + } + + return false +} + +func isNugetCliRestore(cmd []string) bool { + // Search for command of type nuget restore + if len(cmd) < 2 { + return false + } + // Search for nuget restore + return (isBinaryName("nuget", cmd[0]) || isBinaryName("nuget.exe", cmd[0])) && + strings.EqualFold(cmd[1], "restore") +} + +func isDotNetCliRestore(cmd []string) bool { + // Search for command of type dotnet restore + if len(cmd) < 2 { + return false + } + // Search for dotnet restore + return (isBinaryName("dotnet", cmd[0]) || isBinaryName("dotnet.exe", cmd[0])) && + strings.EqualFold(cmd[1], "restore") +} + +func isMsBuildRestore(cmd []string) bool { + // Search for command of type msbuild /t:restore + if len(cmd) < 2 { + return false + } + // Search for msbuild /t:restore + if isBinaryName("msbuild", cmd[0]) || isBinaryName("msbuild.exe", cmd[0]) { + for i := 1; i < len(cmd); i++ { + // look for /t:restore flag + if strings.EqualFold(cmd[i], "/t:restore") { + return true + } + } + } return false } +func isNugetRestore(cmd []string) bool { + return isDotNetCliRestore(cmd) || isNugetCliRestore(cmd) || isMsBuildRestore(cmd) +} + +func isUnpinnedNugetCliRestore(cmd []string) bool { + unpinnedDependency := true + for i := 2; i < len(cmd); i++ { + // look for LockedMode flag + // https://learn.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-restore + if strings.EqualFold(cmd[i], "-LockedMode") { + unpinnedDependency = false + break + } + } + return unpinnedDependency +} + +func isUnpinnedDotNetCliRestore(cmd []string) bool { + unpinnedDependency := true + for i := 2; i < len(cmd); i++ { + // look for locked-mode flag + // https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-restore + if strings.EqualFold(cmd[i], "--locked-mode") { + unpinnedDependency = false + break + } + } + return unpinnedDependency +} + +func isUnpinnedMsBuildCliRestore(cmd []string) bool { + unpinnedDependency := true + for i := 2; i < len(cmd); i++ { + // look for /p:RestoreLockedMode=true + // https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-restore + if strings.EqualFold(cmd[i], "/p:RestoreLockedMode=true") { + unpinnedDependency = false + break + } + } + return unpinnedDependency +} + func collectUnpinnedPackageManagerDownload(startLine, endLine uint, node syntax.Node, cmd, pathfn string, r *checker.PinningDependenciesData, ) { @@ -900,8 +992,8 @@ func collectUnpinnedPackageManagerDownload(startLine, endLine uint, node syntax. return } - // Nuget install. - if isNugetDownload(c) { + // Nuget install and restore + if isNuget(c) { r.Dependencies = append(r.Dependencies, checker.Dependency{ Location: &checker.File{ @@ -911,13 +1003,14 @@ func collectUnpinnedPackageManagerDownload(startLine, endLine uint, node syntax. EndOffset: endLine, Snippet: cmd, }, - Pinned: asBoolPointer(!isNugetUnpinnedDownload(c)), + Pinned: asBoolPointer(!isNugetUnpinned(c)), Type: checker.DependencyUseTypeNugetCommand, }, ) return } + // TODO(laurent): add other package managers. } diff --git a/checks/raw/shell_download_validate_test.go b/checks/raw/shell_download_validate_test.go index 8b644c40914c..7a83a5ee3277 100644 --- a/checks/raw/shell_download_validate_test.go +++ b/checks/raw/shell_download_validate_test.go @@ -128,11 +128,95 @@ func Test_isDotNetUnpinnedDownload(t *testing.T) { }, want: true, }, + { + name: "nuget.exe install", + args: args{ + cmd: []string{"nuget.exe", "install", "Newtonsoft.Json"}, + }, + want: true, + }, { name: "nuget restore", args: args{ cmd: []string{"nuget", "restore"}, }, + want: true, + }, + { + name: "nuget.exe restore", + args: args{ + cmd: []string{"nuget.exe", "restore"}, + }, + want: true, + }, + { + name: "msbuild restore", + args: args{ + cmd: []string{"msbuild", "/t:restore"}, + }, + want: true, + }, + { + name: "msbuild.exe restore", + args: args{ + cmd: []string{"msbuild.exe", "/t:restore"}, + }, + want: true, + }, + { + name: "nuget restore locked", + args: args{ + cmd: []string{"nuget", "restore", "-LockedMode"}, + }, + want: false, + }, + { + name: "nuget.exe restore locked", + args: args{ + cmd: []string{"nuget.exe", "restore", "-LockedMode"}, + }, + want: false, + }, + { + name: "msbuild restore locked", + args: args{ + cmd: []string{"msbuild", "/t:restore", "/p:RestoreLockedMode=true"}, + }, + want: false, + }, + { + name: "msbuild.exe restore locked", + args: args{ + cmd: []string{"msbuild.exe", "/t:restore", "/p:RestoreLockedMode=true"}, + }, + want: false, + }, + { + name: "dotnet restore", + args: args{ + cmd: []string{"dotnet", "restore"}, + }, + want: true, + }, + { + name: "dotnet.exe restore", + args: args{ + cmd: []string{"dotnet.exe", "restore"}, + }, + want: true, + }, + { + name: "dotnet restore locked", + args: args{ + cmd: []string{"dotnet", "restore", "--locked-mode"}, + }, + want: false, + }, + { + name: "dotnet.exe restore locked", + args: args{ + cmd: []string{"dotnet.exe", "restore", "--locked-mode"}, + }, want: false, }, { @@ -142,6 +226,13 @@ func Test_isDotNetUnpinnedDownload(t *testing.T) { }, want: false, }, + { + name: "nuget.exe install with -Version", + args: args{ + cmd: []string{"nuget.exe", "install", "Newtonsoft.Json", "-Version", "2"}, + }, + want: false, + }, { name: "nuget install with packages.config", args: args{ @@ -149,6 +240,13 @@ func Test_isDotNetUnpinnedDownload(t *testing.T) { }, want: false, }, + { + name: "nuget.exe install with packages.config", + args: args{ + cmd: []string{"nuget.exe", "install", "config\\packages.config"}, + }, + want: false, + }, { name: "dotnet add", args: args{ @@ -156,6 +254,13 @@ func Test_isDotNetUnpinnedDownload(t *testing.T) { }, want: true, }, + { + name: "dotnet.exe add", + args: args{ + cmd: []string{"dotnet.exe", "add", "package", "Newtonsoft.Json"}, + }, + want: true, + }, { name: "dotnet add to project", args: args{ @@ -163,6 +268,13 @@ func Test_isDotNetUnpinnedDownload(t *testing.T) { }, want: true, }, + { + name: "dotnet.exe add to project", + args: args{ + cmd: []string{"dotnet.exe", "add", "project1", "package", "Newtonsoft.Json"}, + }, + want: true, + }, { name: "dotnet add reference to project", args: args{ @@ -170,6 +282,13 @@ func Test_isDotNetUnpinnedDownload(t *testing.T) { }, want: false, }, + { + name: "dotnet.exe add reference to project", + args: args{ + cmd: []string{"dotnet.exe", "add", "project1", "reference", "OtherProject"}, + }, + want: false, + }, { name: "dotnet build", args: args{ @@ -184,6 +303,13 @@ func Test_isDotNetUnpinnedDownload(t *testing.T) { }, want: false, }, + { + name: "dotnet.exe add with -v", + args: args{ + cmd: []string{"dotnet.exe", "add", "package", "Newtonsoft.Json", "-v", "2.0"}, + }, + want: false, + }, { name: "dotnet add to project with -v", args: args{ @@ -191,6 +317,13 @@ func Test_isDotNetUnpinnedDownload(t *testing.T) { }, want: false, }, + { + name: "dotnet.exe add to project with -v", + args: args{ + cmd: []string{"dotnet.exe", "add", "project1", "package", "Newtonsoft.Json", "-v", "2.0"}, + }, + want: false, + }, { name: "dotnet add reference to project with -v", args: args{ @@ -198,6 +331,13 @@ func Test_isDotNetUnpinnedDownload(t *testing.T) { }, want: false, }, + { + name: "dotnet.exe add reference to project with -v", + args: args{ + cmd: []string{"dotnet.exe", "add", "project1", "reference", "Newtonsoft.Json", "-v", "2.0"}, + }, + want: false, + }, { name: "dotnet add with --version", args: args{ @@ -205,12 +345,19 @@ func Test_isDotNetUnpinnedDownload(t *testing.T) { }, want: false, }, + { + name: "dotnet.exe add with --version", + args: args{ + cmd: []string{"dotnet.exe", "add", "package", "Newtonsoft.Json", "--version", "2.0"}, + }, + want: false, + }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - if got := isNugetUnpinnedDownload(tt.args.cmd); got != tt.want { + if got := isNugetUnpinned(tt.args.cmd); got != tt.want { t.Errorf("isNugetUnpinnedDownload() = %v, want %v", got, tt.want) } }) diff --git a/checks/raw/testdata/.github/workflows/github-workflow-pkg-managers.yaml b/checks/raw/testdata/.github/workflows/github-workflow-pkg-managers.yaml index 7b28e3ec97c0..f4f300ff9e9e 100644 --- a/checks/raw/testdata/.github/workflows/github-workflow-pkg-managers.yaml +++ b/checks/raw/testdata/.github/workflows/github-workflow-pkg-managers.yaml @@ -166,6 +166,16 @@ jobs: run: nuget install 'some-package' - name: run: nuget restore + - name: + run: nuget restore -LockedMode + - name: + run: dotnet restore + - name: + run: dotnet restore --locked-mode + - name: + run: msbuild /t:restore /p:RestoreLockedMode=true + - name: + run: msbuild /t:restore - name: run: dotnet add package 'some-package' - name: diff --git a/checks/raw/testdata/Dockerfile-pkg-managers b/checks/raw/testdata/Dockerfile-pkg-managers index 0208433c8d5e..72b031027bba 100644 --- a/checks/raw/testdata/Dockerfile-pkg-managers +++ b/checks/raw/testdata/Dockerfile-pkg-managers @@ -127,6 +127,12 @@ RUN choco install --require-checksums 'some-package' RUN nuget install some-package RUN nuget restore +RUN nuget restore -LockedMode +RUN dotnet restore +RUN dotnet restore --locked-mode +RUN msbuild.exe /t:restore /p:RestoreLockedMode=true +RUN msbuild.exe /t:restore +RUN nuget restore -LockedMode RUN nuget install some-package -Version 1.2.3 RUN nuget install packages.config RUN dotnet add package some-package diff --git a/checks/raw/testdata/script-pkg-managers b/checks/raw/testdata/script-pkg-managers index e13e6f2b646c..4041020c3ac8 100644 --- a/checks/raw/testdata/script-pkg-managers +++ b/checks/raw/testdata/script-pkg-managers @@ -125,7 +125,9 @@ choco install --requirechecksums 'some-package' choco install --require-checksums 'some-package' nuget install some-package -nuget restore +nuget restore -LockedMode +dotnet restore --locked-mode +msbuild.exe /t:restore /p:RestoreLockedMode=true nuget install some-package -Version 1.2.3 nuget install packages.config dotnet add package some-package