diff --git a/artifactory/services/utils/tests/xray/consts.go b/artifactory/services/utils/tests/xray/consts.go index 59ba85151..e8992eb57 100644 --- a/artifactory/services/utils/tests/xray/consts.go +++ b/artifactory/services/utils/tests/xray/consts.go @@ -1415,21 +1415,20 @@ const BuildScanResultsResponse = ` } ` - -var MapReportIdEndpoint = map[int]string { - 777: VulnerabilitiesEndpoint, - 888: LicensesEndpoint, +var MapReportIdEndpoint = map[int]string{ + 777: VulnerabilitiesEndpoint, + 888: LicensesEndpoint, } -var MapResponse = map[string]map[string]string { - VulnerabilitiesEndpoint: { - "XrayReportRequest": VulnerabilityXrayReportRequestResponse, - "ReportStatus": VulnerabilityReportStatusResponse, - "ReportDetails": VulnerabilityReportDetailsResponse, - }, - LicensesEndpoint: { - "XrayReportRequest": LicensesXrayReportRequestResponse, - "ReportStatus": LicensesReportStatusResponse, - "ReportDetails": LicensesReportDetailsResponse, - }, +var MapResponse = map[string]map[string]string{ + VulnerabilitiesEndpoint: { + "XrayReportRequest": VulnerabilityXrayReportRequestResponse, + "ReportStatus": VulnerabilityReportStatusResponse, + "ReportDetails": VulnerabilityReportDetailsResponse, + }, + LicensesEndpoint: { + "XrayReportRequest": LicensesXrayReportRequestResponse, + "ReportStatus": LicensesReportStatusResponse, + "ReportDetails": LicensesReportDetailsResponse, + }, } diff --git a/artifactory/services/utils/tests/xray/server.go b/artifactory/services/utils/tests/xray/server.go index ea8840caa..12abe70bf 100644 --- a/artifactory/services/utils/tests/xray/server.go +++ b/artifactory/services/utils/tests/xray/server.go @@ -84,7 +84,7 @@ func reportHandler(w http.ResponseWriter, r *http.Request) { log.Error(err) http.Error(w, err.Error(), http.StatusInternalServerError) } - + return } case http.MethodPost: @@ -114,7 +114,7 @@ func reportHandler(w http.ResponseWriter, r *http.Request) { } case http.MethodDelete: if numSegments == 0 { - _, err := fmt.Fprint(w, XrayReportDeleteResponse) + _, err := fmt.Fprint(w, XrayReportDeleteResponse) if err != nil { log.Error(err) http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/tests/xrayreport_test.go b/tests/xrayreport_test.go index 81c767b00..287bbd89f 100644 --- a/tests/xrayreport_test.go +++ b/tests/xrayreport_test.go @@ -32,34 +32,34 @@ func TestXrayReport(t *testing.T) { t.Run("reportAll", reportAll) } -var vulnerabilitiesReportRequestParams = services.VulnerabilitiesReportRequestParams { - Name: "test-report", - Filters: services.VulnerabilitiesFilter{ - HasRemediation: &trueValue, - Severity: []string{"high"}, - }, - Resources: services.Resource{ - Repositories: []services.Repository{ - { - Name: "dummy-repo", - }, +var vulnerabilitiesReportRequestParams = services.VulnerabilitiesReportRequestParams{ + Name: "test-report", + Filters: services.VulnerabilitiesFilter{ + HasRemediation: &trueValue, + Severity: []string{"high"}, + }, + Resources: services.Resource{ + Repositories: []services.Repository{ + { + Name: "dummy-repo", }, }, - } -var licensesReportRequestParams = services.LicensesReportRequestParams { - Name: "test-report", - Filters: services.LicensesFilter{ - LicensePatterns: []string{"*"}, - }, - Resources: services.Resource{ - Repositories: []services.Repository{ - { - Name: "dummy-repo", - }, + }, +} +var licensesReportRequestParams = services.LicensesReportRequestParams{ + Name: "test-report", + Filters: services.LicensesFilter{ + LicensePatterns: []string{"*"}, + }, + Resources: services.Resource{ + Repositories: []services.Repository{ + { + Name: "dummy-repo", }, }, - } -var reportTypes = []string { + }, +} +var reportTypes = []string{ xray.VulnerabilitiesEndpoint, xray.LicensesEndpoint, } @@ -83,15 +83,15 @@ func reportAll(t *testing.T) { reportReqCont := services.ReportContentRequestParams{ ReportType: ep, - ReportId: reportId, - Direction: "asc", - PageNum: 0, - NumRows: 7, + ReportId: reportId, + Direction: "asc", + PageNum: 0, + NumRows: 7, } if ep == xray.VulnerabilitiesEndpoint { - reportReqCont.OrderBy = "severity" + reportReqCont.OrderBy = "severity" } else if ep == xray.LicensesEndpoint { - reportReqCont.OrderBy = "license" + reportReqCont.OrderBy = "license" } content, err := testXrayReportService.Content(reportReqCont) assert.NoError(t, err) diff --git a/xray/manager.go b/xray/manager.go index c847644c0..06d185a5f 100644 --- a/xray/manager.go +++ b/xray/manager.go @@ -127,10 +127,10 @@ func (sm *XrayServicesManager) ScanGraph(params services.XrayGraphScanParams) (s // GetScanGraphResults returns an Xray scan output of the requested graph scan. // The scanId input should be received from ScanGraph request. -func (sm *XrayServicesManager) GetScanGraphResults(scanID string, includeVulnerabilities, includeLicenses bool) (*services.ScanResponse, error) { +func (sm *XrayServicesManager) GetScanGraphResults(scanID string, includeVulnerabilities, includeLicenses, xscEnabled bool) (*services.ScanResponse, error) { scanService := services.NewScanService(sm.client) scanService.XrayDetails = sm.config.GetServiceDetails() - return scanService.GetScanGraphResults(scanID, includeVulnerabilities, includeLicenses) + return scanService.GetScanGraphResults(scanID, includeVulnerabilities, includeLicenses, xscEnabled) } // BuildScan scans a published build-info with Xray. @@ -197,3 +197,9 @@ func (sm *XrayServicesManager) IsEntitled(featureId string) (bool, error) { entitlementsService.XrayDetails = sm.config.GetServiceDetails() return entitlementsService.IsEntitled(featureId) } + +func (sm *XrayServicesManager) XscEnabled() (string, error) { + scanService := services.NewScanService(sm.client) + scanService.XrayDetails = sm.config.GetServiceDetails() + return scanService.IsXscEnabled() +} diff --git a/xray/services/report.go b/xray/services/report.go index d159c9c0d..194b9ecdf 100644 --- a/xray/services/report.go +++ b/xray/services/report.go @@ -12,11 +12,10 @@ import ( const ( // ReportsAPI refer to: https://www.jfrog.com/confluence/display/JFROG/Xray+REST+API#XrayRESTAPI-REPORTS - ReportsAPI = "api/v1/reports" - Vulnerabilities = "vulnerabilities" - Licenses = "licenses" - Violations = "violations" - + ReportsAPI = "api/v1/reports" + Vulnerabilities = "vulnerabilities" + Licenses = "licenses" + Violations = "violations" ) // ReportService defines the Http client and Xray details @@ -43,11 +42,11 @@ type ReportDetails struct { // ReportContentRequestParams defines a report content request type ReportContentRequestParams struct { ReportType string - ReportId string - Direction string - PageNum int - NumRows int - OrderBy string + ReportId string + Direction string + PageNum int + NumRows int + OrderBy string } // ReportContent defines a report content response @@ -77,17 +76,17 @@ type Row struct { ExternalAdvisorySource string `json:"external_advisory_source,omitempty"` ExternalAdvisorySeverity string `json:"external_advisory_severity,omitempty"` // Licenses Report field - License string `json:"license,omitempty"` - LicenseName string `json:"license_name,omitempty"` - Component string `json:"component,omitempty"` - Artifact string `json:"artifact,omitempty"` - ArtifactScanTime string `json:"artifact_scan_time,omitempty"` - Unknown *bool `json:"unknown,omitempty"` - Unrecognized *bool `json:"unrecognized,omitempty"` - Custom *bool `json:"custom,omitempty"` + License string `json:"license,omitempty"` + LicenseName string `json:"license_name,omitempty"` + Component string `json:"component,omitempty"` + Artifact string `json:"artifact,omitempty"` + ArtifactScanTime string `json:"artifact_scan_time,omitempty"` + Unknown *bool `json:"unknown,omitempty"` + Unrecognized *bool `json:"unrecognized,omitempty"` + Custom *bool `json:"custom,omitempty"` // Common field - Path string `json:"path,omitempty"` - References []string `json:"references,omitempty"` + Path string `json:"path,omitempty"` + References []string `json:"references,omitempty"` } // For backwork compatibility keeping old struct name diff --git a/xray/services/scan.go b/xray/services/scan.go index 13415d9e9..891c10d49 100644 --- a/xray/services/scan.go +++ b/xray/services/scan.go @@ -2,6 +2,7 @@ package services import ( "encoding/json" + "fmt" "github.com/jfrog/jfrog-client-go/utils/log" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" "net/http" @@ -38,6 +39,17 @@ const ( Binary ScanType = "binary" xrayScanStatusFailed = "failed" + + // Xsc consts + postScanContextAPI = "api/v1/gitinfo" + + XscGraphAPI = "api/v1/sca/scan/graph" + + multiScanIdParam = "multi_scan_id=" + + scanTechQueryParam = "tech=" + + XscVersionAPI = "api/v1/system/version" ) type ScanType string @@ -67,6 +79,17 @@ func createScanGraphQueryParams(scanParams XrayGraphScanParams) string { } } + if scanParams.XscVersion != "" { + params = append(params, multiScanIdParam+scanParams.MultiScanId) + gitInfoContext := scanParams.XscGitInfoContext + if gitInfoContext != nil { + if len(gitInfoContext.Technologies) > 0 { + // Append the tech type, each graph can contain only one tech type + params = append(params, scanTechQueryParam+gitInfoContext.Technologies[0]) + } + } + } + if scanParams.ScanType != "" { params = append(params, scanTypeQueryParam+string(scanParams.ScanType)) } @@ -78,6 +101,14 @@ func createScanGraphQueryParams(scanParams XrayGraphScanParams) string { } func (ss *ScanService) ScanGraph(scanParams XrayGraphScanParams) (string, error) { + if scanParams.XscVersion != "" && scanParams.XscGitInfoContext != nil { + multiScanId, err := ss.SendScanGitInfoContext(scanParams.XscGitInfoContext) + if err != nil { + return "", fmt.Errorf("failed sending Git Info to XSC service, error: %s ", err.Error()) + } + scanParams.MultiScanId = multiScanId + } + httpClientsDetails := ss.XrayDetails.CreateHttpClientDetails() utils.SetContentType("application/json", &httpClientsDetails.Headers) var err error @@ -91,6 +122,11 @@ func (ss *ScanService) ScanGraph(scanParams XrayGraphScanParams) (string, error) return "", errorutils.CheckError(err) } url := ss.XrayDetails.GetUrl() + scanGraphAPI + + // When XSC is enabled, modify the URL. + if scanParams.XscVersion != "" { + url = ss.xrayToXscUrl() + XscGraphAPI + } url += createScanGraphQueryParams(scanParams) resp, body, err := ss.client.SendPost(url, requestBody, &httpClientsDetails) if err != nil { @@ -111,12 +147,18 @@ func (ss *ScanService) ScanGraph(scanParams XrayGraphScanParams) (string, error) return scanResponse.ScanId, err } -func (ss *ScanService) GetScanGraphResults(scanId string, includeVulnerabilities, includeLicenses bool) (*ScanResponse, error) { +func (ss *ScanService) GetScanGraphResults(scanId string, includeVulnerabilities, includeLicenses, xscEnabled bool) (*ScanResponse, error) { httpClientsDetails := ss.XrayDetails.CreateHttpClientDetails() utils.SetContentType("application/json", &httpClientsDetails.Headers) // The scan request may take some time to complete. We expect to receive a 202 response, until the completion. - endPoint := ss.XrayDetails.GetUrl() + scanGraphAPI + "/" + scanId + endPoint := ss.XrayDetails.GetUrl() + scanGraphAPI + // Modify endpoint if XSC is enabled + if xscEnabled { + endPoint = ss.xrayToXscUrl() + XscGraphAPI + } + endPoint += "/" + scanId + if includeVulnerabilities { endPoint += includeVulnerabilitiesParam if includeLicenses { @@ -162,6 +204,59 @@ func (ss *ScanService) GetScanGraphResults(scanId string, includeVulnerabilities return &scanResponse, err } +func (ss *ScanService) xrayToXscUrl() string { + return strings.Replace(ss.XrayDetails.GetUrl(), "/xray", "/xsc", 1) +} + +func (ss *ScanService) SendScanGitInfoContext(details *XscGitInfoContext) (multiScanId string, err error) { + httpClientsDetails := ss.XrayDetails.CreateHttpClientDetails() + utils.SetContentType("application/json", &httpClientsDetails.Headers) + requestBody, err := json.Marshal(details) + if err != nil { + return "", errorutils.CheckError(err) + } + url := ss.xrayToXscUrl() + postScanContextAPI + resp, body, err := ss.client.SendPost(url, requestBody, &httpClientsDetails) + if err != nil { + return + } + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusCreated); err != nil { + return + } + xscResponse := XscPostContextResponse{} + if err = json.Unmarshal(body, &xscResponse); err != nil { + return "", errorutils.CheckError(err) + } + return xscResponse.MultiScanId, err +} + +// IsXscEnabled will try to get XSC version. If route is not available, user is not entitled for XSC. +func (ss *ScanService) IsXscEnabled() (xsxVersion string, err error) { + httpClientsDetails := ss.XrayDetails.CreateHttpClientDetails() + serverDetails := ss.XrayDetails + xscUrl := strings.Replace(serverDetails.GetUrl(), "xray", "xsc", 1) + resp, body, _, err := ss.client.SendGet(xscUrl+XscVersionAPI, true, &httpClientsDetails) + if err != nil { + err = errorutils.CheckErrorf("failed to get XSC version, response: " + err.Error()) + return + } + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusNotFound); err != nil { + return + } + // When XSC is disabled, StatusNotFound is expected. Don't return error as this is optional. + if resp.StatusCode == http.StatusNotFound { + return + } + versionResponse := XscVersionResponse{} + if err = json.Unmarshal(body, &versionResponse); err != nil { + err = errorutils.CheckErrorf("failed to parse XSC server response: " + err.Error()) + return + } + xsxVersion = versionResponse.Version + log.Debug("XSC version:", xsxVersion) + return +} + type XrayGraphScanParams struct { // A path in Artifactory that this Artifact is intended to be deployed to. // This will provide a way to extract the watches that should be applied on this graph @@ -175,6 +270,9 @@ type XrayGraphScanParams struct { BinaryGraph *xrayUtils.BinaryGraphNode IncludeVulnerabilities bool IncludeLicenses bool + XscGitInfoContext *XscGitInfoContext + XscVersion string + MultiScanId string } type RequestScanResponse struct { @@ -273,6 +371,27 @@ type JfrogResearchSeverityReason struct { IsPositive bool `json:"is_positive,omitempty"` } +type XscPostContextResponse struct { + MultiScanId string `json:"multi_scan_id,omitempty"` +} + +type XscVersionResponse struct { + Version string `json:"xsc_version"` +} + +type XscGitInfoContext struct { + GitRepoUrl string `json:"git_repo_url"` + GitRepoName string `json:"git_repo_name"` + GitProject string `json:"git_project"` + GitProvider string `json:"git_provider"` + Technologies []string `json:"technologies"` + BranchName string `json:"branch_name"` + LastCommit string `json:"last_commit"` + CommitHash string `json:"commit_hash"` + CommitMessage string `json:"commit_message"` + CommitAuthor string `json:"commit_author"` +} + func (gp *XrayGraphScanParams) GetProjectKey() string { return gp.ProjectKey }