Sync Develop to Main (#4)

* Update Insight service API and client

* Add cli banner

* Show API errors from insight API

* Use standard error model

* Add reporting interface

* Update markdown template

* Add trials registration client

* Add trials registration support

* Add supported ecosystem filter to parsers

* Update OSV scanner

* Use table renderer for CEL filter output

* Rename filter opt to filter

* Add an opinionated console summary reporter

* Update README

* Update README

* Update README

* Add filter spec

* Update spec driven CEL filtering

* Add query workflow with docs

* Add secrets scan workflow
This commit is contained in:
Abhisek Datta 2023-02-03 12:30:48 +05:30 committed by GitHub
parent bde7df3507
commit a18c204b5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 2572 additions and 88 deletions

20
.github/workflows/secret_scan.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Secrets Scan
on:
pull_request:
branches:
- main
jobs:
trufflehog:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v2
with:
fetch-depth: '0'
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: main
head: HEAD

View File

@ -7,17 +7,28 @@ all: clean setup vet
oapi-codegen-install:
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.10.1
protoc-install:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
oapi-codegen:
oapi-codegen -package insightapi -generate types ./api/insights-v1.yml > ./gen/insightapi/insights.types.go
oapi-codegen -package insightapi -generate client ./api/insights-v1.yml > ./gen/insightapi/insights.client.go
oapi-codegen -package controlplane -generate types ./api/cp-v1-trials.yml > ./gen/controlplane/trials.types.go
oapi-codegen -package controlplane -generate client ./api/cp-v1-trials.yml > ./gen/controlplane/trials.client.go
protoc-codegen:
protoc -I ./api \
--go_out=./gen/filterinput \
--go_opt=paths=source_relative \
./api/filter_input_spec.proto
setup:
mkdir -p out gen/insightapi
mkdir -p out gen/insightapi gen/controlplane gen/filterinput
GO_CFLAGS=-X main.commit=$(GITCOMMIT) -X main.version=$(VERSION)
GO_LDFLAGS=-ldflags "-w $(GO_CFLAGS)"
vet: oapi-codegen
vet: oapi-codegen protoc-codegen
go build ${GO_LDFLAGS}
.PHONY: test

View File

@ -1,42 +1,104 @@
# vet : The dependency vetting tool
Tool for identifying software supply chain risks
# vet
`vet` is a tool for identifying risks in open source software supply chain. It
helps engineering and security teams to identify potential issues in their open
source dependencies and evaluate them against organizational policies.
## TL;DR
Build this repository
> Ensure `$(go env GOPATH)/bin` is in your `$PATH`
```bash
make oapi-codegen-install && make
```
Alternatively install using
Install using `go get`
```bash
go install github.com/safedep/vet@latest
```
Configure `vet` to use API Key to access [Insights API](#)
Alternatively, look at [Releases](https://github.com/safedep/vet/releases) for
a pre-built binary for your platform. [SLSA Provenance](https://slsa.dev/provenance/v0.1) is published
along with each binary release.
Get a trial API key for [Insights API](https://safedep.io/docs/concepts/raya-data-platform-overview) access
```bash
vet auth trial --email john.doe@example.com
```
> A time limited trial API key will be sent over email.
Configure `vet` to use API Key to access [Insights API](https://safedep.io/docs/concepts/raya-data-platform-overview)
```bash
vet auth configure
```
> Alternatively pass the API key as environment to skip configuration
> Insights API is used to enrich OSS packages with meta-data for rich query and policy
> decisions
Run `vet` to identify risks
```bash
vet scan
vet scan -D /path/to/repository
```
## Usage
### Configuration
Insights API Key can be passed at runtime using environment variable
or scan a specific (supported) package manifest
```bash
VET_INSIGHTS_API_KEY=... vet scan
vet scan --lockfiles /path/to/pom.xml
vet scan --lockfiles /path/to/requirements.txt
vet scan --lockfiles /path/to/package-lock.json
```
> Use `vet scan parsers` to list supported package manifest parsers
The default scan uses an opinionated [Console Reporter](#) which presents
a summary of findings per package manifest. Thats NOT about it. Read more for
expression based filtering and policy evaluation.
## Filtering
Find dependencies that seems not very popular
```bash
vet scan --lockfiles /path/to/pom.xml --report-console=false \
--filter='projects.exists(x, x.stars < 10)'
```
Find dependencies with a critical vulnerability
```bash
vet scan --lockfiles /path/to/pom.xml --report-console=false \
--filter='vulns.critical.exists_one(x, true)'
```
> Use filtering along with `query` command for offline slicing and dicing of
> enriched package manifests. Read [filtering guide](docs/filtering.md)
[Common Expressions Language](https://github.com/google/cel-spec) is used to
evaluate filters on packages. Learn more about [filtering with vet](docs/filtering.md).
Look at [filter input spec](api/filter_input_spec.proto) on attributes
available to the filter expression.
## Policy Evaluation
TODO
## FAQ
### How do I disable the stupid banner?
Set environment variable `VET_DISABLE_BANNER=1`
### Can I use this tool without an API Key for Insight Service?
Probably no. All useful data (enrichments) for a detected package comes from
a backend service. The service is rate limited with quotas to prevent abuse.
Look at `api/insights-v1.yml`. It contains the contract expected for Insights
API. You can perhaps consider rolling out your own to avoid dependency with our
backend.
## References
* https://github.com/google/osv-scanner

123
api/cp-v1-trials.yml Normal file
View File

@ -0,0 +1,123 @@
openapi: 3.0.2
info:
title: SafeDep Control Plane API for Trials Registration
contact:
name: SafeDep API
url: 'https://safedep.io'
description: |
Trials API provide a way for obtaining an API Key for data plane service access
using an Email Address. Trials is different from Registrations as the later
allows full access to the control plane while Trials is meant to allow access
only to a time bounded (expirable) API key for quick evaluation of tools.
version: 0.0.1
servers:
- url: 'https://{apiHost}/{apiBase}'
variables:
apiHost:
default: api.safedep.io
apiBase:
default: control-plane/v1
tags:
- name: Control Plane
description: Control Plane API
paths:
/trials:
post:
description: |
Register a trial user to obtain an expirable API Key. The API key will
be generated and sent to the user over Email to ensure validity and access
to the email by the requester. System defined limits will be applied to
maximum number of trial API keys that can be generated for an email.
operationId: registerTrialUser
tags:
- Control Plane
requestBody:
description: Trial registration request
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TrialRequest'
responses:
'201':
description: Successfully created an API key request
content:
application/json:
schema:
$ref: '#/components/schemas/TrialResponse'
'403':
description: Access to the API is denied
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'429':
description: Rate limit block
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'500':
description: Failed due to internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
components:
schemas:
ApiError:
type: object
properties:
message:
type: string
description: A descriptive message about the error meant for developer consumption
type:
type: string
description: An optional service or domain specific error group
enum:
- invalid_request
- operation_failed
- internal_error
code:
type: string
description: An error code identifying the error
enum:
- api_guard_invalid_credentials
- api_guard_rate_limit_exceeded
- api_guard_unauthorized
- api_guard_error
- app_generic_error
- app_security_error
- app_insufficient_parameters
- app_feature_not_enabled
- app_package_version_not_found
params:
type: object
description: Optional error specific attributes
additionalProperties:
type: object
properties:
key:
type: string
value:
type: string
TrialRequest:
type: object
properties:
email:
type: string
format: email
required:
- email
TrialResponse:
type: object
properties:
id:
type: string
minLength: 6
maxLength: 512
description: The ID of the trial registration request created in the system
expires_at:
type: string
format: date-time
description: The expiry time of the API key

View File

@ -0,0 +1,50 @@
syntax = "proto3";
/* Specifcations for filter input that can be used for query by CEL */
option go_package = "github.com/safedep/vet/gen/filterinput";
message Vulnerability {
string id = 1; // OSV ID
string cve = 2; // CVE ID
}
// Only hold vulnerability IDs
message Vulnerabilities {
repeated Vulnerability all = 1;
repeated Vulnerability critical = 2;
repeated Vulnerability high = 3;
repeated Vulnerability medium = 4;
repeated Vulnerability low = 5;
}
// OpenSSF Scorecard
message Scorecard {
map<string, float> scores = 1;
}
enum ProjectType {
UNKNOWN = 0;
GITHUB = 1;
}
message ProjectInfo {
string name = 1;
ProjectType type = 2;
int32 stars = 3;
int32 forks = 4;
int32 issues = 5;
}
message PackageVersion {
string ecosystem = 1;
string name = 2;
string version = 3;
}
message FilterInput {
PackageVersion pkg = 1;
Vulnerabilities vulns = 2;
Scorecard scorecard = 3;
repeated ProjectInfo projects = 4;
repeated string licenses = 5;
}

View File

@ -47,6 +47,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/PackageVersionInsight'
'403':
description: Access to the API is denied
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
'404':
description: Requested resource was not found
content:
@ -757,11 +763,63 @@ components:
type: integer
link:
type: string
PackageVulnerability:
type: object
description: |
Subset of OSV schema required to perform policy
decision by various tools
properties:
id:
type: string
description: Vulnerability identifier
summary:
type: string
description: Short summary of vulnerability
aliases:
type: array
items:
type: string
description: |
Alias identifiers of the same vulnerability in
other databases
related:
type: array
items:
type: string
description: |
Related vulnerability identifiers for similar issues
in
severities:
type: array
items:
type: object
properties:
type:
type: string
enum:
- UNSPECIFIED
- CVSS_V3
- CVSS_V2
score:
type: string
description: Type specific vulnerability score
risk:
type: string
enum:
- CRITICAL
- HIGH
- MEDIUM
- LOW
- UNKNOWN
description: Normalized risk rating computed from score
PackageVersionInsight:
type: object
properties:
package_version:
$ref: '#/components/schemas/PackageVersion'
package_current_version:
type: string
description: The latest version available for the package
projects:
type: array
items:
@ -778,3 +836,7 @@ components:
$ref: '#/components/schemas/PackageDependency'
scorecard:
$ref: '#/components/schemas/Scorecard'
vulnerabilities:
type: array
items:
$ref: '#/components/schemas/PackageVulnerability'

41
auth.go
View File

@ -12,7 +12,9 @@ import (
)
var (
authInsightApiBaseUrl string
authInsightApiBaseUrl string
authControlPlaneApiBaseUrl string
authTrialEmail string
)
func newAuthCommand() *cobra.Command {
@ -28,6 +30,7 @@ func newAuthCommand() *cobra.Command {
cmd.AddCommand(configureAuthCommand())
cmd.AddCommand(verifyAuthCommand())
cmd.AddCommand(trialsRegisterCommand())
return cmd
}
@ -46,6 +49,7 @@ func configureAuthCommand() *cobra.Command {
ApiUrl: authInsightApiBaseUrl,
ApiKey: string(key),
})
if err != nil {
panic(err)
}
@ -55,7 +59,7 @@ func configureAuthCommand() *cobra.Command {
},
}
cmd.Flags().StringVarP(&authInsightApiBaseUrl, "api", "", "https://api.safedep.io/insights/v1",
cmd.Flags().StringVarP(&authInsightApiBaseUrl, "api", "", auth.DefaultApiUrl(),
"Base URL of Insights API")
return cmd
@ -66,7 +70,7 @@ func verifyAuthCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "verify",
RunE: func(cmd *cobra.Command, args []string) error {
// Run auth.Verify()
fmt.Printf("Verify auth command is currently work in progress\n")
os.Exit(1)
return nil
},
@ -74,3 +78,34 @@ func verifyAuthCommand() *cobra.Command {
return cmd
}
func trialsRegisterCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "trial",
RunE: func(cmd *cobra.Command, args []string) error {
client := auth.NewTrialRegistrationClient(auth.TrialConfig{
Email: authTrialEmail,
ControlPlaneApiUrl: authControlPlaneApiBaseUrl,
})
res, err := client.Execute()
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Trial registration successful with Id:%s\n", res.Id)
fmt.Printf("Check your email (%s) for API key and usage instructions\n", authTrialEmail)
fmt.Printf("The trial API key will expire on %s\n", res.ExpiresAt.String())
return nil
},
}
cmd.Flags().StringVarP(&authTrialEmail, "email", "", "",
"Email address to use for sending trial API key")
cmd.Flags().StringVarP(&authControlPlaneApiBaseUrl, "control-plane", "",
auth.DefaultControlPlaneApiUrl(), "Base URL of Control Plane API for registrations")
return cmd
}

115
docs/filtering.md Normal file
View File

@ -0,0 +1,115 @@
# Filtering
Filter command helps solve the problem of visibility for OSS dependencies in an
application. To support various requirements, we adopt a generic [expressions
language](https://github.com/google/cel-spec) for flexible filtering.
## Input
Filter expressions work on packages (aka. dependencies) and evaluates to
a boolean result. The package is included in the results table if the
expression evaluates to `true`.
Filter expressions get the following input data to work with
| Variable | Content |
|-------------|-------------------------------------------------------------|
| `_` | The root variable, holding other variables |
| `vulns` | Holds a map of vulnerabiliteis by severity |
| `scorecard` | Holds OpenSSF scorecard |
| `projects` | Holds a list of source projects associated with the package |
| `licenses` | Holds a list of liceses in SPDX license code format |
## Expressions
Expressions are [CEL](https://github.com/google/cel-spec) statements. While
CEL internals are not required, an [introductory](https://github.com/google/cel-spec/blob/master/doc/intro.md)
knowledge of CEL will help formulating queries.
### Example Queries
| Description | Query |
|----------------------------------------------|---------------------------------------|
| Find packages with a critical vulnerability | `vulns.critical.exists(x, true)` |
| Find unmaintained packages as per OpenSSF SC | `scorecard.score["Maintenance"] == 0` |
| Find packages with low stars | `projects.exists(x, x.stars < 10)` |
| Find packages with GPL-2.0 license | `licenses.exists(x, x == "GPL-2.0")`
Refer to [scorecard checks](https://github.com/ossf/scorecard#checks-1) for
a list of checks available from OpenSSF Scorecards project.
## Query Workflow
Scanning a package manifest is a resource intensive process as it involves
enriching package metadata by queryin [Insights API](https://safedep.io/docs/concepts/raya-data-platform-overview).
However, for filtering and reporting may be done multiple times on the same
manifest. To speed up the process, we can dump the enriched data as JSON and
load the same for filtering and reporting.
Dump enriched JSON manifests to a directory (example)
```bash
vet scan --lockfile /path/to/package-lock.json --json-dump-dir /tmp/dump
vet scan -D /path/to/repository --json-dump-dir /tmp/dump-many
```
Load the enriched metadata for filtering and reporting
```bash
vet query --from /tmp/dump --report-console
vet query --from /tmp/dump --filter 'scorecard.score["Maintenance"] == 0'
```
## FAQ
### How does the filter input JSON look like?
```json
{
"pkg": {
"ecosystem": "npm",
"name": "lodash.camelcase",
"version": "4.3.0"
},
"vulns": {
"all": [],
"critical": [],
"high": [],
"medium": [],
"low": []
},
"scorecard": {
"scores": {
"Binary-Artifacts": 10,
"Branch-Protection": 0,
"CII-Best-Practices": 0,
"Code-Review": 8,
"Dangerous-Workflow": 10,
"Dependency-Update-Tool": 0,
"Fuzzing": 0,
"License": 10,
"Maintained": 0,
"Packaging": -1,
"Pinned-Dependencies": 9,
"SAST": 0,
"Security-Policy": 10,
"Signed-Releases": -1,
"Token-Permissions": 0,
"Vulnerabilities": 10
}
},
"projects": [
{
"name": "lodash/lodash",
"type": "GITHUB",
"stars": 55518,
"forks": 6787,
"issues": 464
}
],
"licenses": [
"MIT"
]
}
```

View File

@ -0,0 +1,297 @@
// Package controlplane provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.10.1 DO NOT EDIT.
package controlplane
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// RequestEditorFn is the function signature for the RequestEditor callback function
type RequestEditorFn func(ctx context.Context, req *http.Request) error
// Doer performs HTTP requests.
//
// The standard http.Client implements this interface.
type HttpRequestDoer interface {
Do(req *http.Request) (*http.Response, error)
}
// Client which conforms to the OpenAPI3 specification for this service.
type Client struct {
// The endpoint of the server conforming to this interface, with scheme,
// https://api.deepmap.com for example. This can contain a path relative
// to the server, such as https://api.deepmap.com/dev-test, and all the
// paths in the swagger spec will be appended to the server.
Server string
// Doer for performing requests, typically a *http.Client with any
// customized settings, such as certificate chains.
Client HttpRequestDoer
// A list of callbacks for modifying requests which are generated before sending over
// the network.
RequestEditors []RequestEditorFn
}
// ClientOption allows setting custom parameters during construction
type ClientOption func(*Client) error
// Creates a new Client, with reasonable defaults
func NewClient(server string, opts ...ClientOption) (*Client, error) {
// create a client with sane default values
client := Client{
Server: server,
}
// mutate client and add all optional params
for _, o := range opts {
if err := o(&client); err != nil {
return nil, err
}
}
// ensure the server URL always has a trailing slash
if !strings.HasSuffix(client.Server, "/") {
client.Server += "/"
}
// create httpClient, if not already present
if client.Client == nil {
client.Client = &http.Client{}
}
return &client, nil
}
// WithHTTPClient allows overriding the default Doer, which is
// automatically created using http.Client. This is useful for tests.
func WithHTTPClient(doer HttpRequestDoer) ClientOption {
return func(c *Client) error {
c.Client = doer
return nil
}
}
// WithRequestEditorFn allows setting up a callback function, which will be
// called right before sending the request. This can be used to mutate the request.
func WithRequestEditorFn(fn RequestEditorFn) ClientOption {
return func(c *Client) error {
c.RequestEditors = append(c.RequestEditors, fn)
return nil
}
}
// The interface specification for the client above.
type ClientInterface interface {
// RegisterTrialUser request with any body
RegisterTrialUserWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
RegisterTrialUser(ctx context.Context, body RegisterTrialUserJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
}
func (c *Client) RegisterTrialUserWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewRegisterTrialUserRequestWithBody(c.Server, contentType, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) RegisterTrialUser(ctx context.Context, body RegisterTrialUserJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewRegisterTrialUserRequest(c.Server, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
// NewRegisterTrialUserRequest calls the generic RegisterTrialUser builder with application/json body
func NewRegisterTrialUserRequest(server string, body RegisterTrialUserJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
return NewRegisterTrialUserRequestWithBody(server, "application/json", bodyReader)
}
// NewRegisterTrialUserRequestWithBody generates requests for RegisterTrialUser with any type of body
func NewRegisterTrialUserRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/trials")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", queryURL.String(), body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", contentType)
return req, nil
}
func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error {
for _, r := range c.RequestEditors {
if err := r(ctx, req); err != nil {
return err
}
}
for _, r := range additionalEditors {
if err := r(ctx, req); err != nil {
return err
}
}
return nil
}
// ClientWithResponses builds on ClientInterface to offer response payloads
type ClientWithResponses struct {
ClientInterface
}
// NewClientWithResponses creates a new ClientWithResponses, which wraps
// Client with return type handling
func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) {
client, err := NewClient(server, opts...)
if err != nil {
return nil, err
}
return &ClientWithResponses{client}, nil
}
// WithBaseURL overrides the baseURL.
func WithBaseURL(baseURL string) ClientOption {
return func(c *Client) error {
newBaseURL, err := url.Parse(baseURL)
if err != nil {
return err
}
c.Server = newBaseURL.String()
return nil
}
}
// ClientWithResponsesInterface is the interface specification for the client with responses above.
type ClientWithResponsesInterface interface {
// RegisterTrialUser request with any body
RegisterTrialUserWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterTrialUserResponse, error)
RegisterTrialUserWithResponse(ctx context.Context, body RegisterTrialUserJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterTrialUserResponse, error)
}
type RegisterTrialUserResponse struct {
Body []byte
HTTPResponse *http.Response
JSON201 *TrialResponse
JSON403 *ApiError
JSON429 *ApiError
JSON500 *ApiError
}
// Status returns HTTPResponse.Status
func (r RegisterTrialUserResponse) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r RegisterTrialUserResponse) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
// RegisterTrialUserWithBodyWithResponse request with arbitrary body returning *RegisterTrialUserResponse
func (c *ClientWithResponses) RegisterTrialUserWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterTrialUserResponse, error) {
rsp, err := c.RegisterTrialUserWithBody(ctx, contentType, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseRegisterTrialUserResponse(rsp)
}
func (c *ClientWithResponses) RegisterTrialUserWithResponse(ctx context.Context, body RegisterTrialUserJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterTrialUserResponse, error) {
rsp, err := c.RegisterTrialUser(ctx, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseRegisterTrialUserResponse(rsp)
}
// ParseRegisterTrialUserResponse parses an HTTP response from a RegisterTrialUserWithResponse call
func ParseRegisterTrialUserResponse(rsp *http.Response) (*RegisterTrialUserResponse, error) {
bodyBytes, err := ioutil.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &RegisterTrialUserResponse{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201:
var dest TrialResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON201 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403:
var dest ApiError
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON403 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429:
var dest ApiError
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON429 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500:
var dest ApiError
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON500 = &dest
}
return response, nil
}

View File

@ -0,0 +1,159 @@
// Package controlplane provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.10.1 DO NOT EDIT.
package controlplane
import (
"encoding/json"
"fmt"
"time"
openapi_types "github.com/deepmap/oapi-codegen/pkg/types"
)
// Defines values for ApiErrorCode.
const (
ApiErrorCodeApiGuardError ApiErrorCode = "api_guard_error"
ApiErrorCodeApiGuardInvalidCredentials ApiErrorCode = "api_guard_invalid_credentials"
ApiErrorCodeApiGuardRateLimitExceeded ApiErrorCode = "api_guard_rate_limit_exceeded"
ApiErrorCodeApiGuardUnauthorized ApiErrorCode = "api_guard_unauthorized"
ApiErrorCodeAppFeatureNotEnabled ApiErrorCode = "app_feature_not_enabled"
ApiErrorCodeAppGenericError ApiErrorCode = "app_generic_error"
ApiErrorCodeAppInsufficientParameters ApiErrorCode = "app_insufficient_parameters"
ApiErrorCodeAppPackageVersionNotFound ApiErrorCode = "app_package_version_not_found"
ApiErrorCodeAppSecurityError ApiErrorCode = "app_security_error"
)
// Defines values for ApiErrorType.
const (
ApiErrorTypeInternalError ApiErrorType = "internal_error"
ApiErrorTypeInvalidRequest ApiErrorType = "invalid_request"
ApiErrorTypeOperationFailed ApiErrorType = "operation_failed"
)
// ApiError defines model for ApiError.
type ApiError struct {
// An error code identifying the error
Code *ApiErrorCode `json:"code,omitempty"`
// A descriptive message about the error meant for developer consumption
Message *string `json:"message,omitempty"`
// Optional error specific attributes
Params *ApiError_Params `json:"params,omitempty"`
// An optional service or domain specific error group
Type *ApiErrorType `json:"type,omitempty"`
}
// An error code identifying the error
type ApiErrorCode string
// Optional error specific attributes
type ApiError_Params struct {
AdditionalProperties map[string]struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
} `json:"-"`
}
// An optional service or domain specific error group
type ApiErrorType string
// TrialRequest defines model for TrialRequest.
type TrialRequest struct {
Email openapi_types.Email `json:"email"`
}
// TrialResponse defines model for TrialResponse.
type TrialResponse struct {
// The expiry time of the API key
ExpiresAt *time.Time `json:"expires_at,omitempty"`
// The ID of the trial registration request created in the system
Id *string `json:"id,omitempty"`
}
// RegisterTrialUserJSONBody defines parameters for RegisterTrialUser.
type RegisterTrialUserJSONBody TrialRequest
// RegisterTrialUserJSONRequestBody defines body for RegisterTrialUser for application/json ContentType.
type RegisterTrialUserJSONRequestBody RegisterTrialUserJSONBody
// Getter for additional properties for ApiError_Params. Returns the specified
// element and whether it was found
func (a ApiError_Params) Get(fieldName string) (value struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
}, found bool) {
if a.AdditionalProperties != nil {
value, found = a.AdditionalProperties[fieldName]
}
return
}
// Setter for additional properties for ApiError_Params
func (a *ApiError_Params) Set(fieldName string, value struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
}) {
if a.AdditionalProperties == nil {
a.AdditionalProperties = make(map[string]struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
})
}
a.AdditionalProperties[fieldName] = value
}
// Override default JSON handling for ApiError_Params to handle AdditionalProperties
func (a *ApiError_Params) UnmarshalJSON(b []byte) error {
object := make(map[string]json.RawMessage)
err := json.Unmarshal(b, &object)
if err != nil {
return err
}
if len(object) != 0 {
a.AdditionalProperties = make(map[string]struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
})
for fieldName, fieldBuf := range object {
var fieldVal struct {
Key *string `json:"key,omitempty"`
Value *string `json:"value,omitempty"`
}
err := json.Unmarshal(fieldBuf, &fieldVal)
if err != nil {
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
}
a.AdditionalProperties[fieldName] = fieldVal
}
}
return nil
}
// Override default JSON handling for ApiError_Params to handle AdditionalProperties
func (a ApiError_Params) MarshalJSON() ([]byte, error) {
var err error
object := make(map[string]json.RawMessage)
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
if err != nil {
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
}
}
return json.Marshal(object)
}

View File

@ -0,0 +1,678 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v3.18.0
// source: filter_input_spec.proto
package filterinput
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ProjectType int32
const (
ProjectType_UNKNOWN ProjectType = 0
ProjectType_GITHUB ProjectType = 1
)
// Enum value maps for ProjectType.
var (
ProjectType_name = map[int32]string{
0: "UNKNOWN",
1: "GITHUB",
}
ProjectType_value = map[string]int32{
"UNKNOWN": 0,
"GITHUB": 1,
}
)
func (x ProjectType) Enum() *ProjectType {
p := new(ProjectType)
*p = x
return p
}
func (x ProjectType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ProjectType) Descriptor() protoreflect.EnumDescriptor {
return file_filter_input_spec_proto_enumTypes[0].Descriptor()
}
func (ProjectType) Type() protoreflect.EnumType {
return &file_filter_input_spec_proto_enumTypes[0]
}
func (x ProjectType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ProjectType.Descriptor instead.
func (ProjectType) EnumDescriptor() ([]byte, []int) {
return file_filter_input_spec_proto_rawDescGZIP(), []int{0}
}
type Vulnerability struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // OSV ID
Cve string `protobuf:"bytes,2,opt,name=cve,proto3" json:"cve,omitempty"` // CVE ID
}
func (x *Vulnerability) Reset() {
*x = Vulnerability{}
if protoimpl.UnsafeEnabled {
mi := &file_filter_input_spec_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Vulnerability) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Vulnerability) ProtoMessage() {}
func (x *Vulnerability) ProtoReflect() protoreflect.Message {
mi := &file_filter_input_spec_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Vulnerability.ProtoReflect.Descriptor instead.
func (*Vulnerability) Descriptor() ([]byte, []int) {
return file_filter_input_spec_proto_rawDescGZIP(), []int{0}
}
func (x *Vulnerability) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *Vulnerability) GetCve() string {
if x != nil {
return x.Cve
}
return ""
}
// Only hold vulnerability IDs
type Vulnerabilities struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
All []*Vulnerability `protobuf:"bytes,1,rep,name=all,proto3" json:"all,omitempty"`
Critical []*Vulnerability `protobuf:"bytes,2,rep,name=critical,proto3" json:"critical,omitempty"`
High []*Vulnerability `protobuf:"bytes,3,rep,name=high,proto3" json:"high,omitempty"`
Medium []*Vulnerability `protobuf:"bytes,4,rep,name=medium,proto3" json:"medium,omitempty"`
Low []*Vulnerability `protobuf:"bytes,5,rep,name=low,proto3" json:"low,omitempty"`
}
func (x *Vulnerabilities) Reset() {
*x = Vulnerabilities{}
if protoimpl.UnsafeEnabled {
mi := &file_filter_input_spec_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Vulnerabilities) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Vulnerabilities) ProtoMessage() {}
func (x *Vulnerabilities) ProtoReflect() protoreflect.Message {
mi := &file_filter_input_spec_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Vulnerabilities.ProtoReflect.Descriptor instead.
func (*Vulnerabilities) Descriptor() ([]byte, []int) {
return file_filter_input_spec_proto_rawDescGZIP(), []int{1}
}
func (x *Vulnerabilities) GetAll() []*Vulnerability {
if x != nil {
return x.All
}
return nil
}
func (x *Vulnerabilities) GetCritical() []*Vulnerability {
if x != nil {
return x.Critical
}
return nil
}
func (x *Vulnerabilities) GetHigh() []*Vulnerability {
if x != nil {
return x.High
}
return nil
}
func (x *Vulnerabilities) GetMedium() []*Vulnerability {
if x != nil {
return x.Medium
}
return nil
}
func (x *Vulnerabilities) GetLow() []*Vulnerability {
if x != nil {
return x.Low
}
return nil
}
// OpenSSF Scorecard
type Scorecard struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Scores map[string]float32 `protobuf:"bytes,1,rep,name=scores,proto3" json:"scores,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"fixed32,2,opt,name=value,proto3"`
}
func (x *Scorecard) Reset() {
*x = Scorecard{}
if protoimpl.UnsafeEnabled {
mi := &file_filter_input_spec_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Scorecard) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Scorecard) ProtoMessage() {}
func (x *Scorecard) ProtoReflect() protoreflect.Message {
mi := &file_filter_input_spec_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Scorecard.ProtoReflect.Descriptor instead.
func (*Scorecard) Descriptor() ([]byte, []int) {
return file_filter_input_spec_proto_rawDescGZIP(), []int{2}
}
func (x *Scorecard) GetScores() map[string]float32 {
if x != nil {
return x.Scores
}
return nil
}
type ProjectInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Type ProjectType `protobuf:"varint,2,opt,name=type,proto3,enum=ProjectType" json:"type,omitempty"`
Stars int32 `protobuf:"varint,3,opt,name=stars,proto3" json:"stars,omitempty"`
Forks int32 `protobuf:"varint,4,opt,name=forks,proto3" json:"forks,omitempty"`
Issues int32 `protobuf:"varint,5,opt,name=issues,proto3" json:"issues,omitempty"`
}
func (x *ProjectInfo) Reset() {
*x = ProjectInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_filter_input_spec_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ProjectInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProjectInfo) ProtoMessage() {}
func (x *ProjectInfo) ProtoReflect() protoreflect.Message {
mi := &file_filter_input_spec_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProjectInfo.ProtoReflect.Descriptor instead.
func (*ProjectInfo) Descriptor() ([]byte, []int) {
return file_filter_input_spec_proto_rawDescGZIP(), []int{3}
}
func (x *ProjectInfo) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *ProjectInfo) GetType() ProjectType {
if x != nil {
return x.Type
}
return ProjectType_UNKNOWN
}
func (x *ProjectInfo) GetStars() int32 {
if x != nil {
return x.Stars
}
return 0
}
func (x *ProjectInfo) GetForks() int32 {
if x != nil {
return x.Forks
}
return 0
}
func (x *ProjectInfo) GetIssues() int32 {
if x != nil {
return x.Issues
}
return 0
}
type PackageVersion struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Ecosystem string `protobuf:"bytes,1,opt,name=ecosystem,proto3" json:"ecosystem,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
}
func (x *PackageVersion) Reset() {
*x = PackageVersion{}
if protoimpl.UnsafeEnabled {
mi := &file_filter_input_spec_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *PackageVersion) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PackageVersion) ProtoMessage() {}
func (x *PackageVersion) ProtoReflect() protoreflect.Message {
mi := &file_filter_input_spec_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PackageVersion.ProtoReflect.Descriptor instead.
func (*PackageVersion) Descriptor() ([]byte, []int) {
return file_filter_input_spec_proto_rawDescGZIP(), []int{4}
}
func (x *PackageVersion) GetEcosystem() string {
if x != nil {
return x.Ecosystem
}
return ""
}
func (x *PackageVersion) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *PackageVersion) GetVersion() string {
if x != nil {
return x.Version
}
return ""
}
type FilterInput struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Pkg *PackageVersion `protobuf:"bytes,1,opt,name=pkg,proto3" json:"pkg,omitempty"`
Vulns *Vulnerabilities `protobuf:"bytes,2,opt,name=vulns,proto3" json:"vulns,omitempty"`
Scorecard *Scorecard `protobuf:"bytes,3,opt,name=scorecard,proto3" json:"scorecard,omitempty"`
Projects []*ProjectInfo `protobuf:"bytes,4,rep,name=projects,proto3" json:"projects,omitempty"`
Licenses []string `protobuf:"bytes,5,rep,name=licenses,proto3" json:"licenses,omitempty"`
}
func (x *FilterInput) Reset() {
*x = FilterInput{}
if protoimpl.UnsafeEnabled {
mi := &file_filter_input_spec_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *FilterInput) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FilterInput) ProtoMessage() {}
func (x *FilterInput) ProtoReflect() protoreflect.Message {
mi := &file_filter_input_spec_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FilterInput.ProtoReflect.Descriptor instead.
func (*FilterInput) Descriptor() ([]byte, []int) {
return file_filter_input_spec_proto_rawDescGZIP(), []int{5}
}
func (x *FilterInput) GetPkg() *PackageVersion {
if x != nil {
return x.Pkg
}
return nil
}
func (x *FilterInput) GetVulns() *Vulnerabilities {
if x != nil {
return x.Vulns
}
return nil
}
func (x *FilterInput) GetScorecard() *Scorecard {
if x != nil {
return x.Scorecard
}
return nil
}
func (x *FilterInput) GetProjects() []*ProjectInfo {
if x != nil {
return x.Projects
}
return nil
}
func (x *FilterInput) GetLicenses() []string {
if x != nil {
return x.Licenses
}
return nil
}
var File_filter_input_spec_proto protoreflect.FileDescriptor
var file_filter_input_spec_proto_rawDesc = []byte{
0x0a, 0x17, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x73,
0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x31, 0x0a, 0x0d, 0x56, 0x75, 0x6c,
0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x76,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x76, 0x65, 0x22, 0xcd, 0x01, 0x0a,
0x0f, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73,
0x12, 0x20, 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e,
0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x03, 0x61,
0x6c, 0x6c, 0x12, 0x2a, 0x0a, 0x08, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x18, 0x02,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69,
0x6c, 0x69, 0x74, 0x79, 0x52, 0x08, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x12, 0x22,
0x0a, 0x04, 0x68, 0x69, 0x67, 0x68, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x56,
0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x04, 0x68, 0x69,
0x67, 0x68, 0x12, 0x26, 0x0a, 0x06, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x18, 0x04, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69,
0x74, 0x79, 0x52, 0x06, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x12, 0x20, 0x0a, 0x03, 0x6c, 0x6f,
0x77, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72,
0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x03, 0x6c, 0x6f, 0x77, 0x22, 0x76, 0x0a, 0x09,
0x53, 0x63, 0x6f, 0x72, 0x65, 0x63, 0x61, 0x72, 0x64, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x63, 0x6f,
0x72, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x53, 0x63, 0x6f, 0x72,
0x65, 0x63, 0x61, 0x72, 0x64, 0x2e, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x53, 0x63, 0x6f,
0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x3a, 0x02, 0x38, 0x01, 0x22, 0x87, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74,
0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74,
0x61, 0x72, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x73,
0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52,
0x05, 0x66, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x73,
0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x73, 0x22, 0x5c,
0x0a, 0x0e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x12, 0x1c, 0x0a, 0x09, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x12,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xc8, 0x01, 0x0a,
0x0b, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x21, 0x0a, 0x03,
0x70, 0x6b, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x50, 0x61, 0x63, 0x6b,
0x61, 0x67, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x70, 0x6b, 0x67, 0x12,
0x26, 0x0a, 0x05, 0x76, 0x75, 0x6c, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10,
0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73,
0x52, 0x05, 0x76, 0x75, 0x6c, 0x6e, 0x73, 0x12, 0x28, 0x0a, 0x09, 0x73, 0x63, 0x6f, 0x72, 0x65,
0x63, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x53, 0x63, 0x6f,
0x72, 0x65, 0x63, 0x61, 0x72, 0x64, 0x52, 0x09, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x63, 0x61, 0x72,
0x64, 0x12, 0x28, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x04, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6e, 0x66,
0x6f, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6c,
0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6c,
0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x2a, 0x26, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x6a, 0x65,
0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57,
0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x47, 0x49, 0x54, 0x48, 0x55, 0x42, 0x10, 0x01, 0x42,
0x28, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x61,
0x66, 0x65, 0x64, 0x65, 0x70, 0x2f, 0x76, 0x65, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x66, 0x69,
0x6c, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_filter_input_spec_proto_rawDescOnce sync.Once
file_filter_input_spec_proto_rawDescData = file_filter_input_spec_proto_rawDesc
)
func file_filter_input_spec_proto_rawDescGZIP() []byte {
file_filter_input_spec_proto_rawDescOnce.Do(func() {
file_filter_input_spec_proto_rawDescData = protoimpl.X.CompressGZIP(file_filter_input_spec_proto_rawDescData)
})
return file_filter_input_spec_proto_rawDescData
}
var file_filter_input_spec_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_filter_input_spec_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_filter_input_spec_proto_goTypes = []interface{}{
(ProjectType)(0), // 0: ProjectType
(*Vulnerability)(nil), // 1: Vulnerability
(*Vulnerabilities)(nil), // 2: Vulnerabilities
(*Scorecard)(nil), // 3: Scorecard
(*ProjectInfo)(nil), // 4: ProjectInfo
(*PackageVersion)(nil), // 5: PackageVersion
(*FilterInput)(nil), // 6: FilterInput
nil, // 7: Scorecard.ScoresEntry
}
var file_filter_input_spec_proto_depIdxs = []int32{
1, // 0: Vulnerabilities.all:type_name -> Vulnerability
1, // 1: Vulnerabilities.critical:type_name -> Vulnerability
1, // 2: Vulnerabilities.high:type_name -> Vulnerability
1, // 3: Vulnerabilities.medium:type_name -> Vulnerability
1, // 4: Vulnerabilities.low:type_name -> Vulnerability
7, // 5: Scorecard.scores:type_name -> Scorecard.ScoresEntry
0, // 6: ProjectInfo.type:type_name -> ProjectType
5, // 7: FilterInput.pkg:type_name -> PackageVersion
2, // 8: FilterInput.vulns:type_name -> Vulnerabilities
3, // 9: FilterInput.scorecard:type_name -> Scorecard
4, // 10: FilterInput.projects:type_name -> ProjectInfo
11, // [11:11] is the sub-list for method output_type
11, // [11:11] is the sub-list for method input_type
11, // [11:11] is the sub-list for extension type_name
11, // [11:11] is the sub-list for extension extendee
0, // [0:11] is the sub-list for field type_name
}
func init() { file_filter_input_spec_proto_init() }
func file_filter_input_spec_proto_init() {
if File_filter_input_spec_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_filter_input_spec_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Vulnerability); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_filter_input_spec_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Vulnerabilities); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_filter_input_spec_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Scorecard); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_filter_input_spec_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ProjectInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_filter_input_spec_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PackageVersion); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_filter_input_spec_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FilterInput); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_filter_input_spec_proto_rawDesc,
NumEnums: 1,
NumMessages: 7,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_filter_input_spec_proto_goTypes,
DependencyIndexes: file_filter_input_spec_proto_depIdxs,
EnumInfos: file_filter_input_spec_proto_enumTypes,
MessageInfos: file_filter_input_spec_proto_msgTypes,
}.Build()
File_filter_input_spec_proto = out.File
file_filter_input_spec_proto_rawDesc = nil
file_filter_input_spec_proto_goTypes = nil
file_filter_input_spec_proto_depIdxs = nil
}

View File

@ -269,6 +269,7 @@ type GetPackageVersionInsightResponse struct {
Body []byte
HTTPResponse *http.Response
JSON200 *PackageVersionInsight
JSON403 *ApiError
JSON404 *ApiError
JSON429 *ApiError
JSON500 *ApiError
@ -345,6 +346,13 @@ func ParseGetPackageVersionInsightResponse(rsp *http.Response) (*GetPackageVersi
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403:
var dest ApiError
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON403 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404:
var dest ApiError
if err := json.Unmarshal(bodyBytes, &dest); err != nil {

View File

@ -1021,6 +1021,28 @@ const (
LicenseZlibAcknowledgement License = "zlib-acknowledgement"
)
// Defines values for PackageVulnerabilitySeveritiesRisk.
const (
PackageVulnerabilitySeveritiesRiskCRITICAL PackageVulnerabilitySeveritiesRisk = "CRITICAL"
PackageVulnerabilitySeveritiesRiskHIGH PackageVulnerabilitySeveritiesRisk = "HIGH"
PackageVulnerabilitySeveritiesRiskLOW PackageVulnerabilitySeveritiesRisk = "LOW"
PackageVulnerabilitySeveritiesRiskMEDIUM PackageVulnerabilitySeveritiesRisk = "MEDIUM"
PackageVulnerabilitySeveritiesRiskUNKNOWN PackageVulnerabilitySeveritiesRisk = "UNKNOWN"
)
// Defines values for PackageVulnerabilitySeveritiesType.
const (
PackageVulnerabilitySeveritiesTypeCVSSV2 PackageVulnerabilitySeveritiesType = "CVSS_V2"
PackageVulnerabilitySeveritiesTypeCVSSV3 PackageVulnerabilitySeveritiesType = "CVSS_V3"
PackageVulnerabilitySeveritiesTypeUNSPECIFIED PackageVulnerabilitySeveritiesType = "UNSPECIFIED"
)
// Defines values for ScorecardVersion.
const (
ScorecardVersionV2 ScorecardVersion = "V2"
@ -1133,14 +1155,50 @@ type PackageVersion struct {
// PackageVersionInsight defines model for PackageVersionInsight.
type PackageVersionInsight struct {
Dependencies *[]PackageDependency `json:"dependencies,omitempty"`
Dependents *PackageDependents `json:"dependents,omitempty"`
Licenses *[]License `json:"licenses,omitempty"`
PackageVersion *PackageVersion `json:"package_version,omitempty"`
Projects *[]PackageProjectInfo `json:"projects,omitempty"`
Scorecard *Scorecard `json:"scorecard,omitempty"`
Dependencies *[]PackageDependency `json:"dependencies,omitempty"`
Dependents *PackageDependents `json:"dependents,omitempty"`
Licenses *[]License `json:"licenses,omitempty"`
// The latest version available for the package
PackageCurrentVersion *string `json:"package_current_version,omitempty"`
PackageVersion *PackageVersion `json:"package_version,omitempty"`
Projects *[]PackageProjectInfo `json:"projects,omitempty"`
Scorecard *Scorecard `json:"scorecard,omitempty"`
Vulnerabilities *[]PackageVulnerability `json:"vulnerabilities,omitempty"`
}
// Subset of OSV schema required to perform policy
// decision by various tools
type PackageVulnerability struct {
// Alias identifiers of the same vulnerability in
// other databases
Aliases *[]string `json:"aliases,omitempty"`
// Vulnerability identifier
Id *string `json:"id,omitempty"`
// Related vulnerability identifiers for similar issues
// in
Related *[]string `json:"related,omitempty"`
Severities *[]struct {
// Normalized risk rating computed from score
Risk *PackageVulnerabilitySeveritiesRisk `json:"risk,omitempty"`
// Type specific vulnerability score
Score *string `json:"score,omitempty"`
Type *PackageVulnerabilitySeveritiesType `json:"type,omitempty"`
} `json:"severities,omitempty"`
// Short summary of vulnerability
Summary *string `json:"summary,omitempty"`
}
// Normalized risk rating computed from score
type PackageVulnerabilitySeveritiesRisk string
// PackageVulnerabilitySeveritiesType defines model for PackageVulnerability.Severities.Type.
type PackageVulnerabilitySeveritiesType string
// Scorecard defines model for Scorecard.
type Scorecard struct {
Content *ScorecardContentV2 `json:"content,omitempty"`

14
go.mod
View File

@ -5,11 +5,13 @@ go 1.18
require (
github.com/deepmap/oapi-codegen v1.12.4
github.com/google/cel-go v0.13.0
github.com/google/osv-scanner v1.0.2
github.com/google/osv-scanner v1.1.0
github.com/jedib0t/go-pretty/v6 v6.4.4
github.com/safedep/dry v0.0.0-20230202121135-2225c66946de
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
golang.org/x/term v0.3.0
golang.org/x/term v0.4.0
gopkg.in/yaml.v2 v2.4.0
)
@ -18,13 +20,19 @@ require (
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/masterminds/semver v1.5.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oklog/ulid/v2 v2.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.5.0 // indirect
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect
google.golang.org/protobuf v1.28.1 // indirect

34
go.sum
View File

@ -1,5 +1,6 @@
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
@ -13,20 +14,38 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s=
github.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGnFiDaOaKKl5yas=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/cel-go v0.13.0 h1:z+8OBOcmh7IeKyqwT/6IlnMvy621fYUqnTVPEdegGlU=
github.com/google/cel-go v0.13.0/go.mod h1:K2hpQgEjDp18J76a2DKFRlPBPpgRZgi6EbnpDgIhJ8s=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/osv-scanner v1.0.2 h1:EiDbP8XQhEvo9I7WZMvA7OkJinyOULhNTD7SITS2tBY=
github.com/google/osv-scanner v1.0.2/go.mod h1:KTYFW64rATMvw7MtWAVXxIkG7u0R86n6VUKM8pzOzF0=
github.com/google/osv-scanner v1.1.0 h1:6XL8tD8u4w8NFyiMo03Yd4xGG1VXhZXyrBESBuyWeUY=
github.com/google/osv-scanner v1.1.0/go.mod h1:w8BdEP4PJSosGhDfZ6W5RGMfIGb73rW38vCXB9DWA4c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty/v6 v6.4.4 h1:N+gz6UngBPF4M288kiMURPHELDMIhF/Em35aYuKrsSc=
github.com/jedib0t/go-pretty/v6 v6.4.4/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/masterminds/semver v1.5.0 h1:hTxJTTY7tjvnWMrl08O6u3G6BLlKVwxSz01lVac9P8U=
github.com/masterminds/semver v1.5.0/go.mod h1:s7KNT9fnd7edGzwwP7RBX4H0v/CYd5qdOLfkL1V75yg=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safedep/dry v0.0.0-20230202121135-2225c66946de h1:07LwA5P5bxaVK8SXtoBfP8qZGcWAtqiFbq54UZnMcyg=
github.com/safedep/dry v0.0.0-20230202121135-2225c66946de/go.mod h1:H111d9khzpHFEqKXb9lgAZ1W5eeK7yEN7ToWu0ak+JI=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
@ -43,22 +62,25 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo=
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

View File

@ -13,7 +13,8 @@ const (
apiUrlEnvKey = "VET_INSIGHTS_API_URL"
apiKeyEnvKey = "VET_INSIGHTS_API_KEY"
defaultApiUrl = "https://api.safedep.io/insights/v1"
defaultApiUrl = "https://api.safedep.io/insights/v1"
defaultControlPlaneApiUrl = "https://api.safedep.io/control-plane/v1"
homeRelativeConfigPath = ".safedep/vet-auth.yml"
)
@ -40,6 +41,14 @@ func Verify() error {
return nil
}
func DefaultApiUrl() string {
return defaultApiUrl
}
func DefaultControlPlaneApiUrl() string {
return defaultControlPlaneApiUrl
}
func ApiUrl() string {
if url, ok := os.LookupEnv(apiUrlEnvKey); ok {
return url

77
internal/auth/trial.go Normal file
View File

@ -0,0 +1,77 @@
package auth
import (
"context"
"errors"
"fmt"
"net/http"
"time"
"github.com/deepmap/oapi-codegen/pkg/types"
"github.com/safedep/dry/utils"
"github.com/safedep/vet/gen/controlplane"
"github.com/safedep/vet/pkg/common/logger"
apierr "github.com/safedep/dry/errors"
)
type TrialConfig struct {
Email string
ControlPlaneApiUrl string
}
type trialRegistrationResponse struct {
Id string
ExpiresAt time.Time
}
type trialRegistrationClient struct {
config TrialConfig
}
func NewTrialRegistrationClient(config TrialConfig) *trialRegistrationClient {
return &trialRegistrationClient{config: config}
}
func (client *trialRegistrationClient) Execute() (*trialRegistrationResponse, error) {
if utils.IsEmptyString(client.config.Email) {
return nil, errors.New("email is required")
}
if utils.IsEmptyString(client.config.ControlPlaneApiUrl) {
return nil, errors.New("control plane API is required")
}
logger.Infof("Trial registrations using Control Plane: %s",
client.config.ControlPlaneApiUrl)
cpClient, err := controlplane.NewClientWithResponses(client.config.ControlPlaneApiUrl)
if err != nil {
return nil, err
}
logger.Infof("Trial registration requesting API key for: %s",
client.config.Email)
res, err := cpClient.RegisterTrialUserWithResponse(context.Background(),
controlplane.RegisterTrialUserJSONRequestBody{
Email: types.Email(client.config.Email),
})
if err != nil {
return nil, err
}
if res.HTTPResponse.StatusCode != http.StatusCreated {
if err, ok := apierr.UnmarshalApiError(res.Body); ok {
return nil, err
} else {
return nil, fmt.Errorf("unexpected status code:%d from control plane",
res.HTTPResponse.StatusCode)
}
}
return &trialRegistrationResponse{
Id: utils.SafelyGetValue(res.JSON201.Id),
ExpiresAt: utils.SafelyGetValue(res.JSON201.ExpiresAt),
}, nil
}

27
main.go
View File

@ -3,6 +3,7 @@ package main
import (
"fmt"
"os"
"strconv"
"github.com/safedep/vet/pkg/common/logger"
"github.com/spf13/cobra"
@ -13,10 +14,25 @@ var (
debug bool
)
var banner string = `
.----------------. .----------------. .----------------.
| .--------------. || .--------------. || .--------------. |
| | ____ ____ | || | _________ | || | _________ | |
| ||_ _| |_ _| | || | |_ ___ | | || | | _ _ | | |
| | \ \ / / | || | | |_ \_| | || | |_/ | | \_| | |
| | \ \ / / | || | | _| _ | || | | | | |
| | \ ' / | || | _| |___/ | | || | _| |_ | |
| | \_/ | || | |_________| | || | |_____| | |
| | | || | | || | | |
| '--------------' || '--------------' || '--------------' |
'----------------' '----------------' '----------------'
`
func main() {
cmd := &cobra.Command{
Use: "vet [OPTIONS] COMMAND [ARG...]",
Short: "Vet your 3rd party dependencies for security risks",
Short: "[ Establish trust in open source software supply chain ]",
TraverseChildren: true,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
@ -32,9 +48,11 @@ func main() {
cmd.AddCommand(newAuthCommand())
cmd.AddCommand(newScanCommand())
cmd.AddCommand(newQueryCommand())
cmd.AddCommand(newVersionCommand())
cobra.OnInitialize(func() {
printBanner()
logger.SetLogLevel(verbose, debug)
})
@ -42,3 +60,10 @@ func main() {
os.Exit(1)
}
}
func printBanner() {
bRet, err := strconv.ParseBool(os.Getenv("VET_DISABLE_BANNER"))
if (err != nil) || (!bRet) {
fmt.Print(banner)
}
}

View File

@ -3,21 +3,42 @@ package analyzer
import (
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"github.com/golang/protobuf/jsonpb"
"github.com/google/cel-go/cel"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/safedep/dry/utils"
"github.com/safedep/vet/gen/filterinput"
"github.com/safedep/vet/gen/insightapi"
"github.com/safedep/vet/pkg/common/logger"
"github.com/safedep/vet/pkg/models"
)
const (
filterInputVarRoot = "_"
filterInputVarPkg = "pkg"
filterInputVarVulns = "vulns"
filterInputVarScorecard = "scorecard"
filterInputVarProjects = "projects"
filterInputVarLicenses = "licenses"
)
type celFilterAnalyzer struct {
program cel.Program
}
func NewCelFilterAnalyzer(filter string) (Analyzer, error) {
env, err := cel.NewEnv(
cel.Variable("pkg", cel.DynType),
cel.Variable("manifest", cel.DynType),
cel.Variable(filterInputVarPkg, cel.DynType),
cel.Variable(filterInputVarVulns, cel.DynType),
cel.Variable(filterInputVarProjects, cel.DynType),
cel.Variable(filterInputVarScorecard, cel.DynType),
cel.Variable(filterInputVarLicenses, cel.DynType),
cel.Variable(filterInputVarRoot, cel.DynType),
)
if err != nil {
@ -44,25 +65,47 @@ func (f *celFilterAnalyzer) Name() string {
func (f *celFilterAnalyzer) Analyze(manifest *models.PackageManifest,
handler AnalyzerEventHandler) error {
pkgManifestVal, err := f.valType(manifest)
if err != nil {
logger.Errorf("Failed to convert manifest to val: %v", err)
}
tbl := table.NewWriter()
tbl.SetStyle(table.StyleLight)
tbl.SetOutputMirror(os.Stdout)
tbl.AppendHeader(table.Row{"Ecosystem", "Package", "Version",
"Latest", "Source"})
filterStat := struct {
total int
matched int
err int
}{}
logger.Infof("CEL filtering manifest: %s", manifest.Path)
for _, pkg := range manifest.Packages {
pkgVal, err := f.valType(pkg)
filterStat.total += 1
filterInput, err := f.buildFilterInput(pkg)
if err != nil {
logger.Errorf("Failed to convert package to val: %v", err)
filterStat.err += 1
logger.Errorf("Failed to convert package to filter input: %v", err)
continue
}
serializedInput, err := f.serializeFilterInput(filterInput)
if err != nil {
filterStat.err += 1
logger.Errorf("Failed to serialize filter input: %v", err)
continue
}
out, _, err := f.program.Eval(map[string]interface{}{
"pkg": pkgVal,
"manifest": pkgManifestVal,
filterInputVarRoot: serializedInput,
filterInputVarPkg: serializedInput["pkg"],
filterInputVarProjects: serializedInput["projects"],
filterInputVarVulns: serializedInput["vulns"],
filterInputVarScorecard: serializedInput["scorecard"],
filterInputVarLicenses: serializedInput["licenses"],
})
if err != nil {
filterStat.err += 1
logger.Errorf("Failed to evaluate CEL for %s:%v : %v",
pkg.PackageDetails.Name,
pkg.PackageDetails.Version, err)
@ -71,25 +114,162 @@ func (f *celFilterAnalyzer) Analyze(manifest *models.PackageManifest,
if (reflect.TypeOf(out).Kind() == reflect.Bool) &&
(reflect.ValueOf(out).Bool()) {
fmt.Printf("[%s] %s %v\n", pkg.PackageDetails.Ecosystem,
pkg.PackageDetails.Name, pkg.PackageDetails.Version)
filterStat.matched += 1
tbl.AppendRow(table.Row{pkg.PackageDetails.Ecosystem,
pkg.PackageDetails.Name,
pkg.PackageDetails.Version,
f.pkgLatestVersion(pkg),
f.pkgSource(pkg),
})
}
}
fmt.Printf("%s\n", text.Bold.Sprint("Filter evaluated with ",
filterStat.matched, " out of ", filterStat.total, " matched and ",
filterStat.err, " error(s)"))
tbl.Render()
return nil
}
func (f *celFilterAnalyzer) valType(i any) (any, error) {
data, err := json.Marshal(i)
// TODO: Fix this JSON round-trip problem by directly configuring CEL env to
// work with Protobuf messages
func (f *celFilterAnalyzer) serializeFilterInput(fi *filterinput.FilterInput) (map[string]interface{}, error) {
var ret map[string]interface{}
m := jsonpb.Marshaler{OrigName: true, EnumsAsInts: false, EmitDefaults: true}
data, err := m.MarshalToString(fi)
if err != nil {
return nil, err
return ret, err
}
ret := make(map[string]interface{})
err = json.Unmarshal(data, &ret)
logger.Debugf("Serialized filter input: %s", data)
err = json.Unmarshal([]byte(data), &ret)
if err != nil {
return nil, err
return ret, err
}
return ret, nil
}
func (f *celFilterAnalyzer) pkgLatestVersion(pkg *models.Package) string {
insight := utils.SafelyGetValue(pkg.Insights)
return utils.SafelyGetValue(insight.PackageCurrentVersion)
}
func (f *celFilterAnalyzer) pkgSource(pkg *models.Package) string {
insight := utils.SafelyGetValue(pkg.Insights)
projects := utils.SafelyGetValue(insight.Projects)
if len(projects) > 0 {
return utils.SafelyGetValue(projects[0].Link)
}
return ""
}
func (f *celFilterAnalyzer) buildFilterInput(pkg *models.Package) (*filterinput.FilterInput, error) {
fi := filterinput.FilterInput{
Pkg: &filterinput.PackageVersion{
Ecosystem: strings.ToLower(string(pkg.PackageDetails.Ecosystem)),
Name: pkg.PackageDetails.Name,
Version: pkg.PackageDetails.Version,
},
Projects: []*filterinput.ProjectInfo{},
Vulns: &filterinput.Vulnerabilities{
All: []*filterinput.Vulnerability{},
Critical: []*filterinput.Vulnerability{},
High: []*filterinput.Vulnerability{},
Medium: []*filterinput.Vulnerability{},
Low: []*filterinput.Vulnerability{},
},
Scorecard: &filterinput.Scorecard{
Scores: map[string]float32{},
},
Licenses: []string{},
}
// Safely get insight
insight := utils.SafelyGetValue(pkg.Insights)
// Add projects
projectTypeMapper := func(tp string) filterinput.ProjectType {
tp = strings.ToLower(tp)
if tp == "github" {
return filterinput.ProjectType_GITHUB
} else {
return filterinput.ProjectType_UNKNOWN
}
}
for _, project := range utils.SafelyGetValue(insight.Projects) {
fi.Projects = append(fi.Projects, &filterinput.ProjectInfo{
Name: utils.SafelyGetValue(project.Name),
Stars: int32(utils.SafelyGetValue(project.Stars)),
Forks: int32(utils.SafelyGetValue(project.Forks)),
Issues: int32(utils.SafelyGetValue(project.Issues)),
Type: projectTypeMapper(utils.SafelyGetValue(project.Type)),
})
}
// Add vulnerabilities
cveFilter := func(aliases []string) string {
for _, alias := range aliases {
if strings.HasPrefix(strings.ToUpper(alias), "CVE-") {
return alias
}
}
return ""
}
for _, vuln := range utils.SafelyGetValue(insight.Vulnerabilities) {
fiv := filterinput.Vulnerability{
Id: utils.SafelyGetValue(vuln.Id),
Cve: cveFilter(utils.SafelyGetValue(vuln.Aliases)),
}
fi.Vulns.All = append(fi.Vulns.All, &fiv)
risk := insightapi.PackageVulnerabilitySeveritiesRiskUNKNOWN
for _, s := range utils.SafelyGetValue(vuln.Severities) {
sType := utils.SafelyGetValue(s.Type)
if (sType == insightapi.PackageVulnerabilitySeveritiesTypeCVSSV3) ||
(sType == insightapi.PackageVulnerabilitySeveritiesTypeCVSSV2) {
risk = utils.SafelyGetValue(s.Risk)
break
}
}
switch risk {
case insightapi.PackageVulnerabilitySeveritiesRiskCRITICAL:
fi.Vulns.Critical = append(fi.Vulns.Critical, &fiv)
break
case insightapi.PackageVulnerabilitySeveritiesRiskHIGH:
fi.Vulns.High = append(fi.Vulns.High, &fiv)
break
case insightapi.PackageVulnerabilitySeveritiesRiskMEDIUM:
fi.Vulns.Medium = append(fi.Vulns.Medium, &fiv)
break
case insightapi.PackageVulnerabilitySeveritiesRiskLOW:
fi.Vulns.Low = append(fi.Vulns.Low, &fiv)
break
}
}
// Add licenses
for _, lic := range utils.SafelyGetValue(insight.Licenses) {
fi.Licenses = append(fi.Licenses, string(lic))
}
// Scorecard
scorecard := utils.SafelyGetValue(insight.Scorecard)
checks := utils.SafelyGetValue(utils.SafelyGetValue(scorecard.Content).Checks)
for _, check := range checks {
fi.Scorecard.Scores[string(utils.SafelyGetValue(check.Name))] =
utils.SafelyGetValue(check.Score)
}
return &fi, nil
}

View File

@ -25,12 +25,10 @@ func NewJsonDumperAnalyzer(dir string) (Analyzer, error) {
if err != nil {
return nil, fmt.Errorf("cannot create dir: %w", err)
}
} else {
return nil, fmt.Errorf("cannot stat dir: %w", err)
}
return nil, fmt.Errorf("cannot stat dir: %w", err)
}
if !fi.IsDir() {
} else if !fi.IsDir() {
return nil, fmt.Errorf("%s is not a dir", dir)
}

View File

@ -11,6 +11,15 @@ func init() {
logrus.SetLevel(logrus.WarnLevel)
}
func LogToFile(path string) {
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
if err != nil {
panic(err)
}
logrus.SetOutput(file)
}
func SetLogLevel(verbose, debug bool) {
if verbose {
logrus.SetLevel(logrus.InfoLevel)

View File

@ -8,6 +8,16 @@ import (
"github.com/safedep/vet/pkg/models"
)
// We are supporting only those ecosystems for which we have data
// for enrichment. More ecosystems will be supported as we improve
// the capability of our Insights API
var supportedEcosystems map[string]bool = map[string]bool{
models.EcosystemGo: true,
models.EcosystemMaven: true,
models.EcosystemNpm: true,
models.EcosystemPyPI: true,
}
type Parser interface {
Ecosystem() string
Parse(lockfilePath string) (models.PackageManifest, error)
@ -19,19 +29,38 @@ type parserWrapper struct {
}
func List() []string {
return lockfile.ListParsers()
supportedParsers := make([]string, 0, 0)
parsers := lockfile.ListParsers()
for _, p := range parsers {
_, err := FindParser("", p)
if err != nil {
continue
}
supportedParsers = append(supportedParsers, p)
}
return supportedParsers
}
func FindParser(lockfilePath, lockfileAs string) (Parser, error) {
p, pa := lockfile.FindParser(lockfilePath, lockfileAs)
if p != nil {
return &parserWrapper{parser: p, parseAs: pa}, nil
pw := &parserWrapper{parser: p, parseAs: pa}
if pw.supported() {
return pw, nil
}
}
return nil, fmt.Errorf("no parser found with: %s for: %s", lockfileAs,
lockfilePath)
}
func (pw *parserWrapper) supported() bool {
return supportedEcosystems[pw.Ecosystem()]
}
func (pw *parserWrapper) Ecosystem() string {
switch pw.parseAs {
case "Cargo.lock":

View File

@ -8,7 +8,7 @@ import (
func TestListParser(t *testing.T) {
parsers := List()
assert.Equal(t, 14, len(parsers))
assert.Equal(t, 9, len(parsers))
}
func TestInvalidEcosystemMapping(t *testing.T) {

6
pkg/policy/policy.go Normal file
View File

@ -0,0 +1,6 @@
package policy
type PolicyEvent struct {
}
type PolicyEventHandler func(event *PolicyEvent) error

121
pkg/reporter/console.go Normal file
View File

@ -0,0 +1,121 @@
package reporter
import (
"fmt"
"os"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/safedep/dry/semver"
"github.com/safedep/dry/utils"
"github.com/safedep/vet/pkg/analyzer"
"github.com/safedep/vet/pkg/models"
"github.com/safedep/vet/pkg/policy"
)
type consoleReporter struct{}
func NewConsoleReporter() (Reporter, error) {
return &consoleReporter{}, nil
}
func (r *consoleReporter) Name() string {
return "Console Report Generator"
}
func (r *consoleReporter) AddManifest(manifest *models.PackageManifest) {
tbl := table.NewWriter()
tbl.SetOutputMirror(os.Stdout)
tbl.SetStyle(table.StyleLight)
tbl.AppendHeader(table.Row{"Package", "Attribute", "Summary"})
for _, pkg := range manifest.Packages {
r.report(tbl, pkg)
}
fmt.Print(text.Bold.Sprint("Manifest: ", text.FgBlue.Sprint(manifest.Path)))
fmt.Print("\n")
tbl.Render()
}
func (r *consoleReporter) AddAnalyzerEvent(event *analyzer.AnalyzerEvent) {
}
func (r *consoleReporter) AddPolicyEvent(event *policy.PolicyEvent) {
}
func (r *consoleReporter) Finish() error {
return nil
}
func (r *consoleReporter) report(tbl table.Writer, pkg *models.Package) {
insight := utils.SafelyGetValue(pkg.Insights)
headerAppended := false
headerAppender := func() {
if headerAppended {
return
}
// Header for this package
tbl.AppendRow(table.Row{
fmt.Sprintf("%s/%s", pkg.PackageDetails.Name, pkg.PackageDetails.Version),
"", "",
})
headerAppended = true
}
// Report vulnerabilities
sm := map[string]int{"CRITICAL": 0, "HIGH": 0}
for _, vuln := range utils.SafelyGetValue(insight.Vulnerabilities) {
for _, s := range utils.SafelyGetValue(vuln.Severities) {
risk := string(utils.SafelyGetValue(s.Risk))
if (risk == "CRITICAL") || (risk == "HIGH") {
sm[risk] += 1
}
}
}
if (sm["CRITICAL"] > 0) || (sm["HIGH"] > 0) {
headerAppender()
tbl.AppendRow(table.Row{"",
text.Bold.Sprint(text.BgRed.Sprint("Vulnerability")),
fmt.Sprintf("Critical:%d High:%d",
sm["CRITICAL"], sm["HIGH"])})
}
// Popularity
projects := utils.SafelyGetValue(insight.Projects)
if len(projects) > 0 {
p := projects[0]
sc := utils.SafelyGetValue(p.Stars)
ic := utils.SafelyGetValue(p.Issues)
if (sc > 0) && (sc < 10) && (ic > 0) && (ic < 5) {
headerAppender()
tbl.AppendRow(table.Row{"",
text.Bold.Sprint("Low Popularity"),
fmt.Sprintf("Stars:%d Issues:%d", sc, ic)})
}
}
// High version drift
version := pkg.PackageDetails.Version
latestVersion := utils.SafelyGetValue(insight.PackageCurrentVersion)
driftType, _ := semver.Diff(version, latestVersion)
if driftType.IsMajor() {
headerAppender()
tbl.AppendRow(table.Row{"",
text.Bold.Sprint("Version Drift"),
fmt.Sprintf("%s > %s", version, latestVersion),
})
}
if headerAppended {
tbl.AppendSeparator()
}
}

83
pkg/reporter/markdown.go Normal file
View File

@ -0,0 +1,83 @@
package reporter
import (
"os"
"sync"
"text/template"
"github.com/safedep/vet/pkg/analyzer"
"github.com/safedep/vet/pkg/common/logger"
"github.com/safedep/vet/pkg/models"
"github.com/safedep/vet/pkg/policy"
_ "embed"
)
//go:embed markdown.template.md
var markdownTemplate string
type MarkdownReportingConfig struct {
Path string
}
type markdownTemplateInput struct {
Manifests []*models.PackageManifest
AnalyzerEvents []*analyzer.AnalyzerEvent
PolicyEvents []*policy.PolicyEvent
}
type markdownReportGenerator struct {
m sync.Mutex
config MarkdownReportingConfig
templateInput markdownTemplateInput
}
func NewMarkdownReportGenerator(config MarkdownReportingConfig) (Reporter, error) {
return &markdownReportGenerator{
config: config,
templateInput: markdownTemplateInput{
Manifests: make([]*models.PackageManifest, 0),
AnalyzerEvents: make([]*analyzer.AnalyzerEvent, 0),
PolicyEvents: make([]*policy.PolicyEvent, 0),
},
}, nil
}
func (r *markdownReportGenerator) Name() string {
return "Markdown Report Generator"
}
func (r *markdownReportGenerator) AddManifest(manifest *models.PackageManifest) {
r.m.Lock()
defer r.m.Unlock()
r.templateInput.Manifests = append(r.templateInput.Manifests, manifest)
}
func (r *markdownReportGenerator) AddAnalyzerEvent(event *analyzer.AnalyzerEvent) {
r.m.Lock()
defer r.m.Unlock()
r.templateInput.AnalyzerEvents = append(r.templateInput.AnalyzerEvents, event)
}
func (r *markdownReportGenerator) AddPolicyEvent(event *policy.PolicyEvent) {
r.m.Lock()
defer r.m.Unlock()
r.templateInput.PolicyEvents = append(r.templateInput.PolicyEvents, event)
}
func (r *markdownReportGenerator) Finish() error {
logger.Infof("Generating consolidated markdown report: %s", r.config.Path)
tmpl, err := template.New("markdown").Parse(markdownTemplate)
if err != nil {
return err
}
file, err := os.Create(r.config.Path)
if err != nil {
return err
}
defer file.Close()
return tmpl.Execute(file, r.templateInput)
}

View File

@ -0,0 +1,30 @@
# Vet Report
## Summary
* {{ len .Manifests }} manifest(s) were scanned
* {{ len .AnalyzerEvents }} analyzer event(s) were generated
* {{ len .PolicyEvents }} policy violation(s) were observed
## Details
The scan was performed on following manifests:
{{ range $m := .Manifests }}
* [{{ $m.Ecosystem }}] {{ $m.Path }}
{{ end }}
## Packages
{{ range $m := .Manifests }}
### [{{ $m.Ecosystem }}] {{ $m.Path }}
{{ range $p := $m.Packages }}
#### {{ $p.PackageDetails.Name }} / {{ $p.PackageDetails.Version }}
{{ if and $p.Insights $p.Insights.Vulnerabilities }}
* {{ len *$p.Insights.Vulnerabilites }} vulnerabilities were identified
{{ end }}
{{ end }}
{{ end }}

19
pkg/reporter/reporter.go Normal file
View File

@ -0,0 +1,19 @@
package reporter
import (
"github.com/safedep/vet/pkg/analyzer"
"github.com/safedep/vet/pkg/models"
"github.com/safedep/vet/pkg/policy"
)
type Reporter interface {
Name() string
// Feed collected data to reporting module
AddManifest(manifest *models.PackageManifest)
AddAnalyzerEvent(event *analyzer.AnalyzerEvent)
AddPolicyEvent(event *policy.PolicyEvent)
// Inform reporting module to finalise (e.g. write report to file)
Finish() error
}

View File

@ -6,6 +6,8 @@ import (
"net/http"
"strings"
"github.com/safedep/dry/errors"
"github.com/safedep/dry/utils"
"github.com/safedep/vet/gen/insightapi"
"github.com/safedep/vet/internal/auth"
"github.com/safedep/vet/pkg/common/logger"
@ -62,15 +64,16 @@ func (e *insightsBasedPackageEnricher) Enrich(pkg *models.Package,
}
if res.HTTPResponse.StatusCode != 200 {
return fmt.Errorf("bad response: %d: %s", res.HTTPResponse.StatusCode,
res.HTTPResponse.Status)
err, _ = errors.UnmarshalApiError(res.Body)
return err
}
if (res.JSON200 == nil) || (res.JSON200.Dependencies == nil) {
return fmt.Errorf("unexpected nil response from Insight API")
if res.JSON200 == nil {
return fmt.Errorf("unexpected nil response for: %s/%s/%s",
pkg.Manifest.Ecosystem, pkg.PackageDetails.Name, pkg.Insights.PackageVersion.Version)
}
for _, dep := range *res.JSON200.Dependencies {
for _, dep := range utils.SafelyGetValue(res.JSON200.Dependencies) {
if strings.EqualFold(dep.PackageVersion.Name, pkg.PackageDetails.Name) {
// Skip self references in dependency
continue
@ -85,7 +88,7 @@ func (e *insightsBasedPackageEnricher) Enrich(pkg *models.Package,
})
if err != nil {
logger.Errorf("Failed to invoke package dependency callback: %v", err)
logger.Errorf("package dependency callback failed: %v", err)
}
}

43
pkg/scanner/load.go Normal file
View File

@ -0,0 +1,43 @@
package scanner
import (
"encoding/json"
"os"
"path/filepath"
"github.com/safedep/vet/pkg/models"
)
func scanDumpFilesForManifest(dir string) ([]*models.PackageManifest, error) {
var manifests []*models.PackageManifest
err := filepath.WalkDir(dir, func(path string, info os.DirEntry, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
path, err = filepath.Abs(path)
if err != nil {
return err
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
var manifest models.PackageManifest
err = json.Unmarshal(data, &manifest)
if err != nil {
return err
}
manifests = append(manifests, &manifest)
return nil
})
return manifests, err
}

View File

@ -5,6 +5,7 @@ import (
"github.com/safedep/vet/pkg/common/logger"
"github.com/safedep/vet/pkg/common/utils"
"github.com/safedep/vet/pkg/models"
"github.com/safedep/vet/pkg/reporter"
)
type Config struct {
@ -17,15 +18,18 @@ type packageManifestScanner struct {
config Config
enrichers []PackageMetaEnricher
analyzers []analyzer.Analyzer
reporters []reporter.Reporter
}
func NewPackageManifestScanner(config Config,
enrichers []PackageMetaEnricher,
analyzers []analyzer.Analyzer) *packageManifestScanner {
analyzers []analyzer.Analyzer,
reporters []reporter.Reporter) *packageManifestScanner {
return &packageManifestScanner{
config: config,
enrichers: enrichers,
analyzers: analyzers,
reporters: reporters,
}
}
@ -35,7 +39,6 @@ func (s *packageManifestScanner) ScanDirectory(dir string) error {
manifests, err := scanDirectoryForManifests(dir)
if err != nil {
logger.Errorf("Failed to scan directory: %v", err)
return err
}
@ -51,7 +54,6 @@ func (s *packageManifestScanner) ScanLockfiles(lockfiles []string,
manifests, err := scanLockfilesForManifests(lockfiles, lockfileAs)
if err != nil {
logger.Errorf("Failed to scan lockfiles: %v", err)
return err
}
@ -59,6 +61,19 @@ func (s *packageManifestScanner) ScanLockfiles(lockfiles []string,
return s.analyzeManifests(manifests)
}
// Load the manifests from a previous dumped JSON file
func (s *packageManifestScanner) ScanDumpDirectory(dir string) error {
logger.Infof("Scan dump files to load as manifests: %s", dir)
manifests, err := scanDumpFilesForManifest(dir)
if err != nil {
return err
}
logger.Infof("Loaded %d manifest(s)", len(manifests))
return s.analyzeManifests(manifests)
}
func (s *packageManifestScanner) analyzeManifests(manifests []*models.PackageManifest) error {
for _, manifest := range manifests {
logger.Infof("Analysing %s as %s ecosystem with %d packages", manifest.Path,
@ -72,18 +87,28 @@ func (s *packageManifestScanner) analyzeManifests(manifests []*models.PackageMan
err = s.analyzeManifest(manifest)
if err != nil {
logger.Errorf("Failed to analyze %s manifest %v : %v",
logger.Errorf("Failed to analyze %s manifest %s : %v",
manifest.Ecosystem, manifest.Path, err)
}
err = s.reportManifest(manifest)
if err != nil {
logger.Errorf("Failed to report %s manifest %s : %v",
manifest.Ecosystem, manifest.Path, err)
}
}
s.finishReporting()
return nil
}
func (s *packageManifestScanner) analyzeManifest(manifest *models.PackageManifest) error {
for _, task := range s.analyzers {
err := task.Analyze(manifest, func(event *analyzer.AnalyzerEvent) error {
// Handle analyzer event
for _, r := range s.reporters {
r.AddAnalyzerEvent(event)
}
return nil
})
if err != nil {
@ -94,7 +119,28 @@ func (s *packageManifestScanner) analyzeManifest(manifest *models.PackageManifes
return nil
}
func (s *packageManifestScanner) reportManifest(manifest *models.PackageManifest) error {
for _, r := range s.reporters {
r.AddManifest(manifest)
}
return nil
}
func (s *packageManifestScanner) finishReporting() {
for _, r := range s.reporters {
err := r.Finish()
if err != nil {
logger.Errorf("Reporter: %s failed with %v", r.Name(), err)
}
}
}
func (s *packageManifestScanner) enrichManifest(manifest *models.PackageManifest) error {
if len(s.enrichers) == 0 {
return nil
}
// FIXME: Potential deadlock situation in case of channel buffer is full
// because the goroutines perform both read and write to channel. Write occurs
// when goroutine invokes the work queue handler and the handler pushes back

72
query.go Normal file
View File

@ -0,0 +1,72 @@
package main
import (
"github.com/safedep/dry/utils"
"github.com/safedep/vet/pkg/analyzer"
"github.com/safedep/vet/pkg/common/logger"
"github.com/safedep/vet/pkg/reporter"
"github.com/safedep/vet/pkg/scanner"
"github.com/spf13/cobra"
)
// We will re-use the variable declarations in scan.go
// since query.go is a subset of scan function where
// data is loaded from JSON instead of lockfiles and enriched
// with backend
func newQueryCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "query",
Short: "Query JSON dump and run filters or render reports",
RunE: func(cmd *cobra.Command, args []string) error {
startQuery()
return nil
},
}
cmd.Flags().StringVarP(&dumpJsonManifestDir, "from", "F", "",
"The directory to load JSON dump files")
cmd.Flags().StringVarP(&celFilterExpression, "filter", "", "",
"Filter and print packages using CEL")
cmd.Flags().BoolVarP(&consoleReport, "report-console", "", false,
"Minimal summary of package manifest")
return cmd
}
func startQuery() {
err := internalStartQuery()
if err != nil {
logger.Errorf("Query completed with error: %v", err)
}
}
func internalStartQuery() error {
analyzers := []analyzer.Analyzer{}
reporters := []reporter.Reporter{}
enrichers := []scanner.PackageMetaEnricher{}
if !utils.IsEmptyString(celFilterExpression) {
task, err := analyzer.NewCelFilterAnalyzer(celFilterExpression)
if err != nil {
return err
}
analyzers = append(analyzers, task)
}
if consoleReport {
rp, err := reporter.NewConsoleReporter()
if err != nil {
return err
}
reporters = append(reporters, rp)
}
pmScanner := scanner.NewPackageManifestScanner(scanner.Config{
TransitiveAnalysis: false,
}, enrichers, analyzers, reporters)
return pmScanner.ScanDumpDirectory(dumpJsonManifestDir)
}

48
scan.go
View File

@ -4,9 +4,11 @@ import (
"fmt"
"os"
"github.com/safedep/dry/utils"
"github.com/safedep/vet/pkg/analyzer"
"github.com/safedep/vet/pkg/common/logger"
"github.com/safedep/vet/pkg/parser"
"github.com/safedep/vet/pkg/reporter"
"github.com/safedep/vet/pkg/scanner"
"github.com/spf13/cobra"
)
@ -18,9 +20,10 @@ var (
transitiveAnalysis bool
transitiveDepth int
concurrency int
dumpJsonManifest bool
dumpJsonManifestDir string
celFilterExpression string
markdownReportPath string
consoleReport bool
)
func newScanCommand() *cobra.Command {
@ -44,18 +47,20 @@ func newScanCommand() *cobra.Command {
"List of lockfiles to scan")
cmd.Flags().StringVarP(&lockfileAs, "lockfile-as", "", "",
"Parser to use for the lockfile (vet scan parsers to list)")
cmd.Flags().BoolVarP(&transitiveAnalysis, "transitive", "", true,
cmd.Flags().BoolVarP(&transitiveAnalysis, "transitive", "", false,
"Analyze transitive dependencies")
cmd.Flags().IntVarP(&transitiveDepth, "transitive-depth", "", 2,
"Analyze transitive dependencies till depth")
cmd.Flags().IntVarP(&concurrency, "concurrency", "C", 10,
"Number of goroutines to use for analysis")
cmd.Flags().BoolVarP(&dumpJsonManifest, "json-dump", "", false,
"Dump enriched manifests as JSON docs")
cmd.Flags().IntVarP(&concurrency, "concurrency", "C", 5,
"Number of concurrent analysis to run")
cmd.Flags().StringVarP(&dumpJsonManifestDir, "json-dump-dir", "", "",
"Dump dir for enriched JSON docs")
cmd.Flags().StringVarP(&celFilterExpression, "filter-cel", "", "",
"Dump enriched package manifests as JSON files to dir")
cmd.Flags().StringVarP(&celFilterExpression, "filter", "", "",
"Filter and print packages using CEL")
cmd.Flags().StringVarP(&markdownReportPath, "report-markdown", "", "",
"Generate consolidated markdown report to file")
cmd.Flags().BoolVarP(&consoleReport, "report-console", "", true,
"Minimal summary of package manifest")
cmd.AddCommand(listParsersCommand())
return cmd
@ -87,7 +92,7 @@ func startScan() {
func internalStartScan() error {
analyzers := []analyzer.Analyzer{}
if dumpJsonManifest {
if !utils.IsEmptyString(dumpJsonManifestDir) {
task, err := analyzer.NewJsonDumperAnalyzer(dumpJsonManifestDir)
if err != nil {
return err
@ -96,7 +101,7 @@ func internalStartScan() error {
analyzers = append(analyzers, task)
}
if len(celFilterExpression) > 0 {
if !utils.IsEmptyString(celFilterExpression) {
task, err := analyzer.NewCelFilterAnalyzer(celFilterExpression)
if err != nil {
return err
@ -105,6 +110,27 @@ func internalStartScan() error {
analyzers = append(analyzers, task)
}
reporters := []reporter.Reporter{}
if consoleReport {
rp, err := reporter.NewConsoleReporter()
if err != nil {
return err
}
reporters = append(reporters, rp)
}
if !utils.IsEmptyString(markdownReportPath) {
rp, err := reporter.NewMarkdownReportGenerator(reporter.MarkdownReportingConfig{
Path: markdownReportPath,
})
if err != nil {
return err
}
reporters = append(reporters, rp)
}
enrichers := []scanner.PackageMetaEnricher{
scanner.NewInsightBasedPackageEnricher(),
}
@ -113,7 +139,7 @@ func internalStartScan() error {
TransitiveAnalysis: transitiveAnalysis,
TransitiveDepth: transitiveDepth,
ConcurrentAnalyzer: concurrency,
}, enrichers, analyzers)
}, enrichers, analyzers, reporters)
var err error
if len(lockfiles) > 0 {