|
| 1 | +// |
| 2 | +// Author:: Salim Afiune Maya (<[email protected]>) |
| 3 | +// Copyright:: Copyright 2020, Lacework Inc. |
| 4 | +// License:: Apache License, Version 2.0 |
| 5 | +// |
| 6 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | +// you may not use this file except in compliance with the License. |
| 8 | +// You may obtain a copy of the License at |
| 9 | +// |
| 10 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +// |
| 12 | +// Unless required by applicable law or agreed to in writing, software |
| 13 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | +// See the License for the specific language governing permissions and |
| 16 | +// limitations under the License. |
| 17 | +// |
| 18 | + |
| 19 | +package api |
| 20 | + |
| 21 | +import ( |
| 22 | + "fmt" |
| 23 | + "strings" |
| 24 | +) |
| 25 | + |
| 26 | +// VulnerabilitiesService is a service that interacts with the vulnerabilities |
| 27 | +// endpoints from the Lacework Server |
| 28 | +type VulnerabilitiesService struct { |
| 29 | + client *Client |
| 30 | +} |
| 31 | + |
| 32 | +// Scan triggers a vulnerability scan to the provider registry, repository, and |
| 33 | +// tag provided. This function calls the underlaying API endpoint that assumes |
| 34 | +// that the container repository has been already integrated with the platform. |
| 35 | +func (svc *VulnerabilitiesService) Scan(registry, repository, tagOrHash string) ( |
| 36 | + response vulScanResponse, |
| 37 | + err error, |
| 38 | +) { |
| 39 | + err = svc.client.RequestEncoderDecoder("POST", |
| 40 | + apiVulnerabilitiesScan, |
| 41 | + vulScanRequest{registry, repository, tagOrHash}, |
| 42 | + &response, |
| 43 | + ) |
| 44 | + return |
| 45 | +} |
| 46 | + |
| 47 | +type vulScanRequest struct { |
| 48 | + Registry string `json:"registry"` |
| 49 | + Repository string `json:"repository"` |
| 50 | + Tag string `json:"tag"` |
| 51 | +} |
| 52 | + |
| 53 | +type vulScanResponse struct { |
| 54 | + Data struct { |
| 55 | + Status string `json:"status"` |
| 56 | + RequestID string `json:"requestId"` |
| 57 | + } `json:"data"` |
| 58 | + Ok bool `json:"ok"` |
| 59 | + Message string `json:"message"` |
| 60 | +} |
| 61 | + |
| 62 | +func (svc *VulnerabilitiesService) ScanStatus(requestID string) ( |
| 63 | + response vulScanStatusResponse, |
| 64 | + err error, |
| 65 | +) { |
| 66 | + apiPath := fmt.Sprintf(apiVulnerabilitiesScanStatus, requestID) |
| 67 | + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) |
| 68 | + return |
| 69 | +} |
| 70 | + |
| 71 | +type vulScanStatusResponse struct { |
| 72 | + Data VulContainerReport `json:"data"` |
| 73 | + Ok bool `json:"ok"` |
| 74 | + Message string `json:"message"` |
| 75 | +} |
| 76 | + |
| 77 | +func (scan *vulScanStatusResponse) CheckStatus() string { |
| 78 | + if !scan.Ok { |
| 79 | + return fmt.Sprintf("there is a problem with the vulnerability scan: %s", scan.Message) |
| 80 | + } |
| 81 | + |
| 82 | + if scan.Data.Status != "" { |
| 83 | + // @afiune as far as I can see, the three status we could have are: |
| 84 | + // * Scanning |
| 85 | + // * Failed |
| 86 | + // * NotFound |
| 87 | + // |
| 88 | + // Where is "Success"? Not here. Why? |
| 89 | + return scan.Data.Status |
| 90 | + } |
| 91 | + |
| 92 | + // If the scan is not running, that means, it completed running, now the |
| 93 | + // status of the scan changes to be stored in "ScanStatus" :sadpanda: |
| 94 | + if scan.Data.ScanStatus != "" { |
| 95 | + return scan.Data.ScanStatus |
| 96 | + } |
| 97 | + |
| 98 | + return "Unknown" |
| 99 | +} |
| 100 | + |
| 101 | +func (svc *VulnerabilitiesService) ReportFromID(imageID string) ( |
| 102 | + response VulContainerReportResponse, |
| 103 | + err error, |
| 104 | +) { |
| 105 | + apiPath := fmt.Sprintf(apiVulnerabilitiesReportFromID, imageID) |
| 106 | + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) |
| 107 | + return |
| 108 | +} |
| 109 | + |
| 110 | +func (svc *VulnerabilitiesService) ReportFromDigest(imageDigest string) ( |
| 111 | + response VulContainerReportResponse, |
| 112 | + err error, |
| 113 | +) { |
| 114 | + apiPath := fmt.Sprintf(apiVulnerabilitiesReportFromDigest, imageDigest) |
| 115 | + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) |
| 116 | + return |
| 117 | +} |
| 118 | + |
| 119 | +type VulContainerReportResponse struct { |
| 120 | + Data VulContainerReport `json:"data"` |
| 121 | + Ok bool `json:"ok"` |
| 122 | + Message string `json:"message"` |
| 123 | +} |
| 124 | + |
| 125 | +func (res *VulContainerReportResponse) CheckStatus() string { |
| 126 | + if !res.Ok { |
| 127 | + return fmt.Sprintf("there is a problem with the vulnerability report: %s", res.Message) |
| 128 | + } |
| 129 | + |
| 130 | + if res.Data.ScanStatus != "" { |
| 131 | + return res.Data.ScanStatus |
| 132 | + } |
| 133 | + |
| 134 | + if res.Data.Status != "" { |
| 135 | + return res.Data.Status |
| 136 | + } |
| 137 | + |
| 138 | + return "Unknown" |
| 139 | +} |
| 140 | + |
| 141 | +type VulContainerReport struct { |
| 142 | + TotalVulnerabilities int32 `json:"total_vulnerabilities"` |
| 143 | + CriticalVulnerabilities int32 `json:"critical_vulnerabilities"` |
| 144 | + HighVulnerabilities int32 `json:"high_vulnerabilities"` |
| 145 | + MediumVulnerabilities int32 `json:"medium_vulnerabilities"` |
| 146 | + LowVulnerabilities int32 `json:"low_vulnerabilities"` |
| 147 | + InfoVulnerabilities int32 `json:"info_vulnerabilities"` |
| 148 | + FixableVulnerabilities int32 `json:"fixable_vulnerabilities"` |
| 149 | + LastEvaluationTime string `json:"last_evaluation_time"` |
| 150 | + Image vulContainerImage `json:"image"` |
| 151 | + |
| 152 | + // @afiune these two parameters, Status and Message will appear when |
| 153 | + // the vulnerability scan is still running. ugh. why? |
| 154 | + Status string `json:"status,omitempty"` |
| 155 | + Message string `json:"message,omitempty"` |
| 156 | + |
| 157 | + // ScanStatus is a property that will appear when the vulnerability scan finished |
| 158 | + // running, this status indicates whether the scan finished successfully or not |
| 159 | + ScanStatus string `json:"scan_status"` |
| 160 | + |
| 161 | + // @afiune why we can't parse the time? |
| 162 | + //LastEvaluationTime time.Time `json:"last_evaluation_time"` |
| 163 | +} |
| 164 | + |
| 165 | +func (report *VulContainerReport) VulCountsTable() [][]string { |
| 166 | + return [][]string{ |
| 167 | + []string{"Critical", fmt.Sprint(report.CriticalVulnerabilities)}, |
| 168 | + []string{"Fixable", fmt.Sprint(report.FixableVulnerabilities)}, |
| 169 | + []string{"High", fmt.Sprint(report.HighVulnerabilities)}, |
| 170 | + []string{"Medium", fmt.Sprint(report.MediumVulnerabilities)}, |
| 171 | + []string{"Low", fmt.Sprint(report.LowVulnerabilities)}, |
| 172 | + []string{"Info", fmt.Sprint(report.InfoVulnerabilities)}, |
| 173 | + []string{"Total", fmt.Sprint(report.TotalVulnerabilities)}, |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +type vulContainerImage struct { |
| 178 | + ImageInfo vulContainerImageInfo `json:"image_info"` |
| 179 | + ImageLayers []vulContainerImageLayer `json:"image_layers"` |
| 180 | +} |
| 181 | + |
| 182 | +func (image *vulContainerImage) Table() [][]string { |
| 183 | + info := image.ImageInfo |
| 184 | + return [][]string{ |
| 185 | + []string{"ID", info.ImageID}, |
| 186 | + []string{"Digest", info.ImageDigest}, |
| 187 | + []string{"Registry", info.Registry}, |
| 188 | + []string{"Repository", info.Repository}, |
| 189 | + []string{"Size", ByteCountBinary(info.Size)}, |
| 190 | + []string{"Created At", info.CreatedTime}, |
| 191 | + []string{"Tags", strings.Join(info.Tags, ",")}, |
| 192 | + } |
| 193 | +} |
| 194 | + |
| 195 | +func ByteCountBinary(b int64) string { |
| 196 | + const unit = 1024 |
| 197 | + if b < unit { |
| 198 | + return fmt.Sprintf("%d B", b) |
| 199 | + } |
| 200 | + div, exp := int64(unit), 0 |
| 201 | + for n := b / unit; n >= unit; n /= unit { |
| 202 | + div *= unit |
| 203 | + exp++ |
| 204 | + } |
| 205 | + return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp]) |
| 206 | +} |
| 207 | + |
| 208 | +type vulContainerImageInfo struct { |
| 209 | + ImageDigest string `json:"image_digest"` |
| 210 | + ImageID string `json:"image_id"` |
| 211 | + Registry string `json:"registry"` |
| 212 | + Repository string `json:"repository"` |
| 213 | + CreatedTime string `json:"created_time"` |
| 214 | + Size int64 `json:"size"` |
| 215 | + Tags []string `json:"tags"` |
| 216 | +} |
| 217 | + |
| 218 | +type vulContainerImageLayer struct { |
| 219 | + Hash string `json:"hash"` |
| 220 | + CreatedBy string `json:"created_by"` |
| 221 | + Packages []vulContainerPackage `json:"packages"` |
| 222 | +} |
| 223 | + |
| 224 | +type vulContainerPackage struct { |
| 225 | + Name string `json:"name"` |
| 226 | + Namespace string `json:"namescape"` |
| 227 | + Version string `json:"version"` |
| 228 | + Vulnerabilities []containerVulnerability `json:"vulnerabilities"` |
| 229 | + |
| 230 | + // @afiune maybe these fields are host related information and not container |
| 231 | + FixAvailable string `json:"fix_available"` |
| 232 | + FixedVersion string `json:"fixed_version"` |
| 233 | + HostCount string `json:"host_count"` |
| 234 | + Severity string `json:"severity"` |
| 235 | + Status string `json:"status"` |
| 236 | + CveLink string `json:"cve_link"` |
| 237 | + CveScore string `json:"cve_score"` |
| 238 | + CvssV3Score string `json:"cvss_v3_score"` |
| 239 | + CvssV2Score string `json:"cvss_v2_score"` |
| 240 | + FirstSeenTime string `json:"first_seen_time"` |
| 241 | +} |
| 242 | + |
| 243 | +type containerVulnerability struct { |
| 244 | + Name string `json:"name"` |
| 245 | + Description string `json:"description"` |
| 246 | + Severity string `json:"severity"` |
| 247 | + Link string `json:"link"` |
| 248 | + FixVersion string `json:"fix_version"` |
| 249 | + Metadata map[string]interface{} `json:"metadata"` |
| 250 | +} |
0 commit comments