1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00
sap-jenkins-library/pkg/whitesource/whitesource.go
Jordan Levin 34967c502c
Whitesource scan (MVP) (#1658)
* Whitesource MVP for Gradle, Golang, and NPM/Yarn

* Refactoring

* Refactor and cleanup, better error checking

* publish stepResults, use pkg/versioning, bubble up errors, add gomod versioning support

* Run gofmt and cleanup comments

* Resolve PR comments

* Update resources/metadata/whitesource.yaml

Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>

* Only determine project coordinates if they are missing

Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>

* Gradle versioning artifact

* fix gradle artifact version regexp and refactor

* Fix token extraction from output buffer

* Fix some issues with pip and jsonfile versioning logic

* Remove useless spacing

* Remove unnecessary test file and fix naming style for JSONDescriptor

* Automatically download wss-unified-agent if file does not exist

* adds downloadVulnerabilityReport, checkSecurityViolations, minor refactoring

* adds config.ReportDirectoryName, improves readability

* Version-wide reporting for vulnerabilities and list of libraries.

* Refactor and improve build accuracy

* fix sed command

* Add includes file pattern config option

* Adds --exclude command line flag

* run go mod tidy and regenerate step framework

* Fix unit tests

* revert changes

* poll project status before downloading reports

* merge with master

* go mod tidy, go fmt, and fix whitesource unit test

* sync go.mod

* sync go.mod again

Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>
Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
2020-07-01 07:54:13 +02:00

446 lines
12 KiB
Go

package whitesource
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/pkg/errors"
)
// Product defines a WhiteSource product with name and token
type Product struct {
Name string `json:"name"`
Token string `json:"token"`
CreationDate string `json:"creationDate,omitempty"`
LastUpdateDate string `json:"lastUpdatedDate,omitempty"`
}
// Alert
type Alert struct {
Vulnerability Vulnerability `json:"vulnerability"`
Library Library `json:"library,omitempty"`
Project string `json:"project,omitempty"`
CreationDate string `json:"creation_date,omitempty"`
}
// Library
type Library struct {
Name string `json:"name,omitempty"`
Filename string `json:"filename,omitempty"`
Version string `json:"version,omitempty"`
Project string `json:"project,omitempty"`
}
// Vulnerability
type Vulnerability struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Level string `json:"level,omitempty"`
Description string `json:"description,omitempty"`
Severity string `json:"severity,omitempty"`
CVSS3Severity string `json:"cvss3_severity,omitempty"`
CVSS3Score float64 `json:"cvss3_score,omitempty"`
Score float64 `json:"score,omitempty"`
FixResolutionText string `json:"fixResolutionText,omitempty"`
PublishDate string `json:"publishDate,omitempty"`
}
// Project defines a WhiteSource project with name and token
type Project struct {
ID int64 `json:"id"`
Name string `json:"name"`
PluginName string `json:"pluginName"`
Token string `json:"token"`
UploadedBy string `json:"uploadedBy"`
CreationDate string `json:"creationDate,omitempty"`
LastUpdateDate string `json:"lastUpdatedDate,omitempty"`
}
// Request defines a request object to be sent to the WhiteSource system
type Request struct {
RequestType string `json:"requestType,omitempty"`
UserKey string `json:"userKey,omitempty"`
ProductToken string `json:"productToken,omitempty"`
ProductName string `json:"productName,omitempty"`
ProjectToken string `json:"projectToken,omitempty"`
OrgToken string `json:"orgToken,omitempty"`
Format string `json:"format,omitempty"`
}
// System defines a WhiteSource system including respective tokens (e.g. org token, user token)
type System struct {
HTTPClient piperhttp.Sender
OrgToken string
ServerURL string
UserToken string
}
// NewSystem constructs a new system instance
func NewSystem(serverURL, orgToken, userToken string) *System {
return &System{
ServerURL: serverURL,
OrgToken: orgToken,
UserToken: userToken,
HTTPClient: &piperhttp.Client{},
}
}
// GetProductsMetaInfo retrieves meta information for all WhiteSource products a user has access to
func (s *System) GetProductsMetaInfo() ([]Product, error) {
wsResponse := struct {
ProductVitals []Product `json:"productVitals"`
}{
ProductVitals: []Product{},
}
req := Request{
RequestType: "getOrganizationProductVitals",
}
respBody, err := s.sendRequest(req)
if err != nil {
return wsResponse.ProductVitals, errors.Wrap(err, "WhiteSource request failed")
}
err = json.Unmarshal(respBody, &wsResponse)
if err != nil {
return wsResponse.ProductVitals, errors.Wrap(err, "failed to parse WhiteSource response")
}
return wsResponse.ProductVitals, nil
}
// GetMetaInfoForProduct retrieves meta information for a specific WhiteSource product
func (s *System) GetMetaInfoForProduct(productName string) (Product, error) {
products, err := s.GetProductsMetaInfo()
if err != nil {
return Product{}, errors.Wrap(err, "failed to retrieve WhiteSource products")
}
for _, p := range products {
if p.Name == productName {
return p, nil
}
}
return Product{}, fmt.Errorf("product '%v' not found in WhiteSource", productName)
}
// GetProjectsMetaInfo retrieves meta information for a specific WhiteSource product
func (s *System) GetProjectsMetaInfo(productToken string) ([]Project, error) {
wsResponse := struct {
ProjectVitals []Project `json:"projectVitals"`
}{
ProjectVitals: []Project{},
}
req := Request{
RequestType: "getProductProjectVitals",
ProductToken: productToken,
}
respBody, err := s.sendRequest(req)
if err != nil {
return nil, errors.Wrap(err, "WhiteSource request failed")
}
err = json.Unmarshal(respBody, &wsResponse)
if err != nil {
return nil, errors.Wrap(err, "failed to parse WhiteSource response")
}
return wsResponse.ProjectVitals, nil
}
// GetProjectToken returns the project token for a project with a given name
func (s *System) GetProjectToken(productToken, projectName string) (string, error) {
var token string
project, err := s.GetProjectByName(productToken, projectName)
if err != nil {
return "", err
}
// returns a nil token and no error if not found
if project != nil {
token = project.Token
}
return token, nil
}
// GetProjectVitals returns project meta info given a project token
func (s *System) GetProjectVitals(projectToken string) (*Project, error) {
wsResponse := struct {
ProjectVitals []Project `json:"projectVitals"`
}{
ProjectVitals: []Project{},
}
req := Request{
RequestType: "getProjectVitals",
ProjectToken: projectToken,
}
respBody, err := s.sendRequest(req)
if err != nil {
return nil, errors.Wrap(err, "WhiteSource request failed")
}
err = json.Unmarshal(respBody, &wsResponse)
if err != nil {
return nil, errors.Wrap(err, "failed to parse WhiteSource response")
}
return &wsResponse.ProjectVitals[0], nil
}
// GetProjectByName returns the finds and returns a project by name
func (s *System) GetProjectByName(productToken, projectName string) (*Project, error) {
var project *Project
projects, err := s.GetProjectsMetaInfo(productToken)
if err != nil {
return nil, errors.Wrap(err, "failed to retrieve WhiteSource project meta info")
}
for _, proj := range projects {
if projectName == proj.Name {
project = &proj
break
}
}
// returns a nil project and no error if no project exists with projectName
return project, nil
}
// GetProjectsByIDs: get all project tokens given a list of project ids
func (s *System) GetProjectsByIDs(productToken string, projectIDs []int64) ([]Project, error) {
var projectsMatched []Project
projects, err := s.GetProjectsMetaInfo(productToken)
if err != nil {
return nil, errors.Wrap(err, "failed to retrieve WhiteSource project meta info")
}
for _, project := range projects {
for _, projectID := range projectIDs {
if projectID == project.ID {
projectsMatched = append(projectsMatched, project)
}
}
}
return projectsMatched, nil
}
// GetProjectTokens returns the project tokens matching a given a slice of project names
func (s *System) GetProjectTokens(productToken string, projectNames []string) ([]string, error) {
projectTokens := []string{}
projects, err := s.GetProjectsMetaInfo(productToken)
if err != nil {
return nil, errors.Wrap(err, "failed to retrieve WhiteSource project meta info")
}
for _, project := range projects {
for _, projectName := range projectNames {
if projectName == project.Name {
projectTokens = append(projectTokens, project.Token)
}
}
}
return projectTokens, nil
}
// GetProductName returns the product name for a given product token
func (s *System) GetProductName(productToken string) (string, error) {
wsResponse := struct {
ProductTags []Product `json:"productTags"`
}{
ProductTags: []Product{},
}
req := Request{
RequestType: "getProductTags",
ProductToken: productToken,
}
respBody, err := s.sendRequest(req)
if err != nil {
return "", errors.Wrap(err, "WhiteSource request failed")
}
err = json.Unmarshal(respBody, &wsResponse)
if err != nil {
return "", errors.Wrap(err, "failed to parse WhiteSource response")
}
if len(wsResponse.ProductTags) > 0 {
return wsResponse.ProductTags[0].Name, nil
}
return "", nil
}
// GetProjectRiskReport
func (s *System) GetProjectRiskReport(projectToken string) ([]byte, error) {
req := Request{
RequestType: "getProjectRiskReport",
ProjectToken: projectToken,
}
respBody, err := s.sendRequest(req)
if err != nil {
return nil, errors.Wrap(err, "WhiteSource getProjectRiskReport request failed")
}
return respBody, nil
}
// GetProjectVulnerabilityReport
func (s *System) GetProjectVulnerabilityReport(projectToken string, format string) ([]byte, error) {
req := Request{
RequestType: "getProjectVulnerabilityReport",
ProjectToken: projectToken,
Format: format,
}
respBody, err := s.sendRequest(req)
if err != nil {
return nil, errors.Wrap(err, "WhiteSource getProjectVulnerabilityReport request failed")
}
return respBody, nil
}
// GetOrganizationProductVitals
func (s *System) GetOrganizationProductVitals() ([]Product, error) {
wsResponse := struct {
ProductVitals []Product `json:"productVitals"`
}{
ProductVitals: []Product{},
}
req := Request{
RequestType: "getOrganizationProductVitals",
}
respBody, err := s.sendRequest(req)
if err != nil {
return nil, errors.Wrap(err, "WhiteSource request failed")
}
err = json.Unmarshal(respBody, &wsResponse)
if err != nil {
return nil, errors.Wrap(err, "failed to parse WhiteSource response")
}
return wsResponse.ProductVitals, nil
}
// GetProductByName
func (s *System) GetProductByName(productName string) (*Product, error) {
var product Product
products, err := s.GetOrganizationProductVitals()
if err != nil {
return nil, errors.Wrap(err, "failed to getOrganizationProductVitals")
}
for _, prod := range products {
if prod.Name == productName {
product = prod
}
}
// returns nil, nil if no product was found
return &product, nil
}
// GetProjectAlerts
func (s *System) GetProjectAlerts(projectToken string) ([]Alert, error) {
wsResponse := struct {
Alerts []Alert `json:"alerts"`
}{
Alerts: []Alert{},
}
req := Request{
RequestType: "getProjectAlerts",
ProjectToken: projectToken,
}
respBody, err := s.sendRequest(req)
if err != nil {
return nil, errors.Wrap(err, "WhiteSource request failed")
}
err = json.Unmarshal(respBody, &wsResponse)
if err != nil {
return nil, errors.Wrap(err, "failed to parse WhiteSource response")
}
return wsResponse.Alerts, nil
}
// GetProjectLibraryLocations
func (s *System) GetProjectLibraryLocations(projectToken string) ([]Library, error) {
wsResponse := struct {
Libraries []Library `json:"libraryLocations"`
}{
Libraries: []Library{},
}
req := Request{
RequestType: "getProjectLibraryLocations",
ProjectToken: projectToken,
}
respBody, err := s.sendRequest(req)
if err != nil {
return nil, errors.Wrap(err, "WhiteSource request failed")
}
err = json.Unmarshal(respBody, &wsResponse)
if err != nil {
return nil, errors.Wrap(err, "failed to parse WhiteSource response")
}
return wsResponse.Libraries, nil
}
func (s *System) sendRequest(req Request) ([]byte, error) {
var responseBody []byte
if req.UserKey == "" {
req.UserKey = s.UserToken
}
if req.OrgToken == "" {
req.OrgToken = s.OrgToken
}
body, err := json.Marshal(req)
if err != nil {
return responseBody, errors.Wrap(err, "failed to create WhiteSource request")
}
log.Entry().Debug(string(body))
headers := http.Header{}
headers.Add("Content-Type", "application/json")
response, err := s.HTTPClient.SendRequest(http.MethodPost, s.ServerURL, bytes.NewBuffer(body), headers, nil)
if err != nil {
return responseBody, errors.Wrap(err, "failed to send request to WhiteSource")
}
responseBody, err = ioutil.ReadAll(response.Body)
if err != nil {
return responseBody, errors.Wrap(err, "failed to read WhiteSource response")
}
return responseBody, nil
}