Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/dev' into curation_go_support
Browse files Browse the repository at this point in the history
  • Loading branch information
asafambar committed May 23, 2024
2 parents 9b8c055 + bde59ff commit dc48085
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 19 deletions.
9 changes: 7 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ concurrency:
jobs:
unit-test:
name: ${{ matrix.os }}, node ${{ matrix.node }}, python ${{ matrix.python }}
runs-on: ${{ matrix.os }}-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu, windows, macos ]
# macos-latest = arm64, macos-14-large = amd64
os: [ ubuntu-latest, windows-latest, macos-latest, macos-14-large ]
node: [ 14, 16, 16.9 ]
include:
- node: "14"
Expand All @@ -27,6 +28,10 @@ jobs:
python: "3.9"
- node: "16.9"
python: "3.x"
exclude:
# MacOS with ARM does not support node 14
- os: macos-latest
node: 14

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion build/maven.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const (
classworldsConfFileName = "classworlds.conf"
PropertiesTempFolderName = "properties"
MavenExtractorRemotePath = "org/jfrog/buildinfo/build-info-extractor-maven3/%s"
MavenExtractorDependencyVersion = "2.41.14"
MavenExtractorDependencyVersion = "2.41.16"

ClassworldsConf = `main is org.apache.maven.cli.MavenCli from plexus.core
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/BurntSushi/toml v1.3.2
github.com/CycloneDX/cyclonedx-go v0.8.0
github.com/buger/jsonparser v1.1.1
github.com/jfrog/gofrog v1.6.3
github.com/jfrog/gofrog v1.7.1
github.com/minio/sha256-simd v1.0.1
github.com/stretchr/testify v1.9.0
github.com/urfave/cli/v2 v2.27.1
Expand All @@ -23,6 +23,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jfrog/gofrog v1.6.3 h1:F7He0+75HcgCe6SGTSHLFCBDxiE2Ja0tekvvcktW6wc=
github.com/jfrog/gofrog v1.6.3/go.mod h1:SZ1EPJUruxrVGndOzHd+LTiwWYKMlHqhKD+eu+v5Hqg=
github.com/jfrog/gofrog v1.7.1 h1:ME1Meg4hukAT/7X6HUQCVSe4DNjMZACCP8aCY37EW/w=
github.com/jfrog/gofrog v1.7.1/go.mod h1:X7bjfWoQDN0Z4FQGbE91j3gbPP7Urwzm4Z8tkvrlbRI=
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
Expand All @@ -37,6 +37,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc=
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
2 changes: 1 addition & 1 deletion utils/fileutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ func GetFileContentAndInfo(filePath string) (fileContent []byte, fileInfo os.Fil
func CreateTempDir() (string, error) {
tempDirBase := os.TempDir()
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
return os.MkdirTemp(tempDirBase, tempDirPrefix+timestamp+"-")
return os.MkdirTemp(tempDirBase, tempDirPrefix+timestamp+"-*")
}

func RemoveTempDir(dirPath string) error {
Expand Down
16 changes: 16 additions & 0 deletions utils/fileutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,19 @@ func TestReadNLines(t *testing.T) {
assert.True(t, strings.HasPrefix(lines[1], "781"))
assert.True(t, strings.HasSuffix(lines[1], ":true}}}"))
}

func TestCreateTempDir(t *testing.T) {
tempDir, err := CreateTempDir()
assert.NoError(t, err)

_, err = os.Stat(tempDir)
assert.NotErrorIs(t, err, os.ErrNotExist)

defer func() {
// Check that a timestamp can be extracted from the temp dir name
_, err = extractTimestamp(tempDir)
assert.NoError(t, err)

assert.NoError(t, os.RemoveAll(tempDir))
}()
}
11 changes: 1 addition & 10 deletions utils/goutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ import (
gofrogcmd "github.com/jfrog/gofrog/io"
)

// #nosec G101 -- False positive - no hardcoded credentials.
const credentialsInUrlRegexp = `(http|https|git)://.+@`

// Minimum go version, which its output does not require masking passwords in URLs.
const minGoVersionForMasking = "go1.13"

Expand Down Expand Up @@ -244,7 +241,7 @@ func getGoVersion() (string, error) {
func prepareGlobalRegExp() error {
var err error
if protocolRegExp == nil {
protocolRegExp, err = initRegExp(credentialsInUrlRegexp, removeCredentials)
protocolRegExp, err = initRegExp(CredentialsInUrlRegexp, RemoveCredentials)
if err != nil {
return err
}
Expand All @@ -267,12 +264,6 @@ func initRegExp(regex string, execFunc func(pattern *gofrogcmd.CmdOutputPattern)
return outputPattern, nil
}

// Remove the credentials information from the line.
func removeCredentials(pattern *gofrogcmd.CmdOutputPattern) (string, error) {
splitResult := strings.Split(pattern.MatchedResults[0], "//")
return strings.Replace(pattern.Line, pattern.MatchedResults[0], splitResult[0]+"//", 1), nil
}

// GetCachePath returns the location of downloads dir inside the GOMODCACHE
func GetCachePath() (string, error) {
goModCachePath, err := GetGoModCachePath()
Expand Down
15 changes: 15 additions & 0 deletions utils/masking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package utils

import (
gofrogcmd "github.com/jfrog/gofrog/io"
"strings"
)

// #nosec G101 -- False positive - no hardcoded credentials.
const CredentialsInUrlRegexp = `(?:http|https|git)://.+@`

// Remove the credentials information from the line.
func RemoveCredentials(pattern *gofrogcmd.CmdOutputPattern) (string, error) {
splitResult := strings.Split(pattern.MatchedResults[0], "//")
return strings.ReplaceAll(pattern.Line, pattern.MatchedResults[0], splitResult[0]+"//***@"), nil
}
66 changes: 64 additions & 2 deletions utils/pythonutils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pythonutils
import (
"errors"
"fmt"
"net/url"
"path/filepath"
"regexp"
"strings"
Expand All @@ -27,6 +28,11 @@ const (

type PythonTool string

var (
credentialsInUrlRegexp = regexp.MustCompile(utils.CredentialsInUrlRegexp)
catchAllRegexp = regexp.MustCompile(".*")
)

// Parse pythonDependencyPackage list to dependencies map. (mapping dependency to his child deps)
// Also returns a list of project's root dependencies
func parseDependenciesToGraph(packages []pythonDependencyPackage) (map[string][]string, []string, error) {
Expand Down Expand Up @@ -176,7 +182,7 @@ func getMultilineSplitCaptureOutputPattern(startCollectingPattern, captureGroup,
// Create a parser for multi line pattern matches.
lineBuffer := ""
collectingMultiLineValue := false
parsers = append(parsers, &gofrogcmd.CmdOutputPattern{RegExp: regexp.MustCompile(".*"), ExecFunc: func(pattern *gofrogcmd.CmdOutputPattern) (string, error) {
parsers = append(parsers, &gofrogcmd.CmdOutputPattern{RegExp: catchAllRegexp, ExecFunc: func(pattern *gofrogcmd.CmdOutputPattern) (string, error) {
// Check if the line matches the startCollectingPattern.
if !collectingMultiLineValue && startCollectionRegexp.MatchString(pattern.Line) {
// Start collecting lines.
Expand Down Expand Up @@ -206,6 +212,53 @@ func getMultilineSplitCaptureOutputPattern(startCollectingPattern, captureGroup,
return
}

// Mask the pre-known credentials that are provided as command arguments from logs.
// This function creates a log parser for each credentials argument.
func maskPreKnownCredentials(args []string) (parsers []*gofrogcmd.CmdOutputPattern) {
for _, arg := range args {
// If this argument is a credentials argument, create a log parser that masks it.
if credentialsInUrlRegexp.MatchString(arg) {
parsers = append(parsers, maskCredentialsArgument(arg, credentialsInUrlRegexp)...)
}
}
return
}

// Creates a log parser that masks a pre-known credentials argument from logs.
// Support both multiline (using the line buffer) and single line credentials.
func maskCredentialsArgument(credentialsArgument string, credentialsRegex *regexp.Regexp) (parsers []*gofrogcmd.CmdOutputPattern) {
lineBuffer := ""
parsers = append(parsers, &gofrogcmd.CmdOutputPattern{RegExp: catchAllRegexp, ExecFunc: func(pattern *gofrogcmd.CmdOutputPattern) (string, error) {
return handlePotentialCredentialsInLogLine(pattern.Line, credentialsArgument, &lineBuffer, credentialsRegex)
}})

return
}

func handlePotentialCredentialsInLogLine(patternLine, credentialsArgument string, lineBuffer *string, credentialsRegex *regexp.Regexp) (string, error) {
patternLine = strings.TrimSpace(patternLine)
if patternLine == "" {
return patternLine, nil
}

*lineBuffer += patternLine
// If the accumulated line buffer is not a prefix of the credentials argument, reset the buffer and return the line unchanged.
if !strings.HasPrefix(credentialsArgument, *lineBuffer) {
*lineBuffer = ""
return patternLine, nil
}

// When the whole credential was found (aggregated multiline or single line), return it filtered.
if credentialsRegex.MatchString(*lineBuffer) {
filteredLine, err := utils.RemoveCredentials(&gofrogcmd.CmdOutputPattern{Line: *lineBuffer, MatchedResults: credentialsRegex.FindStringSubmatch(*lineBuffer)})
*lineBuffer = ""
return filteredLine, err
}

// Avoid logging parts of the credentials till they are fully found.
return "", nil
}

func InstallWithLogParsing(tool PythonTool, commandArgs []string, log utils.Log, srcPath string) (map[string]entities.Dependency, error) {
if tool == Pipenv {
// Add verbosity flag to pipenv commands to collect necessary data
Expand Down Expand Up @@ -271,6 +324,8 @@ func InstallWithLogParsing(tool PythonTool, commandArgs []string, log utils.Log,
// Extract cached file, stored in Artifactory. (value at log may be split into multiple lines)
parsers = append(parsers, getMultilineSplitCaptureOutputPattern(startUsingCachedPattern, usingCacheCaptureGroup, endPattern, saveCaptureGroupAsDependencyInfo)...)

parsers = append(parsers, maskPreKnownCredentials(commandArgs)...)

// Extract already installed packages names.
parsers = append(parsers, &gofrogcmd.CmdOutputPattern{
RegExp: regexp.MustCompile(`^Requirement\salready\ssatisfied:\s(\w[\w-.]+)`),
Expand Down Expand Up @@ -307,5 +362,12 @@ func extractFileNameFromRegexCaptureGroup(pattern *gofrogcmd.CmdOutputPattern) (
if lastSlashIndex == -1 {
return filePath
}
return filePath[lastSlashIndex+1:]
lastComponent := filePath[lastSlashIndex+1:]
// Unescape the last component, for example 'PyYAML-5.1.2%2Bsp1.tar.gz' -> 'PyYAML-5.1.2+sp1.tar.gz'.
unescapedComponent, _ := url.QueryUnescape(lastComponent)
if unescapedComponent == "" {
// Couldn't escape, will use the raw string
return lastComponent
}
return unescapedComponent
}
92 changes: 92 additions & 0 deletions utils/pythonutils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package pythonutils

import (
"fmt"
"github.com/jfrog/build-info-go/utils"
"regexp"
"strings"
"testing"

Expand Down Expand Up @@ -77,6 +79,20 @@ Collecting PyYAML==5.1.2 (from jfrog-python-example==1.0)
Installing build dependencies: started`,
expectedCapture: `PyYAML-5.1.2.tar.gz`,
},
{
name: "Downloading - multi line captures",
startCapturePattern: startDownloadingPattern,
captureGroupPattern: downloadingCaptureGroup,
endCapturePattern: endPattern,
text: ` Preparing metadata (pyproject.toml): finished with status 'done'
Collecting PyYAML==5.1.2 (from jfrog-python-example==1.0)
Downloading http://localhost:8081/artifactory/api/pypi/cli-pypi-virtual-1698
829558/packages/packages/e3/e8/b3212641ee2718d556df0f23f78de8303f068fe29cdaa7a91018849
582fe/PyYAML-5.1.2%2Bsp1.tar.gz (265 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 265.0/265.0 kB 364.4 MB/s eta 0:00:00
Installing build dependencies: started`,
expectedCapture: `PyYAML-5.1.2+sp1.tar.gz`,
},
}

for _, testCase := range tests {
Expand Down Expand Up @@ -125,3 +141,79 @@ func runDummyTextStream(t *testing.T, txt string, parsers []*gofrogcmd.CmdOutput
}
}
}

func TestMaskPreKnownCredentials(t *testing.T) {
tests := []struct {
name string
inputText string
credentialsArgument string
}{
{
name: "Single line credentials",
inputText: `
Preparing Installation of "toml==0.10.2; python_version >= '2.6' and
python_version not in '3.0, 3.1, 3.2'
--hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b
--hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
$
/usr/local/Cellar/pipenv/2023.12.1/libexec/lib/python3.12/site-packages/pipenv/p
atched/pip/__pip-runner__.py install -i
https://user:[email protected]/artifactory/api/pypi/cli-pipenv-pypi-virtual-1715766379/simple
--no-input --upgrade --no-deps -r
/var/folders/2c/cdvww2550p90b0sdbz6w6jqc0000gn/T/pipenv-bs956chg-requirements/pi
penv-hejkfcsj-hashed-reqs.txt`,
credentialsArgument: "https://user:[email protected]/artifactory/api/pypi/cli-pipenv-pypi-virtual-1715766379/simple",
},
{
name: "Multiline credentials",
inputText: `
Preparing Installation of "toml==0.10.2; python_version >= '2.6' and
python_version not in '3.0, 3.1, 3.2'
--hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b
--hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
$
/usr/local/Cellar/pipenv/2023.12.1/libexec/lib/python3.12/site-packages/pipenv/p
atched/pip/__pip-runner__.py install -i
https://user:not.an.actual.token.not.an.actual.token.not.an.actual.token.not.an.
actual.token.not.an.actual.token.not.an.actual.token.not.an.actual.token.not.an.
actual.token.not.an.actual.token.not.an.actual.token.not.an.actual.token.not.an.
[email protected]/artifactory/api/pypi/cli-pipenv-pypi-virtual-17
15766379/simple
--no-input --upgrade --no-deps -r
/var/folders/2c/cdvww2550p90b0sdbz6w6jqc0000gn/T/pipenv-bs956chg-requirements/pi
penv-hejkfcsj-hashed-reqs.txt`,
credentialsArgument: "https://user:not.an.actual.token.not.an.actual.token.not.an.actual.token.not.an." +
"actual.token.not.an.actual.token.not.an.actual.token.not.an.actual.token.not.an." +
"actual.token.not.an.actual.token.not.an.actual.token.not.an.actual.token.not.an." +
"[email protected]/artifactory/api/pypi/cli-pipenv-pypi-virtual-17" +
"15766379/simple",
},
}

for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
assert.Contains(t, getOnelinerText(testCase.inputText), testCase.credentialsArgument)
outputText := maskCredentialsInText(t, testCase.inputText, testCase.credentialsArgument)
assert.NotContains(t, getOnelinerText(outputText), testCase.credentialsArgument)
})
}
}

// This method mimics RunCmdWithOutputParser, in which the masking parsers will be used.
func maskCredentialsInText(t *testing.T, text, credentialsArgument string) string {
lines := strings.Split(text, "\n")
credentialsRegex := regexp.MustCompile(utils.CredentialsInUrlRegexp)
lineBuffer := ""
outputText := ""

for _, line := range lines {
outputLine, err := handlePotentialCredentialsInLogLine(line, credentialsArgument, &lineBuffer, credentialsRegex)
assert.NoError(t, err)
outputText += outputLine + "\n"
}
return outputText
}

func getOnelinerText(inputText string) string {
return strings.ReplaceAll(inputText, "\n", "")
}

0 comments on commit dc48085

Please sign in to comment.