This repository has been archived by the owner on Oct 14, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement simple client for Aqua API (#15)
Signed-off-by: Daniel Pacak <[email protected]>
- Loading branch information
1 parent
3af2b02
commit 1c436a8
Showing
2 changed files
with
222 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package client | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"time" | ||
) | ||
|
||
var ErrNotFound = errors.New("not found") | ||
|
||
type client struct { | ||
baseURL string | ||
username string | ||
password string | ||
httpClient *http.Client | ||
} | ||
|
||
func (c *client) newGetRequest(url string) (*http.Request, error) { | ||
req, err := http.NewRequest(http.MethodGet, url, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
req.Header.Add("Content-Type", "application/json; charset=UTF-8") | ||
req.SetBasicAuth(c.username, c.password) | ||
return req, nil | ||
} | ||
|
||
type Clientset interface { | ||
Registries() RegistriesInterface | ||
Images() ImagesInterface | ||
} | ||
|
||
type ImagesInterface interface { | ||
Vulnerabilities(registry, repo, tag string) (VulnerabilitiesResponse, error) | ||
} | ||
|
||
type RegistriesInterface interface { | ||
List() ([]RegistryResponse, error) | ||
} | ||
|
||
type Client struct { | ||
registries *Registries | ||
images *Images | ||
} | ||
|
||
type Authorization struct { | ||
Basic *UsernameAndPassword | ||
} | ||
|
||
type UsernameAndPassword struct { | ||
Username string | ||
Password string | ||
} | ||
|
||
func NewClient(baseURL string, authorization Authorization) *Client { | ||
httpClient := &http.Client{ | ||
Timeout: 30 * time.Second, | ||
} | ||
client := &client{ | ||
baseURL: baseURL, | ||
username: authorization.Basic.Username, | ||
password: authorization.Basic.Password, | ||
httpClient: httpClient, | ||
} | ||
|
||
return &Client{ | ||
images: &Images{ | ||
client: client, | ||
}, | ||
registries: &Registries{ | ||
client: client, | ||
}, | ||
} | ||
} | ||
|
||
func (c *Client) Images() ImagesInterface { | ||
return c.images | ||
} | ||
|
||
func (c *Client) Registries() RegistriesInterface { | ||
return c.registries | ||
} | ||
|
||
type Images struct { | ||
client *client | ||
} | ||
|
||
func (i *Images) Vulnerabilities(registry, repo, tag string) (VulnerabilitiesResponse, error) { | ||
url := fmt.Sprintf("%s/api/v2/images/%s/%s/%s/vulnerabilities", i.client.baseURL, registry, repo, tag) | ||
|
||
req, err := i.client.newGetRequest(url) | ||
if err != nil { | ||
return VulnerabilitiesResponse{}, err | ||
} | ||
|
||
resp, err := i.client.httpClient.Do(req) | ||
if err != nil { | ||
return VulnerabilitiesResponse{}, err | ||
} | ||
if resp.StatusCode == http.StatusNotFound { | ||
return VulnerabilitiesResponse{}, ErrNotFound | ||
} | ||
if resp.StatusCode != http.StatusOK { | ||
return VulnerabilitiesResponse{}, fmt.Errorf("unexpected response status: %s", resp.Status) | ||
} | ||
var vulnerabilitiesResponse VulnerabilitiesResponse | ||
err = json.NewDecoder(resp.Body).Decode(&vulnerabilitiesResponse) | ||
if err != nil { | ||
return VulnerabilitiesResponse{}, err | ||
} | ||
|
||
return vulnerabilitiesResponse, nil | ||
} | ||
|
||
type Registries struct { | ||
client *client | ||
} | ||
|
||
func (r *Registries) List() ([]RegistryResponse, error) { | ||
url := fmt.Sprintf("%s/api/v1/registries", r.client.baseURL) | ||
req, err := r.client.newGetRequest(url) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
resp, err := r.client.httpClient.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if resp.StatusCode != http.StatusOK { | ||
return nil, fmt.Errorf("unexpected response status: %s", resp.Status) | ||
} | ||
var listRegistriesResponse []RegistryResponse | ||
err = json.NewDecoder(resp.Body).Decode(&listRegistriesResponse) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return listRegistriesResponse, nil | ||
} | ||
|
||
type VulnerabilitiesResponse struct { | ||
Count int `json:"count"` | ||
Results []VulnerabilitiesResponseResult `json:"result"` | ||
} | ||
|
||
type VulnerabilitiesResponseResult struct { | ||
Registry string `json:"registry"` | ||
ImageRepositoryName string `json:"image_repository_name"` | ||
Resource Resource `json:"resource"` | ||
Name string `json:"name"` // e.g. CVE-2020-3910 | ||
Description string `json:"description"` | ||
AquaSeverity string `json:"aqua_severity"` | ||
AquaVectors string `json:"aqua_vectors"` | ||
AquaScoringSystem string `json:"aqua_scoring_system"` | ||
FixVersion string `json:"fix_version"` | ||
} | ||
|
||
type Resource struct { | ||
Type string `json:"type"` // e.g. package | ||
Format string `json:"format"` // e.g. deb | ||
Path string `json:"path"` | ||
Name string `json:"name"` // e.g. libxml2 | ||
Version string `json:"version"` // e.g. 2.9.4+dfsg1-7+b3 | ||
} | ||
|
||
type RegistryResponse struct { | ||
Name string `json:"name"` | ||
Type string `json:"type"` // e.g. HUB, API | ||
Description string `json:"description"` | ||
URL string `json:"url"` | ||
Prefixes []string `json:"prefixes"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package client | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestClient(t *testing.T) { | ||
if testing.Short() { | ||
t.Skip("Run this test manually") | ||
} | ||
|
||
c := NewClient("http://aqua.domain", Authorization{ | ||
Basic: &UsernameAndPassword{"administrator", "Password12345"}}) | ||
|
||
t.Run("Should list registries", func(t *testing.T) { | ||
registries, err := c.Registries().List() | ||
require.NoError(t, err) | ||
for _, registry := range registries { | ||
t.Logf("registry: %+v", registry) | ||
} | ||
}) | ||
|
||
t.Run("Should get vulnerabilities from Ad Hoc Scans registry", func(t *testing.T) { | ||
resp, err := c.Images().Vulnerabilities("Ad Hoc Scans", "core.harbor.domain/library/nginx", "1.16") | ||
require.NoError(t, err) | ||
for _, vulnerability := range resp.Results { | ||
t.Logf("vulnerability: %+v", vulnerability) | ||
} | ||
}) | ||
|
||
t.Run("Should return error when vulnerabilities report cannot be found", func(t *testing.T) { | ||
_, err := c.Images().Vulnerabilities("Ad Hoc Scans", "core.harbor.domain/library/nginx", "unknown") | ||
assert.EqualError(t, err, ErrNotFound.Error()) | ||
}) | ||
|
||
t.Run("Should get vulnerabilities from Harbor registry", func(t *testing.T) { | ||
vr, err := c.Images().Vulnerabilities("Harbor", "library/nginx", "1.16") | ||
require.NoError(t, err) | ||
for _, vulnerability := range vr.Results { | ||
t.Logf("vulnerability: %+v", vulnerability) | ||
} | ||
}) | ||
|
||
} |