Merge pull request #82 from liangchenye/getvulns
*: list vulnerabilities by namespace
This commit is contained in:
commit
7bea9cbe45
@ -8,6 +8,7 @@
|
|||||||
- [Namespaces](#namespaces)
|
- [Namespaces](#namespaces)
|
||||||
- [GET](#get-namespaces)
|
- [GET](#get-namespaces)
|
||||||
- [Vulnerabilities](#vulnerabilities)
|
- [Vulnerabilities](#vulnerabilities)
|
||||||
|
- [List](#get-namespacesnsnamevulnerabilities)
|
||||||
- [POST](#post-namespacesnamevulnerabilities)
|
- [POST](#post-namespacesnamevulnerabilities)
|
||||||
- [GET](#get-namespacesnsnamevulnerabilitiesvulnname)
|
- [GET](#get-namespacesnsnamevulnerabilitiesvulnname)
|
||||||
- [PUT](#put-namespacesnsnamevulnerabilitiesvulnname)
|
- [PUT](#put-namespacesnsnamevulnerabilitiesvulnname)
|
||||||
@ -196,6 +197,61 @@ Server: clair
|
|||||||
|
|
||||||
## Vulnerabilities
|
## Vulnerabilities
|
||||||
|
|
||||||
|
#### GET /namespaces/`:nsName`/vulnerabilities
|
||||||
|
|
||||||
|
###### Description
|
||||||
|
|
||||||
|
The GET route for the Vulnerabilities resource displays the vulnerabilities data for a given namespace.
|
||||||
|
|
||||||
|
###### Query Parameters
|
||||||
|
|
||||||
|
| Name | Type | Required | Description |
|
||||||
|
|---------|------|----------|------------------------------------------------------------|
|
||||||
|
| limit | int | required | Limits the amount of the vunlerabilities data for a given namespace. |
|
||||||
|
| page | int | required | Displays the specific page of the vunlerabilities data for a given namespace. |
|
||||||
|
|
||||||
|
###### Example Request
|
||||||
|
|
||||||
|
```json
|
||||||
|
GET http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities?limit=2 HTTP/1.1
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Example Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json;charset=utf-8
|
||||||
|
Server: clair
|
||||||
|
|
||||||
|
{
|
||||||
|
"Vulnerabilities": [
|
||||||
|
{
|
||||||
|
"Name": "CVE-1999-1332",
|
||||||
|
"Namespace": "debian:8",
|
||||||
|
"Description": "gzexe in the gzip package on Red Hat Linux 5.0 and earlier allows local users to overwrite files of other users via a symlink attack on a temporary file.",
|
||||||
|
"Link": "https://security-tracker.debian.org/tracker/CVE-1999-1332",
|
||||||
|
"Severity": "Low"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "CVE-1999-1572",
|
||||||
|
"Namespace": "debian:8",
|
||||||
|
"Description": "cpio on FreeBSD 2.1.0, Debian GNU/Linux 3.0, and possibly other operating systems, uses a 0 umask when creating files using the -O (archive) or -F options, which creates the files with mode 0666 and allows local users to read or overwrite those files.",
|
||||||
|
"Link": "https://security-tracker.debian.org/tracker/CVE-1999-1572",
|
||||||
|
"Severity": "Low",
|
||||||
|
"Metadata": {
|
||||||
|
"NVD": {
|
||||||
|
"CVSSv2": {
|
||||||
|
"Score": 2.1,
|
||||||
|
"Vectors": "AV:L/AC:L/Au:N/C:P/I:N"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"NextPage":"gAAAAABW1ABiOlm6KMDKYFE022bEy_IFJdm4ExxTNuJZMN0Eycn0Sut2tOH9bDB4EWGy5s6xwATUHiG-6JXXaU5U32sBs6_DmA=="
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### POST /namespaces/`:name`/vulnerabilities
|
#### POST /namespaces/`:name`/vulnerabilities
|
||||||
|
|
||||||
###### Description
|
###### Description
|
||||||
|
@ -215,7 +215,8 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica
|
|||||||
|
|
||||||
var nextPageStr string
|
var nextPageStr string
|
||||||
if nextPage != database.NoVulnerabilityNotificationPage {
|
if nextPage != database.NoVulnerabilityNotificationPage {
|
||||||
nextPageStr = pageNumberToToken(nextPage, key)
|
nextPageBytes, _ := tokenMarshal(nextPage, key)
|
||||||
|
nextPageStr = string(nextPageBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
var created, notified, deleted string
|
var created, notified, deleted string
|
||||||
@ -274,8 +275,10 @@ type NamespaceEnvelope struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VulnerabilityEnvelope struct {
|
type VulnerabilityEnvelope struct {
|
||||||
Vulnerability *Vulnerability `json:"Vulnerability,omitempty"`
|
Vulnerability *Vulnerability `json:"Vulnerability,omitempty"`
|
||||||
Error *Error `json:"Error,omitempty"`
|
Vulnerabilities *[]Vulnerability `json:"Vulnerabilities,omitempty"`
|
||||||
|
NextPage string `json:"NextPage,omitempty"`
|
||||||
|
Error *Error `json:"Error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotificationEnvelope struct {
|
type NotificationEnvelope struct {
|
||||||
@ -289,30 +292,23 @@ type FeatureEnvelope struct {
|
|||||||
Error *Error `json:"Error,omitempty"`
|
Error *Error `json:"Error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func tokenToPageNumber(token, key string) (database.VulnerabilityNotificationPageNumber, error) {
|
func tokenUnmarshal(token string, key string, v interface{}) error {
|
||||||
k, _ := fernet.DecodeKey(key)
|
k, _ := fernet.DecodeKey(key)
|
||||||
msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k})
|
msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k})
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
return database.VulnerabilityNotificationPageNumber{}, errors.New("invalid or expired pagination token")
|
return errors.New("invalid or expired pagination token")
|
||||||
}
|
}
|
||||||
|
|
||||||
page := database.VulnerabilityNotificationPageNumber{}
|
return json.NewDecoder(bytes.NewBuffer(msg)).Decode(&v)
|
||||||
err := json.NewDecoder(bytes.NewBuffer(msg)).Decode(&page)
|
|
||||||
return page, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pageNumberToToken(page database.VulnerabilityNotificationPageNumber, key string) string {
|
func tokenMarshal(v interface{}, key string) ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := json.NewEncoder(&buf).Encode(page)
|
err := json.NewEncoder(&buf).Encode(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("failed to encode VulnerabilityNotificationPageNumber")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
k, _ := fernet.DecodeKey(key)
|
k, _ := fernet.DecodeKey(key)
|
||||||
tokenBytes, err := fernet.EncryptAndSign(buf.Bytes(), k)
|
return fernet.EncryptAndSign(buf.Bytes(), k)
|
||||||
if err != nil {
|
|
||||||
log.Fatal("failed to encrypt VulnerabilityNotificationpageNumber")
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(tokenBytes)
|
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ func NewRouter(ctx *context.RouteContext) *httprouter.Router {
|
|||||||
router.GET("/namespaces", context.HTTPHandler(getNamespaces, ctx))
|
router.GET("/namespaces", context.HTTPHandler(getNamespaces, ctx))
|
||||||
|
|
||||||
// Vulnerabilities
|
// Vulnerabilities
|
||||||
|
router.GET("/namespaces/:namespaceName/vulnerabilities", context.HTTPHandler(getVulnerabilities, ctx))
|
||||||
router.POST("/namespaces/:namespaceName/vulnerabilities", context.HTTPHandler(postVulnerability, ctx))
|
router.POST("/namespaces/:namespaceName/vulnerabilities", context.HTTPHandler(postVulnerability, ctx))
|
||||||
router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(getVulnerability, ctx))
|
router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(getVulnerability, ctx))
|
||||||
router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(putVulnerability, ctx))
|
router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(putVulnerability, ctx))
|
||||||
|
@ -38,6 +38,7 @@ const (
|
|||||||
getLayerRoute = "v1/getLayer"
|
getLayerRoute = "v1/getLayer"
|
||||||
deleteLayerRoute = "v1/deleteLayer"
|
deleteLayerRoute = "v1/deleteLayer"
|
||||||
getNamespacesRoute = "v1/getNamespaces"
|
getNamespacesRoute = "v1/getNamespaces"
|
||||||
|
getVulnerabilitiesRoute = "v1/getVulnerabilities"
|
||||||
postVulnerabilityRoute = "v1/postVulnerability"
|
postVulnerabilityRoute = "v1/postVulnerability"
|
||||||
getVulnerabilityRoute = "v1/getVulnerability"
|
getVulnerabilityRoute = "v1/getVulnerability"
|
||||||
putVulnerabilityRoute = "v1/putVulnerability"
|
putVulnerabilityRoute = "v1/putVulnerability"
|
||||||
@ -184,6 +185,68 @@ func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params,
|
|||||||
return getNamespacesRoute, http.StatusOK
|
return getNamespacesRoute, http.StatusOK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
|
||||||
|
query := r.URL.Query()
|
||||||
|
|
||||||
|
limitStrs, limitExists := query["limit"]
|
||||||
|
if !limitExists {
|
||||||
|
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"must provide limit query parameter"}})
|
||||||
|
return getVulnerabilitiesRoute, http.StatusBadRequest
|
||||||
|
}
|
||||||
|
limit, err := strconv.Atoi(limitStrs[0])
|
||||||
|
if err != nil {
|
||||||
|
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid limit format: " + err.Error()}})
|
||||||
|
return getVulnerabilitiesRoute, http.StatusBadRequest
|
||||||
|
} else if limit < 0 {
|
||||||
|
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"limit value should not be less than zero"}})
|
||||||
|
return getVulnerabilitiesRoute, http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
page := 0
|
||||||
|
pageStrs, pageExists := query["page"]
|
||||||
|
if pageExists {
|
||||||
|
err = tokenUnmarshal(pageStrs[0], ctx.Config.PaginationKey, &page)
|
||||||
|
if err != nil {
|
||||||
|
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
|
||||||
|
return getNotificationRoute, http.StatusBadRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := p.ByName("namespaceName")
|
||||||
|
if namespace == "" {
|
||||||
|
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"namespace should not be empty"}})
|
||||||
|
return getNotificationRoute, http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
dbVulns, nextPage, err := ctx.Store.ListVulnerabilities(namespace, limit, page)
|
||||||
|
if err == cerrors.ErrNotFound {
|
||||||
|
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
|
||||||
|
return getVulnerabilityRoute, http.StatusNotFound
|
||||||
|
} else if err != nil {
|
||||||
|
writeResponse(w, r, http.StatusInternalServerError, VulnerabilityEnvelope{Error: &Error{err.Error()}})
|
||||||
|
return getVulnerabilitiesRoute, http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
var vulns []Vulnerability
|
||||||
|
for _, dbVuln := range dbVulns {
|
||||||
|
vuln := VulnerabilityFromDatabaseModel(dbVuln, false)
|
||||||
|
vulns = append(vulns, vuln)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextPageStr string
|
||||||
|
if nextPage != -1 {
|
||||||
|
nextPageBytes, err := tokenMarshal(nextPage, ctx.Config.PaginationKey)
|
||||||
|
if err != nil {
|
||||||
|
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}})
|
||||||
|
return getNotificationRoute, http.StatusBadRequest
|
||||||
|
}
|
||||||
|
nextPageStr = string(nextPageBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeResponse(w, r, http.StatusOK, VulnerabilityEnvelope{Vulnerabilities: &vulns, NextPage: nextPageStr})
|
||||||
|
return getVulnerabilitiesRoute, http.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
|
func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
|
||||||
request := VulnerabilityEnvelope{}
|
request := VulnerabilityEnvelope{}
|
||||||
err := decodeJSON(r, &request)
|
err := decodeJSON(r, &request)
|
||||||
@ -385,14 +448,19 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
|
|||||||
page := database.VulnerabilityNotificationFirstPage
|
page := database.VulnerabilityNotificationFirstPage
|
||||||
pageStrs, pageExists := query["page"]
|
pageStrs, pageExists := query["page"]
|
||||||
if pageExists {
|
if pageExists {
|
||||||
page, err = tokenToPageNumber(pageStrs[0], ctx.Config.PaginationKey)
|
err := tokenUnmarshal(pageStrs[0], ctx.Config.PaginationKey, &page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
|
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
|
||||||
return getNotificationRoute, http.StatusBadRequest
|
return getNotificationRoute, http.StatusBadRequest
|
||||||
}
|
}
|
||||||
pageToken = pageStrs[0]
|
pageToken = pageStrs[0]
|
||||||
} else {
|
} else {
|
||||||
pageToken = pageNumberToToken(page, ctx.Config.PaginationKey)
|
pageTokenBytes, err := tokenMarshal(page, ctx.Config.PaginationKey)
|
||||||
|
if err != nil {
|
||||||
|
writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}})
|
||||||
|
return getNotificationRoute, http.StatusBadRequest
|
||||||
|
}
|
||||||
|
pageToken = string(pageTokenBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
dbNotification, nextPage, err := ctx.Store.GetNotification(p.ByName("notificationName"), limit, page)
|
dbNotification, nextPage, err := ctx.Store.GetNotification(p.ByName("notificationName"), limit, page)
|
||||||
|
@ -60,6 +60,12 @@ type Datastore interface {
|
|||||||
DeleteLayer(name string) error
|
DeleteLayer(name string) error
|
||||||
|
|
||||||
// # Vulnerability
|
// # Vulnerability
|
||||||
|
// ListVulnerabilities returns the list of vulnerabilies of a certain Namespace.
|
||||||
|
// The Limit and page parameters are used to paginate the return list.
|
||||||
|
// The first given page should be 0. The function will then return the next available page.
|
||||||
|
// If there is no more page, -1 has to be returned.
|
||||||
|
ListVulnerabilities(namespaceName string, limit int, page int) ([]Vulnerability, int, error)
|
||||||
|
|
||||||
// InsertVulnerabilities stores the given Vulnerabilities in the database, updating them if
|
// InsertVulnerabilities stores the given Vulnerabilities in the database, updating them if
|
||||||
// necessary. A vulnerability is uniquely identified by its Namespace and its Name.
|
// necessary. A vulnerability is uniquely identified by its Namespace and its Name.
|
||||||
// The FixedIn field may only contain a partial list of Features that are affected by the
|
// The FixedIn field may only contain a partial list of Features that are affected by the
|
||||||
|
@ -38,7 +38,8 @@ const (
|
|||||||
UNION
|
UNION
|
||||||
SELECT id FROM new_namespace`
|
SELECT id FROM new_namespace`
|
||||||
|
|
||||||
listNamespace = `SELECT id, name FROM Namespace`
|
searchNamespace = `SELECT id FROM Namespace WHERE name = $1`
|
||||||
|
listNamespace = `SELECT id, name FROM Namespace`
|
||||||
|
|
||||||
// feature.go
|
// feature.go
|
||||||
soiFeature = `
|
soiFeature = `
|
||||||
@ -144,6 +145,10 @@ const (
|
|||||||
searchVulnerabilityForUpdate = ` FOR UPDATE OF v`
|
searchVulnerabilityForUpdate = ` FOR UPDATE OF v`
|
||||||
searchVulnerabilityByNamespaceAndName = ` WHERE n.name = $1 AND v.name = $2 AND v.deleted_at IS NULL`
|
searchVulnerabilityByNamespaceAndName = ` WHERE n.name = $1 AND v.name = $2 AND v.deleted_at IS NULL`
|
||||||
searchVulnerabilityByID = ` WHERE v.id = $1`
|
searchVulnerabilityByID = ` WHERE v.id = $1`
|
||||||
|
searchVulnerabilityByNamespace = ` WHERE n.name = $1 AND v.deleted_at IS NULL
|
||||||
|
AND v.id >= $2
|
||||||
|
ORDER BY v.id
|
||||||
|
LIMIT $3`
|
||||||
|
|
||||||
searchVulnerabilityFixedIn = `
|
searchVulnerabilityFixedIn = `
|
||||||
SELECT vfif.version, f.id, f.Name
|
SELECT vfif.version, f.id, f.Name
|
||||||
|
@ -28,6 +28,61 @@ import (
|
|||||||
"github.com/guregu/null/zero"
|
"github.com/guregu/null/zero"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, startID int) ([]database.Vulnerability, int, error) {
|
||||||
|
defer observeQueryTime("listVulnerabilities", "all", time.Now())
|
||||||
|
|
||||||
|
// Query Namespace.
|
||||||
|
var id int
|
||||||
|
err := pgSQL.QueryRow(searchNamespace, namespaceName).Scan(&id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, handleError("searchNamespace", err)
|
||||||
|
} else if id == 0 {
|
||||||
|
return nil, -1, cerrors.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query.
|
||||||
|
query := searchVulnerabilityBase + searchVulnerabilityByNamespace
|
||||||
|
rows, err := pgSQL.Query(query, namespaceName, startID, limit+1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, handleError("searchVulnerabilityByNamespace", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var vulns []database.Vulnerability
|
||||||
|
nextID := -1
|
||||||
|
size := 0
|
||||||
|
// Scan query.
|
||||||
|
for rows.Next() {
|
||||||
|
var vulnerability database.Vulnerability
|
||||||
|
|
||||||
|
err := rows.Scan(
|
||||||
|
&vulnerability.ID,
|
||||||
|
&vulnerability.Name,
|
||||||
|
&vulnerability.Namespace.ID,
|
||||||
|
&vulnerability.Namespace.Name,
|
||||||
|
&vulnerability.Description,
|
||||||
|
&vulnerability.Link,
|
||||||
|
&vulnerability.Severity,
|
||||||
|
&vulnerability.Metadata,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, handleError("searchVulnerabilityByNamespace.Scan()", err)
|
||||||
|
}
|
||||||
|
size++
|
||||||
|
if size > limit {
|
||||||
|
nextID = vulnerability.ID
|
||||||
|
} else {
|
||||||
|
vulns = append(vulns, vulnerability)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, -1, handleError("searchVulnerabilityByNamespace.Rows()", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vulns, nextID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (pgSQL *pgSQL) FindVulnerability(namespaceName, name string) (database.Vulnerability, error) {
|
func (pgSQL *pgSQL) FindVulnerability(namespaceName, name string) (database.Vulnerability, error) {
|
||||||
return findVulnerability(pgSQL, namespaceName, name, false)
|
return findVulnerability(pgSQL, namespaceName, name, false)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user