Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions cli/docs/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ const (
AnalyzerManagerCustomPath = "analyzer-manager-path"

// Unique curation flags
CurationOutput = "curation-format"
DockerImageName = "image"
SolutionPath = "solution-path"
CurationOutput = "curation-format"
DockerImageName = "image"
SolutionPath = "solution-path"
IncludeCachedPackages = "include-cached-packages"

// Unique git flags
InputFile = "input-file"
Expand Down Expand Up @@ -214,7 +215,7 @@ var commandFlags = map[string][]string{
StaticSca, XrayLibPluginBinaryCustomPath, AnalyzerManagerCustomPath, AddSastRules,
},
CurationAudit: {
CurationOutput, WorkingDirs, Threads, RequirementsFile, InsecureTls, useWrapperAudit, UseIncludedBuilds, SolutionPath, DockerImageName,
CurationOutput, WorkingDirs, Threads, RequirementsFile, InsecureTls, useWrapperAudit, UseIncludedBuilds, SolutionPath, DockerImageName,IncludeCachedPackages,
},
GitCountContributors: {
InputFile, ScmType, ScmApiUrl, Token, Owner, RepoName, Months, DetailedSummary, InsecureTls,
Expand Down Expand Up @@ -331,6 +332,7 @@ var flagsMap = map[string]components.Flag{
StaticSca: components.NewBoolFlag(StaticSca, "Set to true to use the new SCA engine which is based on lock files.", components.SetHiddenBoolFlag()),
CurationOutput: components.NewStringFlag(OutputFormat, "Defines the output format of the command. Acceptable values are: table, json.", components.WithStrDefaultValue("table")),
SolutionPath: components.NewStringFlag(SolutionPath, "Path to the .NET solution file (.sln) to use when multiple solution files are present in the directory."),
IncludeCachedPackages: components.NewBoolFlag(IncludeCachedPackages, "Set to true to audit cached packages. This is also required when using Curation on-demand, as packages are cached."),
binarySca: components.NewBoolFlag(Sca, fmt.Sprintf("Selective scanners mode: Execute SCA (Software Composition Analysis) sub-scan. Use --%s to run both SCA and Contextual Analysis. Use --%s --%s to to run SCA. Can be combined with --%s.", Sca, Sca, WithoutCA, Secrets)),
binarySecrets: components.NewBoolFlag(Secrets, fmt.Sprintf("Selective scanners mode: Execute Secrets sub-scan. Can be combined with --%s.", Sca)),
binaryWithoutCA: components.NewBoolFlag(WithoutCA, fmt.Sprintf("Selective scanners mode: Disable Contextual Analysis scanner after SCA. Relevant only with --%s flag.", Sca)),
Expand Down
1 change: 1 addition & 0 deletions cli/scancommands.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ func getCurationCommand(c *components.Context) (*curation.CurationAuditCommand,
SetPipRequirementsFile(c.GetStringFlagValue(flags.RequirementsFile)).
SetSolutionFilePath(c.GetStringFlagValue(flags.SolutionPath))
curationAuditCommand.SetDockerImageName(c.GetStringFlagValue(flags.DockerImageName))
curationAuditCommand.SetIncludeCachedPackages(c.GetBoolFlagValue(flags.IncludeCachedPackages))
return curationAuditCommand, nil
}

Expand Down
98 changes: 59 additions & 39 deletions commands/curation/curationaudit.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,14 @@ const (
blocked = "blocked"
BlockingReasonPolicy = "Policy violations"
BlockingReasonNotFound = "Package pending update"
BlockingReasonOnDemand = "Package pending — Curation on-demand scan in progress"

directRelation = "direct"
indirectRelation = "indirect"

BlockMessageKey = "jfrog packages curation"
NotBeingFoundKey = "not being found"
IsOnDemand = "on-demand"

extractPoliciesRegexTemplate = "({.*?})"

Expand Down Expand Up @@ -202,24 +204,26 @@ type PackageStatusTable struct {
}

type treeAnalyzer struct {
rtManager artifactory.ArtifactoryServicesManager
extractPoliciesRegex *regexp.Regexp
rtAuth auth.ServiceDetails
httpClientDetails httputils.HttpClientDetails
url string
repo string
tech techutils.Technology
parallelRequests int
downloadUrls map[string]string
rtManager artifactory.ArtifactoryServicesManager
extractPoliciesRegex *regexp.Regexp
rtAuth auth.ServiceDetails
httpClientDetails httputils.HttpClientDetails
url string
repo string
tech techutils.Technology
parallelRequests int
downloadUrls map[string]string
includeCachedPackages bool
}

type CurationAuditCommand struct {
PackageManagerConfig *project.RepositoryConfig
extractPoliciesRegex *regexp.Regexp
workingDirs []string
OriginPath string
parallelRequests int
dockerImageName string
PackageManagerConfig *project.RepositoryConfig
extractPoliciesRegex *regexp.Regexp
workingDirs []string
OriginPath string
parallelRequests int
dockerImageName string
includeCachedPackages bool
audit.AuditParamsInterface
}

Expand Down Expand Up @@ -265,6 +269,11 @@ func (ca *CurationAuditCommand) SetDockerImageName(dockerImageName string) *Cura
return ca
}

func (ca *CurationAuditCommand) SetIncludeCachedPackages(includeCachedPackages bool) *CurationAuditCommand {
ca.includeCachedPackages = includeCachedPackages
return ca
}

func (ca *CurationAuditCommand) Run() (err error) {
rootDir, err := os.Getwd()
if err != nil {
Expand Down Expand Up @@ -500,15 +509,16 @@ func (ca *CurationAuditCommand) auditTree(tech techutils.Technology, results map
}
var packagesStatus []*PackageStatus
analyzer := treeAnalyzer{
rtManager: rtManager,
extractPoliciesRegex: ca.extractPoliciesRegex,
rtAuth: rtAuth,
httpClientDetails: rtAuth.CreateHttpClientDetails(),
url: rtAuth.GetUrl(),
repo: ca.PackageManagerConfig.TargetRepo(),
tech: tech,
parallelRequests: ca.parallelRequests,
downloadUrls: depTreeResult.DownloadUrls,
rtManager: rtManager,
extractPoliciesRegex: ca.extractPoliciesRegex,
rtAuth: rtAuth,
httpClientDetails: rtAuth.CreateHttpClientDetails(),
url: rtAuth.GetUrl(),
repo: ca.PackageManagerConfig.TargetRepo(),
tech: tech,
parallelRequests: ca.parallelRequests,
downloadUrls: depTreeResult.DownloadUrls,
includeCachedPackages: ca.includeCachedPackages,
}

rootNodes := map[string]struct{}{}
Expand Down Expand Up @@ -866,7 +876,7 @@ func (nc *treeAnalyzer) fetchNodeStatus(node xrayUtils.GraphNode, p *sync.Map) e
if resp != nil && resp.StatusCode >= 400 && resp.StatusCode != http.StatusForbidden {
return errorutils.CheckErrorf(errorTemplateHeadRequest, packageUrl, name, version, resp.StatusCode, err)
}
if resp.StatusCode == http.StatusForbidden {
if resp.StatusCode == http.StatusForbidden || (nc.includeCachedPackages && resp.StatusCode == http.StatusOK) {
pkStatus, err := nc.getBlockedPackageDetails(packageUrl, name, version)
if err != nil {
return err
Expand Down Expand Up @@ -911,6 +921,8 @@ func (nc *treeAnalyzer) getBlockedPackageDetails(packageUrl string, name string,
blockingReason := BlockingReasonPolicy
if strings.Contains(strings.ToLower(respError.Errors[0].Message), NotBeingFoundKey) {
blockingReason = BlockingReasonNotFound
} else if strings.Contains(strings.ToLower(respError.Errors[0].Message), IsOnDemand) {
blockingReason = BlockingReasonOnDemand
}
policies := nc.extractPoliciesFromMsg(respError)
return &PackageStatus{
Expand All @@ -933,21 +945,29 @@ func (nc *treeAnalyzer) getBlockedPackageDetails(packageUrl string, name string,
func (nc *treeAnalyzer) extractPoliciesFromMsg(respError *ErrorsResp) []Policy {
var policies []Policy
msg := respError.Errors[0].Message
allMatches := nc.extractPoliciesRegex.FindAllString(msg, -1)
for _, match := range allMatches {
match = strings.TrimSuffix(strings.TrimPrefix(match, "{"), "}")
polCond := strings.Split(match, ",")
if len(polCond) >= 2 {
pol := polCond[0]
cond := polCond[1]

if len(polCond) == 4 {
exp, rec := makeLegiblePolicyDetails(polCond[2], polCond[3])
policies = append(policies, Policy{Policy: strings.TrimSpace(pol),
Condition: strings.TrimSpace(cond), Explanation: strings.TrimSpace(exp), Recommendation: strings.TrimSpace(rec)})
continue
lowerMsg := strings.ToLower(msg)
switch {
case strings.Contains(lowerMsg, IsOnDemand):
policies = []Policy{{Explanation: BlockingReasonOnDemand}}
case strings.Contains(lowerMsg, NotBeingFoundKey):
policies = []Policy{{Explanation: BlockingReasonNotFound}}
default:
allMatches := nc.extractPoliciesRegex.FindAllString(msg, -1)
for _, match := range allMatches {
match = strings.TrimSuffix(strings.TrimPrefix(match, "{"), "}")
polCond := strings.Split(match, ",")
if len(polCond) >= 2 {
pol := polCond[0]
cond := polCond[1]

if len(polCond) == 4 {
exp, rec := makeLegiblePolicyDetails(polCond[2], polCond[3])
policies = append(policies, Policy{Policy: strings.TrimSpace(pol),
Condition: strings.TrimSpace(cond), Explanation: strings.TrimSpace(exp), Recommendation: strings.TrimSpace(rec)})
continue
}
policies = append(policies, Policy{Policy: strings.TrimSpace(pol), Condition: strings.TrimSpace(cond)})
}
policies = append(policies, Policy{Policy: strings.TrimSpace(pol), Condition: strings.TrimSpace(cond)})
}
}
return policies
Expand Down
32 changes: 32 additions & 0 deletions commands/curation/curationaudit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,38 @@ func getTestCasesForExtractPoliciesFromMsg() []struct {
},
expect: nil,
},
{
name: "on-demand in progress",
errResp: &ErrorsResp{
Errors: []ErrorResp{
{
Status: 403,
Message: "Package test:1.0.0 download was blocked by JFrog Packages Curation service due to the package not being found in catalog, curation on-demand scan in progress.",
},
},
},
expect: []Policy{
{
Explanation: BlockingReasonOnDemand,
},
},
},
{
name: "package not found in catalog",
errResp: &ErrorsResp{
Errors: []ErrorResp{
{
Status: 403,
Message: "package test:1.0.0 download was blocked by jfrog packages curation service due to the package not being found in catalog",
},
},
},
expect: []Policy{
{
Explanation: BlockingReasonNotFound,
},
},
},
}
return tests
}
Expand Down
3 changes: 3 additions & 0 deletions sca/bom/buildinfo/technologies/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ func findDigestForPlatform(imageName, targetOS, targetArch string) (string, erro
buildxCmd := exec.Command("docker", "buildx", "imagetools", "inspect", imageName, "--raw")
buildxOutput, buildxErr := buildxCmd.CombinedOutput()
if buildxErr != nil {
if strings.Contains(string(buildxOutput), "curation service") {
return extractDigestFromBlockedMessage(string(buildxOutput)), nil
}
return "", fmt.Errorf("docker buildx imagetools inspect failed: %s", strings.TrimSpace(string(buildxOutput)))
}

Expand Down