x/vulndb: add cve org command to lookup org info

Also adds a minor refactor of cveclient to increase code reuse.

Change-Id: I67e798e35124913d916d743f86dcbbbc8d7a6b37
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/412877
Reviewed-by: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Tatiana Bradley <tatiana@golang.org>
Reviewed-by: Tatiana Bradley <tatiana@golang.org>
This commit is contained in:
Tatiana Bradley 2022-06-17 15:31:11 -04:00
Родитель c71e66c2a9
Коммит e5430e2fed
3 изменённых файлов: 98 добавлений и 48 удалений

Просмотреть файл

@ -45,8 +45,9 @@ func main() {
formatCmd := " %s: %s\n"
fmt.Fprintf(out, "usage: cve [-key] [-user] [-org] [-test] <cmd> ...\n commands:\n")
fmt.Fprintf(out, formatCmd, "[-n] [-seq] [-year] reserve", "reserves new CVE IDs")
fmt.Fprintf(out, formatCmd, "quota", "outputs the CVE ID quota of an organization")
fmt.Fprintf(out, formatCmd, "lookup {cve-id}", "outputs details on an assigned CVE ID (CVE-YYYY-NNNN)")
fmt.Fprintf(out, formatCmd, "quota", "outputs the CVE ID quota of the authenticated organization")
fmt.Fprintf(out, formatCmd, "id {cve-id}", "outputs details on an assigned CVE ID (CVE-YYYY-NNNN)")
fmt.Fprintf(out, formatCmd, "org", "outputs details on the authenticated organization")
fmt.Fprintf(out, formatCmd, "[-year] [-state] list", "lists all CVE IDs for an organization")
flag.PrintDefaults()
}
@ -102,13 +103,17 @@ func main() {
if err := quota(c); err != nil {
log.Fatalf("cve quota: could not retrieve quota info due to error:\n %v", err)
}
case "lookup":
case "id":
id, err := validateID(flag.Arg(1))
if err != nil {
logUsageErr("cve lookup", err)
logUsageErr("cve id", err)
}
if err := lookup(c, id); err != nil {
log.Fatalf("cve lookup: could not retrieve CVE IDs due to error:\n %v", err)
if err := lookupID(c, id); err != nil {
log.Fatalf("cve id: could not retrieve CVE IDs due to error:\n %v", err)
}
case "org":
if err := lookupOrg(c); err != nil {
log.Fatalf("cve org: could not retrieve org info due to error:\n %v", err)
}
case "list":
// TODO(http://go.dev/issues/53258): allow time-based filters via flags.
@ -185,8 +190,17 @@ func quota(c *cveclient.Client) error {
return nil
}
func lookup(c *cveclient.Client, id string) error {
cve, err := c.RetrieveCVE(id)
func lookupOrg(c *cveclient.Client) error {
org, err := c.RetrieveOrg()
if err != nil {
return err
}
fmt.Printf("org name: %q\nshort name: %q\nuuid: %s\n", org.Name, org.ShortName, org.UUID)
return nil
}
func lookupID(c *cveclient.Client, id string) error {
cve, err := c.RetrieveID(id)
if err != nil {
return err
}

Просмотреть файл

@ -128,8 +128,7 @@ func (o *ReserveOptions) getURLParams(org string) url.Values {
}
func (c *Client) createReserveIDsRequest(opts ReserveOptions) (*http.Request, error) {
req, err := c.createRequest(http.MethodPost,
fmt.Sprintf("%s/api/cve-id", c.Endpoint))
req, err := c.createRequest(http.MethodPost, c.getURL(cveIDTarget))
if err != nil {
return nil, err
}
@ -169,33 +168,27 @@ type Quota struct {
}
// RetrieveQuota queries the API for the organizations reservation quota.
func (c *Client) RetrieveQuota() (Quota, error) {
req, err := c.createRequest(http.MethodGet, fmt.Sprintf("%s/api/org/%s/id_quota", c.Endpoint, c.Org))
if err != nil {
return Quota{}, err
}
var q Quota
err = c.sendRequest(req, nil, &q)
if err != nil {
return Quota{}, err
}
return q, nil
func (c *Client) RetrieveQuota() (q *Quota, err error) {
err = c.queryAPI(http.MethodGet, c.getURL(orgTarget, c.Org, quotaTarget), &q)
return
}
// RetrieveCVE requests information about an assigned CVE ID.
func (c *Client) RetrieveCVE(id string) (AssignedCVE, error) {
req, err := c.createRequest(http.MethodGet, fmt.Sprintf("%s/api/cve-id/%s", c.Endpoint, id))
if err != nil {
return AssignedCVE{}, err
}
// RetrieveID requests information about an assigned CVE ID.
func (c *Client) RetrieveID(id string) (cve *AssignedCVE, err error) {
err = c.queryAPI(http.MethodGet, c.getURL(cveIDTarget, id), &cve)
return
}
var cve AssignedCVE
err = c.sendRequest(req, nil, &cve)
if err != nil {
return AssignedCVE{}, err
}
return cve, nil
type Org struct {
Name string `json:"name"`
ShortName string `json:"short_name"`
UUID string `json:"UUID"`
}
// RetrieveOrg requests information about an organization.
func (c *Client) RetrieveOrg() (org *Org, err error) {
err = c.queryAPI(http.MethodGet, c.getURL(orgTarget, c.Org), &org)
return
}
// ListOptions contains filters to be used when requesting a list of
@ -234,6 +227,9 @@ func (o ListOptions) String() string {
func (o *ListOptions) getURLParams() url.Values {
params := url.Values{}
if o == nil {
return params
}
if o.State != "" {
params.Set("state", o.State)
}
@ -262,14 +258,11 @@ type listOrgCVEsResponse struct {
}
func (c Client) createListOrgCVEsRequest(opts *ListOptions, page int) (*http.Request, error) {
req, err := c.createRequest(http.MethodGet, fmt.Sprintf("%s/api/cve-id", c.Endpoint))
req, err := c.createRequest(http.MethodGet, c.getURL(cveIDTarget))
if err != nil {
return nil, err
}
params := url.Values{}
if opts != nil {
params = opts.getURLParams()
}
params := opts.getURLParams()
if page > 0 {
params.Set("page", fmt.Sprint(page))
}
@ -301,6 +294,18 @@ func (c *Client) ListOrgCVEs(opts *ListOptions) (AssignedCVEList, error) {
return cves, nil
}
func (c *Client) queryAPI(method, url string, response any) error {
req, err := c.createRequest(method, url)
if err != nil {
return err
}
err = c.sendRequest(req, nil, response)
if err != nil {
return err
}
return nil
}
var (
headerApiUser = "CVE-API-USER"
headerApiOrg = "CVE-API-ORG"
@ -334,7 +339,7 @@ func (c *Client) sendRequest(req *http.Request, checkStatus func(int) bool, resu
}
}
if !checkStatus(resp.StatusCode) {
return fmt.Errorf("HTTP request %s %q returned error: %w", req.Method, req.URL, extractError(resp))
return fmt.Errorf("HTTP request %s %q returned error: %v", req.Method, req.URL, extractError(resp))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
@ -346,6 +351,16 @@ func (c *Client) sendRequest(req *http.Request, checkStatus func(int) bool, resu
return nil
}
var (
cveIDTarget = "cve-id"
orgTarget = "org"
quotaTarget = "id_quota"
)
func (c *Client) getURL(targets ...string) string {
return fmt.Sprintf("%s/api/%s", c.Endpoint, strings.Join(targets, "/"))
}
type apiError struct {
Error string `json:"error"`
Message string `json:"message"`

Просмотреть файл

@ -29,11 +29,16 @@ var defaultTestCVE = newTestCVE("CVE-2022-0000", cveschema.StateReserved, "2022"
var defaultTestCVEs = AssignedCVEList{
defaultTestCVE, newTestCVE("CVE-2022-0001", cveschema.StateReserved, "2022"),
}
var defaultTestQuota = Quota{
var defaultTestQuota = &Quota{
Quota: 10,
Reserved: 3,
Available: 7,
}
var defaultTestOrg = &Org{
Name: "An Org",
ShortName: testApiOrg,
UUID: "000-000-000",
}
var (
testTime2022 = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
@ -188,8 +193,11 @@ var (
retrieveQuotaQuery = func(c *Client) (any, error) {
return c.RetrieveQuota()
}
retrieveCVEQuery = func(c *Client) (any, error) {
return c.RetrieveCVE(defaultTestCVE.ID)
retrieveIDQuery = func(c *Client) (any, error) {
return c.RetrieveID(defaultTestCVE.ID)
}
retrieveOrgQuery = func(c *Client) (any, error) {
return c.RetrieveOrg()
}
listOrgCVEsQuery = func(c *Client) (any, error) {
return c.ListOrgCVEs(&ListOptions{})
@ -236,13 +244,22 @@ func TestAllSuccess(t *testing.T) {
want: defaultTestQuota,
},
{
name: "RetrieveCVE",
query: retrieveCVEQuery,
name: "RetrieveID",
query: retrieveIDQuery,
mockStatus: http.StatusOK,
mockResponse: defaultTestCVE,
wantHTTPMethod: http.MethodGet,
wantPath: "/api/cve-id/CVE-2022-0000",
want: defaultTestCVE,
want: &defaultTestCVE,
},
{
name: "RetrieveOrg",
query: retrieveOrgQuery,
mockStatus: http.StatusOK,
mockResponse: defaultTestOrg,
wantHTTPMethod: http.MethodGet,
wantPath: "/api/org/test_api_org",
want: defaultTestOrg,
},
{
name: "ListOrgCVEs/single page",
@ -296,8 +313,12 @@ func TestAllFail(t *testing.T) {
query: retrieveQuotaQuery,
},
{
name: "RetrieveCVE",
query: retrieveCVEQuery,
name: "RetrieveID",
query: retrieveIDQuery,
},
{
name: "RetrieveOrg",
query: retrieveOrgQuery,
},
{
name: "ListOrgCVEs",