github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cve/anchore.go (about)

     1  package cve
     2  
     3  import (
     4  	"encoding/json"
     5  	"io/ioutil"
     6  	"net/http"
     7  
     8  	"fmt"
     9  
    10  	"github.com/jenkins-x/jx-api/pkg/client/clientset/versioned"
    11  	"github.com/olli-ai/jx/v2/pkg/auth"
    12  	"github.com/olli-ai/jx/v2/pkg/table"
    13  	"github.com/olli-ai/jx/v2/pkg/util"
    14  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/client-go/kubernetes"
    16  )
    17  
    18  const (
    19  	getVulnerabilitiesByImageID     = "/images/by_id/%s/vuln/%s"
    20  	getVulnerabilitiesByImageDigest = "/images/%s"
    21  	vulnerabilityType               = "os"
    22  	GetImages                       = "/images"
    23  )
    24  
    25  type result interface {
    26  }
    27  
    28  type VulnerabilityList struct {
    29  	ImageDigest     string
    30  	Vulnerabilities []Vulnerability
    31  }
    32  
    33  type Vulnerability struct {
    34  	Fix      string
    35  	Package  string
    36  	Severity string
    37  	URL      string
    38  	Vuln     string
    39  }
    40  
    41  type Image struct {
    42  	AnalysisStatus string        `json:"analysis_status,omitempty"`
    43  	ImageDetails   []ImageDetail `json:"image_detail,omitempty"`
    44  }
    45  
    46  type ImageDetail struct {
    47  	Registry string
    48  	Repo     string
    49  	Tag      string
    50  	ImageId  string
    51  	Fulltag  string
    52  }
    53  
    54  // AnchoreProvider implements CVEProvider interface for anchore.io
    55  type AnchoreProvider struct {
    56  	Client    *http.Client
    57  	BasicAuth string
    58  	BaseURL   string
    59  }
    60  
    61  func NewAnchoreProvider(server *auth.AuthServer, user *auth.UserAuth) (CVEProvider, error) {
    62  
    63  	basicAuth := util.BasicAuth(user.Username, user.Password)
    64  
    65  	provider := AnchoreProvider{
    66  		BaseURL:   server.URL,
    67  		BasicAuth: basicAuth,
    68  		Client:    http.DefaultClient,
    69  	}
    70  
    71  	return &provider, nil
    72  }
    73  
    74  func (a AnchoreProvider) GetImageVulnerabilityTable(jxClient versioned.Interface, client kubernetes.Interface, table *table.Table, query CVEQuery) error {
    75  
    76  	var err error
    77  	var vList VulnerabilityList
    78  	var imageIDs []string
    79  
    80  	if query.ImageID != "" {
    81  		var vList VulnerabilityList
    82  		subPath := fmt.Sprintf(getVulnerabilitiesByImageID, query.ImageID, vulnerabilityType)
    83  
    84  		err = a.AnchoreGet(subPath, &vList)
    85  		if err != nil {
    86  			return fmt.Errorf("error getting vulnerabilities for image %s: %v", query.ImageID, err)
    87  		}
    88  
    89  		return a.addVulnerabilitiesTableRows(table, &vList)
    90  	}
    91  
    92  	if query.Environment != "" {
    93  
    94  		var vList VulnerabilityList
    95  		// list pods in the namespace
    96  		podList, err := client.CoreV1().Pods(query.TargetNamespace).List(meta_v1.ListOptions{})
    97  		if err != nil {
    98  			return err
    99  		}
   100  		// if they have the annotation add the value to a list
   101  		for _, p := range podList.Items {
   102  			if p.Annotations[AnnotationCVEImageId] != "" {
   103  				imageIDs = append(imageIDs, p.Annotations[AnnotationCVEImageId])
   104  			}
   105  		}
   106  		// loop over the list and get the CVEs for each, adding the rows
   107  		err = a.getCVEsFromImageList(table, &vList, imageIDs)
   108  		if err != nil {
   109  			return err
   110  		}
   111  	}
   112  
   113  	// see if we can match images using an image name and optional version
   114  	if query.ImageID == "" {
   115  		// if we have an image name then lets try and match image id(s)
   116  		if query.ImageName != "" {
   117  
   118  			var images []Image
   119  			subPath := fmt.Sprintf(GetImages)
   120  
   121  			err = a.AnchoreGet(subPath, &images)
   122  			if err != nil {
   123  				return fmt.Errorf("error getting images %v", err)
   124  			}
   125  
   126  			for _, image := range images {
   127  				for _, d := range image.ImageDetails {
   128  					if d.Repo == query.ImageName {
   129  						// if user has provided a version and it doesn't match lets skip this image
   130  						if query.Vesion != "" && query.Vesion != d.Tag {
   131  							continue
   132  						}
   133  						imageIDs = append(imageIDs, d.ImageId)
   134  					}
   135  				}
   136  			}
   137  			if len(imageIDs) > 0 {
   138  				err = a.getCVEsFromImageList(table, &vList, imageIDs)
   139  				if err != nil {
   140  					return err
   141  				}
   142  			} else {
   143  				return fmt.Errorf("no matching images found for ImageName %s and Vesion %s", query.ImageName, query.Vesion)
   144  			}
   145  		}
   146  	} else {
   147  		return fmt.Errorf("choose an image name, an optinal version or anchore image id to find vulnerabilities")
   148  	}
   149  
   150  	return nil
   151  
   152  }
   153  
   154  // AnchoreGet get command
   155  func (a AnchoreProvider) AnchoreGet(subPath string, rs result) error {
   156  
   157  	url := fmt.Sprintf("%s%s", a.BaseURL, subPath)
   158  	req, err := http.NewRequest("GET", url, nil)
   159  	req.Header.Add("Authorization", "Basic "+a.BasicAuth)
   160  
   161  	resp, err := a.Client.Do(req)
   162  	if err != nil {
   163  		return fmt.Errorf("error getting vulnerabilities from anchore engine %v", err)
   164  	}
   165  
   166  	if resp.StatusCode != 200 {
   167  		return fmt.Errorf("error response getting vulnerabilities from anchore engine: %s", resp.Status)
   168  	}
   169  
   170  	data, err := ioutil.ReadAll(resp.Body)
   171  	err = json.Unmarshal(data, &rs)
   172  	if err != nil {
   173  		return fmt.Errorf("error unmarshalling %v", err)
   174  	}
   175  	return nil
   176  }
   177  
   178  func (a AnchoreProvider) addVulnerabilitiesTableRows(table *table.Table, vList *VulnerabilityList) error {
   179  
   180  	var image []Image
   181  	subPath := fmt.Sprintf(getVulnerabilitiesByImageDigest, vList.ImageDigest)
   182  
   183  	err := a.AnchoreGet(subPath, &image)
   184  	if err != nil {
   185  		return fmt.Errorf("error getting image for image digest %s: %v", vList.ImageDigest, err)
   186  	}
   187  	// TODO sort vList on severity and version?
   188  
   189  	for _, v := range vList.Vulnerabilities {
   190  		var sev string
   191  		switch v.Severity {
   192  		case "High":
   193  			sev = util.ColorError(v.Severity)
   194  		case "Medium":
   195  			sev = util.ColorWarning(v.Severity)
   196  		case "Low":
   197  			sev = util.ColorStatus(v.Severity)
   198  		}
   199  		table.AddRow(image[0].ImageDetails[0].Fulltag, sev, v.Vuln, v.URL, v.Package, v.Fix)
   200  	}
   201  	return nil
   202  }
   203  
   204  func (a AnchoreProvider) getCVEsFromImageList(table *table.Table, vList *VulnerabilityList, ids []string) error {
   205  	for _, imageID := range ids {
   206  		subPath := fmt.Sprintf(getVulnerabilitiesByImageID, imageID, vulnerabilityType)
   207  
   208  		err := a.AnchoreGet(subPath, &vList)
   209  		if err != nil {
   210  			return fmt.Errorf("error getting vulnerabilities for image %s: %v", imageID, err)
   211  		}
   212  
   213  		err = a.addVulnerabilitiesTableRows(table, vList)
   214  		if err != nil {
   215  			return fmt.Errorf("error building vulnerabilities table for image digest %s: %v", vList.ImageDigest, err)
   216  		}
   217  	}
   218  	return nil
   219  }