diff --git a/README.md b/README.md index 63910b382..6167f726b 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,9 @@ - [Get an Xray Policy](#get-an-xray-policy) - [Update an Xray Policy](#update-an-xray-policy) - [Delete an Xray Policy](#delete-an-xray-policy) + - [Create an Xray Ignore Rule](#create-an-xray-ignore-rule) + - [Get an Xray Ignore Rule](#get-an-xray-ignore-rule) + - [Delete an Xray Ignore Rule](#delete-an-xray-ignore-rule) - [Add Builds to Indexing Configuration](#add-builds-to-indexing-configuration) - [Request Graph Scan](#request-graph-scan) - [Retrieve the Graph Scan Results](#retrieve-the-graph-scan-results) @@ -1985,6 +1988,91 @@ err := xrayManager.UpdatePolicy(*policy) err := xrayManager.DeletePolicy("example-policy") ``` +#### Create an Xray Ignore Rule + +```go +params := utils.NewIgnoreRuleParams() +params.Notes := "random-notes-for-ignore-rules" +params.ExpiredAt := time.Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) +params.IgnoreFilters := IgnoreFilters{ + Vulnerabilities: []string{"XRAY-12345", "XRAY-67891"}, + Licenses: []string{"MIT", "BSD"}, + CVEs: []string{"CVE-2021-1234", "CVE-2022-5678"}, + Policies: []string{"policy-name-1", "policy-name-2"}, + Watches: []string{"watch-name-1", "watch-name-2"}, + DockerLayers: []string{"0503825856099e6adb39c8297af09547f69684b7016b7f3680ed801aa310baaa"}, + OperationalRisks: []string{"any"}, + Exposures: []ExposuresFilterName{ + { + FilePath: []string{"/path/to/file1", "/path/to/file2"}, + Scanners: []string{"EXP-12345"}, + Catagories: []ExposuresCatagories{ + { + Secrets: true, + Services: true, + Applications: true, + Iac: true, + }, + }, + }, + }, + ReleaseBundles: []IgnoreFilterNameVersion{ + { + Name: "RB-name", + Version: "0.0.0", + }, + { + Name: "RB-name-2", + Version: "1.2.3", + }, + }, + Builds: []IgnoreFilterNameVersion{ + { + Name: "build-name", + Version: "0.0.0", + }, + { + Name: "build-name-2", + Version: "1.2.3", + }, + }, + Components: []IgnoreFilterNameVersion{ + { + Name: "component-name", + Version: "0.0.0", + }, + { + Name: "component-name-2", + Version: "1.2.3", + }, + }, + Arti: []IgnoreFilterNameVersion{ + { + Name: "artifact-name", + Version: "0.0.0", + }, + { + Name: "artifact-name-2", + Version: "1.2.3", + }, + }, +} + +ignoreRuleIgnoreId, err := xrayManager.CreateIgnoreRule(params) +``` + +#### Get an Xray Ignore Rule + +```go +ignoreRule, err := xrayManager.GetIgnoreRule("ignore-rule-id") +``` + +#### Delete an Xray Ignore Rule + +```go +err := xrayManager.DeleteIgnoreRule("ignore-rule-id") +``` + #### Add Builds to Indexing Configuration ```go diff --git a/tests/jfrogclient_test.go b/tests/jfrogclient_test.go index ceda8bd8a..ab02ff733 100644 --- a/tests/jfrogclient_test.go +++ b/tests/jfrogclient_test.go @@ -65,6 +65,7 @@ func setupIntegrationTests() { createXrayWatchManager() createXrayPolicyManager() createXrayBinMgrManager() + createXrayIgnoreRuleManager() } if *TestPipelines { createPipelinesIntegrationsManager() diff --git a/tests/utils_test.go b/tests/utils_test.go index feb157850..dc3b5c98b 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -113,9 +113,10 @@ var ( testsBundleDeleteRemoteService *distributionServices.DeleteReleaseBundleService // Xray Services - testsXrayWatchService *xrayServices.WatchService - testsXrayPolicyService *xrayServices.PolicyService - testXrayBinMgrService *xrayServices.BinMgrService + testsXrayWatchService *xrayServices.WatchService + testsXrayPolicyService *xrayServices.PolicyService + testXrayBinMgrService *xrayServices.BinMgrService + testsXrayIgnoreRuleService *xrayServices.IgnoreRuleService // Pipelines Services testsPipelinesIntegrationsService *pipelinesServices.IntegrationsService @@ -480,6 +481,18 @@ func createXrayBinMgrManager() { testXrayBinMgrService.XrayDetails = xrayDetails } +func createXrayIgnoreRuleManager() { + xrayDetails := GetXrayDetails() + client, err := jfroghttpclient.JfrogClientBuilder(). + SetClientCertPath(xrayDetails.GetClientCertPath()). + SetClientCertKeyPath(xrayDetails.GetClientCertKeyPath()). + AppendPreRequestInterceptor(xrayDetails.RunPreRequestFunctions). + Build() + failOnHttpClientCreation(err) + testsXrayIgnoreRuleService = xrayServices.NewIgnoreRuleService(client) + testsXrayIgnoreRuleService.XrayDetails = xrayDetails +} + func createPipelinesIntegrationsManager() { pipelinesDetails := GetPipelinesDetails() client, err := jfroghttpclient.JfrogClientBuilder(). diff --git a/tests/xrayignorerule_test.go b/tests/xrayignorerule_test.go new file mode 100644 index 000000000..9b663f282 --- /dev/null +++ b/tests/xrayignorerule_test.go @@ -0,0 +1,124 @@ +package tests + +import ( + "fmt" + "testing" + "time" + + "github.com/jfrog/jfrog-client-go/xray/services/utils" + "github.com/stretchr/testify/assert" +) + +func TestXrayIgnoreRule(t *testing.T) { + initXrayTest(t) + t.Run("createCveIgnoreRule", createCveIgnoreRule) + t.Run("createVulnerabilitesAndLicensesIgnoreRule", createVulnerabilitesAndLicensesIgnoreRule) + t.Run("createIgnoreRuleOnWatch", createIgnoreRuleOnWatch) +} + +func deleteIgnoreRule(t *testing.T, ignoreRuleId string) { + err := testsXrayIgnoreRuleService.Delete(ignoreRuleId) + assert.NoError(t, err) +} + +func createCveIgnoreRule(t *testing.T) { + var ignoreRuleId string + defer func() { + deleteIgnoreRule(t, ignoreRuleId) + }() + + component := utils.IgnoreFilterNameVersion{ + Name: "gav://org.postgresql:postgresql", + Version: "42.2.3.jre7", + } + components := []utils.IgnoreFilterNameVersion{component} + + cve := []string{"CVE-2022-31197"} + ignoreRuleFilter := utils.IgnoreFilters{ + CVEs: cve, + Components: components, + } + + ignoreRuleId = createIgnoreRule(t, ignoreRuleFilter) + assert.NotEmpty(t, ignoreRuleId) +} + +func createVulnerabilitesAndLicensesIgnoreRule(t *testing.T) { + var ignoreRuleId string + defer func() { + deleteIgnoreRule(t, ignoreRuleId) + }() + + vulnerabilities := []string{"any"} + licenses := []string{"any"} + releaseBundle := utils.IgnoreFilterNameVersion{ + Name: "testRB", + } + releaseBundles := []utils.IgnoreFilterNameVersion{releaseBundle} + ignoreRuleFilter := utils.IgnoreFilters{ + Vulnerabilities: vulnerabilities, + Licenses: licenses, + ReleaseBundles: releaseBundles, + } + + ignoreRuleId = createIgnoreRule(t, ignoreRuleFilter) + assert.NotEmpty(t, ignoreRuleId) +} + +func createIgnoreRuleOnWatch(t *testing.T) { + cve := []string{"CVE-2022-31197"} + policyName := fmt.Sprintf("%s-%s", "test-policy-for-dummy-watch", getRunId()) + watchName := fmt.Sprintf("%s-%s", "test-watch-for-ignore-rule", getRunId()) + err := createDummyWatch(policyName, watchName) + defer func() { + assert.NoError(t, testsXrayWatchService.Delete(watchName)) + assert.NoError(t, testsXrayPolicyService.Delete(policyName)) + }() + assert.NoError(t, err) + watches := []string{watchName} + + var ignoreRuleId string + defer func() { + deleteIgnoreRule(t, ignoreRuleId) + }() + + ignoreRuleFilter := utils.IgnoreFilters{ + CVEs: cve, + Watches: watches, + } + + ignoreRuleId = createIgnoreRule(t, ignoreRuleFilter) + assert.NotEmpty(t, ignoreRuleId) +} + +func createIgnoreRule(t *testing.T, ignoreRuleFilter utils.IgnoreFilters) (ignoreRuleId string) { + ignoreRuleParams := utils.IgnoreRuleParams{ + Notes: "Create new ignore rule" + getRunId(), + ExpiresAt: time.Now().AddDate(0, 0, 1), + IgnoreFilters: ignoreRuleFilter, + } + + ignoreRuleId, err := testsXrayIgnoreRuleService.Create(ignoreRuleParams) + assert.NoError(t, err) + return ignoreRuleId +} + +func createDummyWatch(policyName string, watchName string) error { + if err := createDummyPolicy(policyName); err != nil { + return err + } + params := utils.WatchParams{ + Name: watchName, + Active: true, + Repositories: utils.WatchRepositoriesParams{ + Type: utils.WatchRepositoriesAll, + }, + Policies: []utils.AssignedPolicy{ + { + Name: policyName, + Type: "security", + }, + }, + } + return testsXrayWatchService.Create(params) +} diff --git a/xray/manager.go b/xray/manager.go index fa14c5ceb..25256d90f 100644 --- a/xray/manager.go +++ b/xray/manager.go @@ -111,6 +111,30 @@ func (sm *XrayServicesManager) DeletePolicy(policyName string) error { return policyService.Delete(policyName) } +// CreatePolicy will create a new Xray ignore rule +// The function returns the ignore rule id if succeeded or empty string and error message if fails +func (sm *XrayServicesManager) CreateIgnoreRule(params utils.IgnoreRuleParams) (string, error) { + ignoreRuleService := services.NewIgnoreRuleService(sm.client) + ignoreRuleService.XrayDetails = sm.config.GetServiceDetails() + return ignoreRuleService.Create(params) +} + +// CreatePolicy will create a new Xray ignore rule +// The function returns the ignore rule id if succeeded or empty string and error message if fails +func (sm *XrayServicesManager) GetIgnoreRule(ignoreRuleId string) (*utils.IgnoreRuleParams, error) { + ignoreRuleService := services.NewIgnoreRuleService(sm.client) + ignoreRuleService.XrayDetails = sm.config.GetServiceDetails() + return ignoreRuleService.Get(ignoreRuleId) +} + +// CreatePolicy will create a new Xray ignore rule +// The function returns the ignore rule id if succeeded or empty string and error message if fails +func (sm *XrayServicesManager) DeleteIgnoreRule(ignoreRuleId string) error { + ignoreRuleService := services.NewIgnoreRuleService(sm.client) + ignoreRuleService.XrayDetails = sm.config.GetServiceDetails() + return ignoreRuleService.Delete(ignoreRuleId) +} + // AddBuildsToIndexing will add builds to Xray indexing configuration func (sm *XrayServicesManager) AddBuildsToIndexing(buildNames []string) error { binMgrService := services.NewBinMgrService(sm.client) diff --git a/xray/services/ignorerule.go b/xray/services/ignorerule.go new file mode 100644 index 000000000..440ca6ffe --- /dev/null +++ b/xray/services/ignorerule.go @@ -0,0 +1,139 @@ +package services + +import ( + "encoding/json" + "fmt" + "net/http" + "regexp" + + artUtils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + clientutils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/jfrog/jfrog-client-go/xray/services/utils" +) + +const ( + ignoreRuleAPIURL = "api/v1/ignore_rules" +) + +// IgnoreRuleService defines the http client and Xray details +type IgnoreRuleService struct { + client *jfroghttpclient.JfrogHttpClient + XrayDetails auth.ServiceDetails +} + +// NewIgnoreRuleService creates a new Xray Ignore Rule Service +func NewIgnoreRuleService(client *jfroghttpclient.JfrogHttpClient) *IgnoreRuleService { + return &IgnoreRuleService{client: client} +} + +// GetXrayDetails returns the Xray details +func (xirs *IgnoreRuleService) GetXrayDetails() auth.ServiceDetails { + return xirs.XrayDetails +} + +// GetJfrogHttpClient returns the http client +func (xirs *IgnoreRuleService) GetJfrogHttpClient() *jfroghttpclient.JfrogHttpClient { + return xirs.client +} + +// The getIgnoreRuleURL does not end with a slash +// So, calling functions will need to add it +func (xirs *IgnoreRuleService) getIgnoreRuleURL() string { + return clientutils.AddTrailingSlashIfNeeded(xirs.XrayDetails.GetUrl()) + ignoreRuleAPIURL +} + +// Delete will delete an ignore rule by id +func (xirs *IgnoreRuleService) Delete(ignoreRuleId string) error { + httpClientsDetails := xirs.XrayDetails.CreateHttpClientDetails() + artUtils.SetContentType("application/json", &httpClientsDetails.Headers) + + log.Info("Deleting ignore rule...") + resp, body, err := xirs.client.SendDelete(xirs.getIgnoreRuleURL()+"/"+ignoreRuleId, nil, &httpClientsDetails) + if err != nil { + return err + } + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusNoContent); err != nil { + return err + } + log.Debug("Xray response status:", resp.Status) + log.Info("Done deleting ignore rule.") + return nil +} + +// Create will create a new Xray ignore rule +// The function creates the ignore rule and returns its id which is recieved after post +func (xirs *IgnoreRuleService) Create(params utils.IgnoreRuleParams) (ignoreRuleId string, err error) { + ignoreRuleBody := utils.CreateIgnoreRuleBody(params) + content, err := json.Marshal(ignoreRuleBody) + if err != nil { + return "", errorutils.CheckError(err) + } + + httpClientsDetails := xirs.XrayDetails.CreateHttpClientDetails() + artUtils.SetContentType("application/json", &httpClientsDetails.Headers) + var url = xirs.getIgnoreRuleURL() + + log.Info("Create new ignore rule...") + resp, body, err := xirs.client.SendPost(url, content, &httpClientsDetails) + if err != nil { + return "", err + } + + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusCreated); err != nil { + return "", err + } + log.Debug("Xray response status:", resp.Status) + + ignoreRuleId, err = getIgnoreRuleIdFromBody(body) + if err != nil { + return "", err + } + + log.Info("Done creating ignore rule.") + log.Debug("Ignore rule id is: ", ignoreRuleId) + + return ignoreRuleId, nil +} + +func getIgnoreRuleIdFromBody(body []byte) (string, error) { + str := string(body) + + re := regexp.MustCompile(`id:\s*([a-f0-9-]+)`) + match := re.FindStringSubmatch(str) + + if len(match) <= 1 { + return "", errorutils.CheckErrorf("couldn't find id for ignore rule in str: %s", str) + } + + return match[1], nil +} + +// Get retrieves the details about an Xray ignore rule by its id +// It will error if the ignore rule id can't be found. +func (xirs *IgnoreRuleService) Get(ignoreRuleId string) (ignoreRuleResp *utils.IgnoreRuleParams, err error) { + httpClientsDetails := xirs.XrayDetails.CreateHttpClientDetails() + log.Info(fmt.Sprintf("Getting ignore rule '%s'...", ignoreRuleId)) + resp, body, _, err := xirs.client.SendGet(xirs.getIgnoreRuleURL()+"/"+ignoreRuleId, true, &httpClientsDetails) + ignoreRule := &utils.IgnoreRuleBody{} + + if err != nil { + return &utils.IgnoreRuleParams{}, err + } + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil { + log.Debug("Xray response:", string(body), resp.Status) + return &utils.IgnoreRuleParams{}, err + } + + if err = json.Unmarshal(body, ignoreRule); err != nil { + return &utils.IgnoreRuleParams{}, errorutils.CheckErrorf("failed unmarshalling %s for ignore rule %s", string(body), ignoreRuleId) + } + + log.Debug("Xray response status:", resp.Status) + log.Info("Done getting ignore rule.") + + return &ignoreRule.IgnoreRuleParams, nil +} diff --git a/xray/services/utils/ignorerulebody.go b/xray/services/utils/ignorerulebody.go new file mode 100644 index 000000000..4b465a162 --- /dev/null +++ b/xray/services/utils/ignorerulebody.go @@ -0,0 +1,65 @@ +package utils + +import "time" + +type IgnoreRuleParams struct { + Notes string `json:"notes"` + ExpiresAt time.Time `json:"expires_at,omitempty"` + IgnoreFilters IgnoreFilters `json:"ignore_filters"` +} + +type IgnoreRuleBody struct { + Id string `json:"id,omitempty"` + Author string `json:"author,omitempty"` + Created time.Time `json:"created,omitempty"` + IsExpired bool `json:"is_expired,omitempty"` + IgnoreRuleParams +} + +type IgnoreFilters struct { + Vulnerabilities []string `json:"vulnerabilities,omitempty"` + Licenses []string `json:"licenses,omitempty"` + CVEs []string `json:"cves,omitempty"` + Policies []string `json:"policies,omitempty"` + Watches []string `json:"watches,omitempty"` + DockerLayers []string `json:"docker-layers,omitempty"` + OperationalRisks []string `json:"operational_risk,omitempty"` + Exposures []ExposuresFilterName `json:"exposures,omitempty"` + ReleaseBundles []IgnoreFilterNameVersion `json:"release-bundles,omitempty"` + Builds []IgnoreFilterNameVersion `json:"builds,omitempty"` + Components []IgnoreFilterNameVersion `json:"components,omitempty"` + Artifacts []IgnoreFilterNameVersionPath `json:"artifacts,omitempty"` +} + +type IgnoreFilterNameVersion struct { + Name string `json:"name"` + Version string `json:"version,omitempty"` +} + +type IgnoreFilterNameVersionPath struct { + IgnoreFilterNameVersion + Path string `json:"path,omitempty"` +} + +type ExposuresFilterName struct { + Catagories []ExposuresCatagories `json:"catagories,omitempty"` + Scanners []string `json:"scanners,omitempty"` + FilePath []string `json:"file_path,omitempty"` +} + +type ExposuresCatagories struct { + Secrets bool `json:"secrets,omitempty"` + Services bool `json:"services,omitempty"` + Applications bool `json:"applications,omitempty"` + Iac bool `json:"iac,omitempty"` +} + +func NewIgnoreRuleParams() IgnoreRuleParams { + return IgnoreRuleParams{} +} + +func CreateIgnoreRuleBody(ignoreRuleParams IgnoreRuleParams) IgnoreRuleBody { + return IgnoreRuleBody{ + IgnoreRuleParams: ignoreRuleParams, + } +}