update api, update testcases

Signed-off-by: liang chenye <liangchenye@huawei.com>
This commit is contained in:
liang chenye 2016-05-30 17:31:25 +08:00
parent 0a997145ed
commit 3c3a96e71d
25 changed files with 359 additions and 227 deletions

View File

@ -129,18 +129,18 @@ Server: clair
{
"Layer": {
"Name": "17675ec01494d651e1ccf81dc9cf63959ebfeed4f978fddb1666b6ead008ed52",
"NamespaceName": "debian:8",
"Namespaces": [{"Name": "debian", "Version": "8"}],
"ParentName": "140f9bdfeb9784cf8730e9dab5dd12fbd704151cf555ac8cae650451794e5ac2",
"IndexedByVersion": 1,
"Features": [
{
"Name": "coreutils",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Version": "8.23-4",
"Vulnerabilities": [
{
"Name": "CVE-2014-9471",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Description": "The parse_datetime function in GNU coreutils allows remote attackers to cause a denial of service (crash) or possibly execute arbitrary code via a crafted date string, as demonstrated by the \"--date=TZ=\"123\"345\" @1\" string to the touch or date command.",
"Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471",
"Severity": "Low",
@ -196,15 +196,15 @@ Server: clair
{
"Namespaces": [
{ "Name": "debian:8" },
{ "Name": "debian:9" }
{ "ID": "gAAAAABXTQKgma_TLKq0wr1D6wVB507N3fi9wsWMUypYOSXTDVxQ8OR5L5S6PqZ9Wh0IzWojnVmlspyTL4cyjytyra7U9vAHMA==", "Name": "debian", "Version": "8" },
{ "ID": "gAAAAABXTQPZmOFlOR8zzuhv8Y2fD7HbUY8O6z_Py2vibB9uveWZoycSY1HDIkcf7lN_UDynom4kWubFS4h9KBCbWwjNIqacsw==", "Name": "debian", "Version": "9" }
]
}
```
## Vulnerabilities
#### GET /namespaces/`:nsName`/vulnerabilities
#### GET /namespaces/`:nsID`/vulnerabilities
###### Description
@ -220,7 +220,7 @@ The GET route for the Vulnerabilities resource displays the vulnerabilities data
###### Example Request
```json
GET http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities?limit=2 HTTP/1.1
GET http://localhost:6060/v1/namespaces/gAAAAABXTQKgma_TLKq0wr1D6wVB507N3fi9wsWMUypYOSXTDVxQ8OR5L5S6PqZ9Wh0IzWojnVmlspyTL4cyjytyra7U9vAHMA==/vulnerabilities?limit=2 HTTP/1.1
```
###### Example Response
@ -234,14 +234,14 @@ Server: clair
"Vulnerabilities": [
{
"Name": "CVE-1999-1332",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "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",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "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",
@ -259,7 +259,7 @@ Server: clair
}
```
#### POST /namespaces/`:name`/vulnerabilities
#### POST /namespaces/`:nsID`/vulnerabilities
###### Description
@ -268,12 +268,12 @@ The POST route for the Vulnerabilities resource creates a new Vulnerability.
###### Example Request
```json
POST http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities HTTP/1.1
POST http://localhost:6060/v1/namespaces/gAAAAABXTQKgma_TLKq0wr1D6wVB507N3fi9wsWMUypYOSXTDVxQ8OR5L5S6PqZ9Wh0IzWojnVmlspyTL4cyjytyra7U9vAHMA==/vulnerabilities HTTP/1.1
{
"Vulnerability": {
"Name": "CVE-2014-9471",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471",
"Description": "The parse_datetime function in GNU coreutils allows remote attackers to cause a denial of service (crash) or possibly execute arbitrary code via a crafted date string, as demonstrated by the \"--date=TZ=\"123\"345\" @1\" string to the touch or date command.",
"Severity": "Low",
@ -288,7 +288,7 @@ POST http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities HTTP/1.1
"FixedIn": [
{
"Name": "coreutils",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Version": "8.23-1"
}
]
@ -306,7 +306,7 @@ Server: clair
{
"Vulnerability": {
"Name": "CVE-2014-9471",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471",
"Description": "The parse_datetime function in GNU coreutils allows remote attackers to cause a denial of service (crash) or possibly execute arbitrary code via a crafted date string, as demonstrated by the \"--date=TZ=\"123\"345\" @1\" string to the touch or date command.",
"Severity": "Low",
@ -321,7 +321,7 @@ Server: clair
"FixedIn": [
{
"Name": "coreutils",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Version": "8.23-1"
}
]
@ -329,7 +329,7 @@ Server: clair
}
```
#### GET /namespaces/`:nsName`/vulnerabilities/`:vulnName`
#### GET /namespaces/`:nsID`/vulnerabilities/`:vulnName`
###### Description
@ -344,7 +344,7 @@ The GET route for the Vulnerabilities resource displays the current data for a g
###### Example Request
```json
GET http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities/CVE-2014-9471?fixedIn HTTP/1.1
GET http://localhost:6060/v1/namespaces/gAAAAABXTQKgma_TLKq0wr1D6wVB507N3fi9wsWMUypYOSXTDVxQ8OR5L5S6PqZ9Wh0IzWojnVmlspyTL4cyjytyra7U9vAHMA==/vulnerabilities/CVE-2014-9471?fixedIn HTTP/1.1
```
###### Example Response
@ -357,7 +357,7 @@ Server: clair
{
"Vulnerability": {
"Name": "CVE-2014-9471",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471",
"Description": "The parse_datetime function in GNU coreutils allows remote attackers to cause a denial of service (crash) or possibly execute arbitrary code via a crafted date string, as demonstrated by the \"--date=TZ=\"123\"345\" @1\" string to the touch or date command.",
"Severity": "Low",
@ -372,7 +372,7 @@ Server: clair
"FixedIn": [
{
"Name": "coreutils",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Version": "8.23-1"
}
]
@ -380,7 +380,7 @@ Server: clair
}
```
#### PUT /namespaces/`:nsName`/vulnerabilities/`:vulnName`
#### PUT /namespaces/`:nsID`/vulnerabilities/`:vulnName`
###### Description
@ -392,12 +392,12 @@ If this vulnerability was inserted by a Fetcher, changes may be lost when the Fe
###### Example Request
```json
PUT http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities/CVE-2014-9471
PUT http://localhost:6060/v1/namespaces/gAAAAABXTQKgma_TLKq0wr1D6wVB507N3fi9wsWMUypYOSXTDVxQ8OR5L5S6PqZ9Wh0IzWojnVmlspyTL4cyjytyra7U9vAHMA==/vulnerabilities/CVE-2014-9471
{
"Vulnerability": {
"Name": "CVE-2014-9471",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471",
"Description": "The parse_datetime function in GNU coreutils allows remote attackers to cause a denial of service (crash) or possibly execute arbitrary code via a crafted date string, as demonstrated by the \"--date=TZ=\"123\"345\" @1\" string to the touch or date command.",
"Severity": "Low",
@ -422,7 +422,7 @@ Server: clair
{
"Vulnerability": {
"Name": "CVE-2014-9471",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Link": "https://security-tracker.debian.org/tracker/CVE-2014-9471",
"Description": "The parse_datetime function in GNU coreutils allows remote attackers to cause a denial of service (crash) or possibly execute arbitrary code via a crafted date string, as demonstrated by the \"--date=TZ=\"123\"345\" @1\" string to the touch or date command.",
"Severity": "Low",
@ -439,7 +439,7 @@ Server: clair
```
#### DELETE /namespaces/`:nsName`/vulnerabilities/`:vulnName`
#### DELETE /namespaces/`:nsID`/vulnerabilities/`:vulnName`
###### Description
@ -449,7 +449,7 @@ If this vulnerability was inserted by a Fetcher, it may be re-inserted when the
###### Example Request
```json
GET http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities/CVE-2014-9471 HTTP/1.1
GET http://localhost:6060/v1/namespaces/gAAAAABXTQKgma_TLKq0wr1D6wVB507N3fi9wsWMUypYOSXTDVxQ8OR5L5S6PqZ9Wh0IzWojnVmlspyTL4cyjytyra7U9vAHMA==/vulnerabilities/CVE-2014-9471 HTTP/1.1
```
###### Example Response
@ -461,7 +461,7 @@ Server: clair
## Fixes
#### GET /namespaces/`:nsName`/vulnerabilities/`:vulnName`/fixes
#### GET /namespaces/`:nsID`/vulnerabilities/`:vulnName`/fixes
###### Description
@ -470,7 +470,7 @@ The GET route for the Fixes resource displays the list of Features that fix the
###### Example Request
```json
GET http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities/CVE-2014-9471/fixes HTTP/1.1
GET http://localhost:6060/v1/namespaces/gAAAAABXTQKgma_TLKq0wr1D6wVB507N3fi9wsWMUypYOSXTDVxQ8OR5L5S6PqZ9Wh0IzWojnVmlspyTL4cyjytyra7U9vAHMA==/vulnerabilities/CVE-2014-9471/fixes HTTP/1.1
```
###### Example Response
@ -484,14 +484,14 @@ Server: clair
"Features": [
{
"Name": "coreutils",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Version": "8.23-1"
}
]
}
```
#### PUT /namespaces/`:nsName`/vulnerabilities/`:vulnName`/fixes/`:featureName`
#### PUT /namespaces/`:nsID`/vulnerabilities/`:vulnName`/fixes/`:featureName`
###### Description
@ -500,12 +500,12 @@ The PUT route for the Fixes resource updates a Feature that is the fix for a giv
###### Example Request
```json
PUT http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities/CVE-2014-9471/fixes/coreutils HTTP/1.1
PUT http://localhost:6060/v1/namespaces/gAAAAABXTQKgma_TLKq0wr1D6wVB507N3fi9wsWMUypYOSXTDVxQ8OR5L5S6PqZ9Wh0IzWojnVmlspyTL4cyjytyra7U9vAHMA==/vulnerabilities/CVE-2014-9471/fixes/coreutils HTTP/1.1
{
"Feature": {
"Name": "coreutils",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Version": "4.24-9"
}
}
@ -520,13 +520,13 @@ Server: clair
{
"Feature": {
"Name": "coreutils",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Version": "4.24-9"
}
}
```
#### DELETE /namespaces/`:nsName`/vulnerabilities/`:vulnName`/fixes/`:featureName`
#### DELETE /namespaces/`:nsID`/vulnerabilities/`:vulnName`/fixes/`:featureName`
###### Description
@ -535,7 +535,7 @@ The DELETE route for the Fixes resource removes a Feature as fix for the given V
###### Example Request
```json
DELETE http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities/CVE-2014-9471/fixes/coreutils
DELETE http://localhost:6060/v1/namespaces/gAAAAABXTQKgma_TLKq0wr1D6wVB507N3fi9wsWMUypYOSXTDVxQ8OR5L5S6PqZ9Wh0IzWojnVmlspyTL4cyjytyra7U9vAHMA==/vulnerabilities/CVE-2014-9471/fixes/coreutils
```
###### Example Response
@ -585,13 +585,13 @@ Server: clair
"New": {
"Vulnerability": {
"Name": "CVE-TEST",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Description": "New CVE",
"Severity": "Low",
"FixedIn": [
{
"Name": "grep",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Version": "2.25"
}
]
@ -604,7 +604,7 @@ Server: clair
"Old": {
"Vulnerability": {
"Name": "CVE-TEST",
"NamespaceName": "debian:8",
"Namespace": {"Name": "debian", "Version": "8"},
"Description": "New CVE",
"Severity": "Low",
"FixedIn": []

View File

@ -35,7 +35,7 @@ type Error struct {
type Layer struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
Namespaces []Namespace `json:"Namespaces,omitempty"`
Path string `json:"Path,omitempty"`
Headers map[string]string `json:"Headers,omitempty"`
ParentName string `json:"ParentName,omitempty"`
@ -54,27 +54,27 @@ func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabil
layer.ParentName = dbLayer.Parent.Name
}
if dbLayer.Namespace != nil {
layer.NamespaceName = dbLayer.Namespace.Name
for _, namespace := range dbLayer.Namespaces {
layer.Namespaces = append(layer.Namespaces, Namespace{Name: namespace.Name, Version: namespace.Version.String()})
}
if withFeatures || withVulnerabilities && dbLayer.Features != nil {
for _, dbFeatureVersion := range dbLayer.Features {
feature := Feature{
Name: dbFeatureVersion.Feature.Name,
NamespaceName: dbFeatureVersion.Feature.Namespace.Name,
Version: dbFeatureVersion.Version.String(),
AddedBy: dbFeatureVersion.AddedBy.Name,
Name: dbFeatureVersion.Feature.Name,
Namespace: Namespace{Name: dbFeatureVersion.Feature.Namespace.Name, Version: dbFeatureVersion.Feature.Namespace.Version.String()},
Version: dbFeatureVersion.Version.String(),
AddedBy: dbFeatureVersion.AddedBy.Name,
}
for _, dbVuln := range dbFeatureVersion.AffectedBy {
vuln := Vulnerability{
Name: dbVuln.Name,
NamespaceName: dbVuln.Namespace.Name,
Description: dbVuln.Description,
Link: dbVuln.Link,
Severity: string(dbVuln.Severity),
Metadata: dbVuln.Metadata,
Name: dbVuln.Name,
Namespace: Namespace{Name: dbVuln.Namespace.Name, Version: dbVuln.Namespace.Version.String()},
Description: dbVuln.Description,
Link: dbVuln.Link,
Severity: string(dbVuln.Severity),
Metadata: dbVuln.Metadata,
}
if dbVuln.FixedBy != types.MaxVersion {
@ -90,18 +90,20 @@ func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabil
}
type Namespace struct {
Name string `json:"Name,omitempty"`
ID string `json:"ID,omitempty"`
Name string `json:"Name,omitempty"`
Version string `json:"Version,omitempty"`
}
type Vulnerability struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
Description string `json:"Description,omitempty"`
Link string `json:"Link,omitempty"`
Severity string `json:"Severity,omitempty"`
Metadata map[string]interface{} `json:"Metadata,omitempty"`
FixedBy string `json:"FixedBy,omitempty"`
FixedIn []Feature `json:"FixedIn,omitempty"`
Name string `json:"Name,omitempty"`
Namespace Namespace `json:"Namespace,omitempty"`
Description string `json:"Description,omitempty"`
Link string `json:"Link,omitempty"`
Severity string `json:"Severity,omitempty"`
Metadata map[string]interface{} `json:"Metadata,omitempty"`
FixedBy string `json:"FixedBy,omitempty"`
FixedIn []Feature `json:"FixedIn,omitempty"`
}
func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) {
@ -110,6 +112,11 @@ func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) {
return database.Vulnerability{}, errors.New("Invalid severity")
}
version, err := types.NewVersion(v.Namespace.Version)
if err != nil {
return database.Vulnerability{}, errors.New("Invalid namespace version")
}
var dbFeatures []database.FeatureVersion
for _, feature := range v.FixedIn {
dbFeature, err := feature.DatabaseModel()
@ -122,7 +129,7 @@ func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) {
return database.Vulnerability{
Name: v.Name,
Namespace: database.Namespace{Name: v.NamespaceName},
Namespace: database.Namespace{Name: v.Namespace.Name, Version: version},
Description: v.Description,
Link: v.Link,
Severity: severity,
@ -133,12 +140,12 @@ func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) {
func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn bool) Vulnerability {
vuln := Vulnerability{
Name: dbVuln.Name,
NamespaceName: dbVuln.Namespace.Name,
Description: dbVuln.Description,
Link: dbVuln.Link,
Severity: string(dbVuln.Severity),
Metadata: dbVuln.Metadata,
Name: dbVuln.Name,
Namespace: Namespace{Name: dbVuln.Namespace.Name, Version: dbVuln.Namespace.Version.String()},
Description: dbVuln.Description,
Link: dbVuln.Link,
Severity: string(dbVuln.Severity),
Metadata: dbVuln.Metadata,
}
if withFixedIn {
@ -152,7 +159,7 @@ func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn b
type Feature struct {
Name string `json:"Name,omitempty"`
NamespaceName string `json:"NamespaceName,omitempty"`
Namespace Namespace `json:"Namespace,omitempty"`
Version string `json:"Version,omitempty"`
Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"`
AddedBy string `json:"AddedBy,omitempty"`
@ -165,10 +172,10 @@ func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature
}
return Feature{
Name: dbFeatureVersion.Feature.Name,
NamespaceName: dbFeatureVersion.Feature.Namespace.Name,
Version: versionStr,
AddedBy: dbFeatureVersion.AddedBy.Name,
Name: dbFeatureVersion.Feature.Name,
Namespace: Namespace{Name: dbFeatureVersion.Feature.Namespace.Name, Version: dbFeatureVersion.Feature.Namespace.Version.String()},
Version: versionStr,
AddedBy: dbFeatureVersion.AddedBy.Name,
}
}
@ -184,10 +191,15 @@ func (f Feature) DatabaseModel() (database.FeatureVersion, error) {
}
}
nsVersion, err := types.NewVersion(f.Namespace.Version)
if err != nil {
return database.FeatureVersion{}, err
}
return database.FeatureVersion{
Feature: database.Feature{
Name: f.Name,
Namespace: database.Namespace{Name: f.NamespaceName},
Namespace: database.Namespace{Name: f.Namespace.Name, Version: nsVersion},
},
Version: version,
}, nil

View File

@ -34,16 +34,16 @@ func NewRouter(ctx *context.RouteContext) *httprouter.Router {
router.GET("/namespaces", context.HTTPHandler(getNamespaces, ctx))
// Vulnerabilities
router.GET("/namespaces/:namespaceName/vulnerabilities", context.HTTPHandler(getVulnerabilities, ctx))
router.POST("/namespaces/:namespaceName/vulnerabilities", context.HTTPHandler(postVulnerability, ctx))
router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(getVulnerability, ctx))
router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(putVulnerability, ctx))
router.DELETE("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(deleteVulnerability, ctx))
router.GET("/namespaces/:namespaceID/vulnerabilities", context.HTTPHandler(getVulnerabilities, ctx))
router.POST("/namespaces/:namespaceID/vulnerabilities", context.HTTPHandler(postVulnerability, ctx))
router.GET("/namespaces/:namespaceID/vulnerabilities/:vulnerabilityName", context.HTTPHandler(getVulnerability, ctx))
router.PUT("/namespaces/:namespaceID/vulnerabilities/:vulnerabilityName", context.HTTPHandler(putVulnerability, ctx))
router.DELETE("/namespaces/:namespaceID/vulnerabilities/:vulnerabilityName", context.HTTPHandler(deleteVulnerability, ctx))
// Fixes
router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes", context.HTTPHandler(getFixes, ctx))
router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes/:fixName", context.HTTPHandler(putFix, ctx))
router.DELETE("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName/fixes/:fixName", context.HTTPHandler(deleteFix, ctx))
router.GET("/namespaces/:namespaceID/vulnerabilities/:vulnerabilityName/fixes", context.HTTPHandler(getFixes, ctx))
router.PUT("/namespaces/:namespaceID/vulnerabilities/:vulnerabilityName/fixes/:fixName", context.HTTPHandler(putFix, ctx))
router.DELETE("/namespaces/:namespaceID/vulnerabilities/:vulnerabilityName/fixes/:fixName", context.HTTPHandler(deleteFix, ctx))
// Notifications
router.GET("/notifications/:notificationName", context.HTTPHandler(getNotification, ctx))

View File

@ -179,7 +179,12 @@ func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params,
}
var namespaces []Namespace
for _, dbNamespace := range dbNamespaces {
namespaces = append(namespaces, Namespace{Name: dbNamespace.Name})
if namespaceIDBytes, err := tokenMarshal(dbNamespace.ID, ctx.Config.PaginationKey); err != nil {
writeResponse(w, r, http.StatusInternalServerError, NamespaceEnvelope{Error: &Error{err.Error()}})
return getNamespacesRoute, http.StatusInternalServerError
} else {
namespaces = append(namespaces, Namespace{ID: string(namespaceIDBytes), Name: dbNamespace.Name, Version: dbNamespace.Version.String()})
}
}
writeResponse(w, r, http.StatusOK, NamespaceEnvelope{Namespaces: &namespaces})
@ -213,13 +218,13 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par
}
}
namespace := p.ByName("namespaceName")
if namespace == "" {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"namespace should not be empty"}})
namespaceID := 0
if err = tokenUnmarshal(p.ByName("namespaceID"), ctx.Config.PaginationKey, &namespaceID); err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid namespace id format: " + err.Error()}})
return getNotificationRoute, http.StatusBadRequest
}
dbVulns, nextPage, err := ctx.Store.ListVulnerabilities(namespace, limit, page)
dbVulns, nextPage, err := ctx.Store.ListVulnerabilities(namespaceID, limit, page)
if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return getVulnerabilityRoute, http.StatusNotFound
@ -286,7 +291,13 @@ func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Para
func getVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
_, withFixedIn := r.URL.Query()["fixedIn"]
dbVuln, err := ctx.Store.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
namespaceID := 0
if err := tokenUnmarshal(p.ByName("namespaceID"), ctx.Config.PaginationKey, &namespaceID); err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid namespace id format: " + err.Error()}})
return getVulnerabilityRoute, http.StatusBadRequest
}
dbVuln, err := ctx.Store.FindVulnerability(namespaceID, p.ByName("vulnerabilityName"))
if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return getVulnerabilityRoute, http.StatusNotFound
@ -302,6 +313,12 @@ func getVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Param
}
func putVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
namespaceID := 0
if err := tokenUnmarshal(p.ByName("namespaceID"), ctx.Config.PaginationKey, &namespaceID); err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid namespace id format: " + err.Error()}})
return putVulnerabilityRoute, http.StatusBadRequest
}
request := VulnerabilityEnvelope{}
err := decodeJSON(r, &request)
if err != nil {
@ -325,7 +342,7 @@ func putVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Param
return putVulnerabilityRoute, http.StatusBadRequest
}
vuln.Namespace.Name = p.ByName("namespaceName")
vuln.Namespace.ID = namespaceID
vuln.Name = p.ByName("vulnerabilityName")
err = ctx.Store.InsertVulnerabilities([]database.Vulnerability{vuln}, true)
@ -345,7 +362,13 @@ func putVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Param
}
func deleteVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
err := ctx.Store.DeleteVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
namespaceID := 0
if err := tokenUnmarshal(p.ByName("namespaceID"), ctx.Config.PaginationKey, &namespaceID); err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid namespace id format: " + err.Error()}})
return deleteVulnerabilityRoute, http.StatusBadRequest
}
err := ctx.Store.DeleteVulnerability(namespaceID, p.ByName("vulnerabilityName"))
if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}})
return deleteVulnerabilityRoute, http.StatusNotFound
@ -359,7 +382,13 @@ func deleteVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Pa
}
func getFixes(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
dbVuln, err := ctx.Store.FindVulnerability(p.ByName("namespaceName"), p.ByName("vulnerabilityName"))
namespaceID := 0
if err := tokenUnmarshal(p.ByName("namespaceID"), ctx.Config.PaginationKey, &namespaceID); err != nil {
writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid namespace id format: " + err.Error()}})
return getFixesRoute, http.StatusBadRequest
}
dbVuln, err := ctx.Store.FindVulnerability(namespaceID, p.ByName("vulnerabilityName"))
if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}})
return getFixesRoute, http.StatusNotFound
@ -374,6 +403,12 @@ func getFixes(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *
}
func putFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
namespaceID := 0
if err := tokenUnmarshal(p.ByName("namespaceID"), ctx.Config.PaginationKey, &namespaceID); err != nil {
writeResponse(w, r, http.StatusBadRequest, FeatureEnvelope{Error: &Error{"invalid namespace id format: " + err.Error()}})
return putFixRoute, http.StatusBadRequest
}
request := FeatureEnvelope{}
err := decodeJSON(r, &request)
if err != nil {
@ -397,7 +432,7 @@ func putFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *co
return putFixRoute, http.StatusBadRequest
}
err = ctx.Store.InsertVulnerabilityFixes(p.ByName("vulnerabilityNamespace"), p.ByName("vulnerabilityName"), []database.FeatureVersion{dbFix})
err = ctx.Store.InsertVulnerabilityFixes(namespaceID, p.ByName("vulnerabilityName"), []database.FeatureVersion{dbFix})
if err != nil {
switch err.(type) {
case *cerrors.ErrBadRequest:
@ -418,7 +453,13 @@ func putFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *co
}
func deleteFix(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) {
err := ctx.Store.DeleteVulnerabilityFix(p.ByName("vulnerabilityNamespace"), p.ByName("vulnerabilityName"), p.ByName("fixName"))
namespaceID := 0
if err := tokenUnmarshal(p.ByName("namespaceID"), ctx.Config.PaginationKey, &namespaceID); err != nil {
writeResponse(w, r, http.StatusBadRequest, FeatureEnvelope{Error: &Error{"invalid namespace id format: " + err.Error()}})
return deleteFixRoute, http.StatusBadRequest
}
err := ctx.Store.DeleteVulnerabilityFix(namespaceID, p.ByName("vulnerabilityName"), p.ByName("fixName"))
if err == cerrors.ErrNotFound {
writeResponse(w, r, http.StatusNotFound, FeatureEnvelope{Error: &Error{err.Error()}})
return deleteFixRoute, http.StatusNotFound

View File

@ -67,6 +67,8 @@ type Datastore interface {
// # Namespace
// ListNamespaces returns the entire list of known Namespaces.
ListNamespaces() ([]Namespace, error)
// GetNamespaceID returns the id by name and version.
GetNamespaceID(Namespace) (int, error)
// # Layer
// InsertLayer stores a Layer in the database.
@ -93,7 +95,7 @@ type Datastore interface {
// 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(namespace Namespace, limit int, page int) ([]Vulnerability, int, error)
ListVulnerabilities(namespaceID int, limit int, page int) ([]Vulnerability, int, error)
// InsertVulnerabilities stores the given Vulnerabilities in the database, updating them if
// necessary. A vulnerability is uniquely identified by its Namespace and its Name.
@ -110,22 +112,22 @@ type Datastore interface {
InsertVulnerabilities(vulnerabilities []Vulnerability, createNotification bool) error
// FindVulnerability retrieves a Vulnerability from the database, including the FixedIn list.
FindVulnerability(namespace Namespace, name string) (Vulnerability, error)
FindVulnerability(namespaceID int, name string) (Vulnerability, error)
// DeleteVulnerability removes a Vulnerability from the database.
// It has to create a Notification that will contain the old Vulnerability.
DeleteVulnerability(namespace Namespace, name string) error
DeleteVulnerability(namespaceID int, name string) error
// InsertVulnerabilityFixes adds new FixedIn Feature or update the Versions of existing ones to
// the specified Vulnerability in the database.
// It has has to create a Notification that will contain the old and the updated Vulnerability.
InsertVulnerabilityFixes(vulnerabilityNamespace Namespace, vulnerabilityName string, fixes []FeatureVersion) error
InsertVulnerabilityFixes(vulnerabilityNamespaceID int, vulnerabilityName string, fixes []FeatureVersion) error
// DeleteVulnerabilityFix removes a FixedIn Feature from the specified Vulnerability in the
// database. It can be used to store the fact that a Vulnerability no longer affects the given
// Feature in any Version.
// It has has to create a Notification that will contain the old and the updated Vulnerability.
DeleteVulnerabilityFix(vulnerabilityNamespace Namespace, vulnerabilityName, featureName string) error
DeleteVulnerabilityFix(vulnerabilityNamespaceID int, vulnerabilityName, featureName string) error
// # Notification
// GetAvailableNotification returns the Name, Created, Notified and Deleted fields of a

View File

@ -20,15 +20,16 @@ import "time"
// The default behavior of each method is to simply panic.
type MockDatastore struct {
FctListNamespaces func() ([]Namespace, error)
FctGetNamespaceID func(namespace Namespace) (int, error)
FctInsertLayer func(Layer) error
FctFindLayer func(name string, withFeatures, withVulnerabilities bool) (Layer, error)
FctDeleteLayer func(name string) error
FctListVulnerabilities func(namespace Namespace, limit int, page int) ([]Vulnerability, int, error)
FctListVulnerabilities func(namespaceID int, limit int, page int) ([]Vulnerability, int, error)
FctInsertVulnerabilities func(vulnerabilities []Vulnerability, createNotification bool) error
FctFindVulnerability func(namespace Namespace, name string) (Vulnerability, error)
FctDeleteVulnerability func(namespace Namespace, name string) error
FctInsertVulnerabilityFixes func(vulnerabilityNamespace Namespace, vulnerabilityName string, fixes []FeatureVersion) error
FctDeleteVulnerabilityFix func(vulnerabilityNamespace Namespace, vulnerabilityName, featureName string) error
FctFindVulnerability func(namespaceID int, name string) (Vulnerability, error)
FctDeleteVulnerability func(namespaceID int, name string) error
FctInsertVulnerabilityFixes func(vulnerabilityNamespaceID int, vulnerabilityName string, fixes []FeatureVersion) error
FctDeleteVulnerabilityFix func(vulnerabilityNamespaceID int, vulnerabilityName, featureName string) error
FctGetAvailableNotification func(renotifyInterval time.Duration) (VulnerabilityNotification, error)
FctGetNotification func(name string, limit int, page VulnerabilityNotificationPageNumber) (VulnerabilityNotification, VulnerabilityNotificationPageNumber, error)
FctSetNotificationNotified func(name string) error
@ -49,6 +50,13 @@ func (mds *MockDatastore) ListNamespaces() ([]Namespace, error) {
panic("required mock function not implemented")
}
func (mds *MockDatastore) GetNamespaceID(namespace Namespace) (int, error) {
if mds.FctGetNamespaceID != nil {
return mds.FctGetNamespaceID(namespace)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) InsertLayer(layer Layer) error {
if mds.FctInsertLayer != nil {
return mds.FctInsertLayer(layer)
@ -70,9 +78,9 @@ func (mds *MockDatastore) DeleteLayer(name string) error {
panic("required mock function not implemented")
}
func (mds *MockDatastore) ListVulnerabilities(namespace Namespace, limit int, page int) ([]Vulnerability, int, error) {
func (mds *MockDatastore) ListVulnerabilities(namespaceID int, limit int, page int) ([]Vulnerability, int, error) {
if mds.FctListVulnerabilities != nil {
return mds.FctListVulnerabilities(namespace, limit, page)
return mds.FctListVulnerabilities(namespaceID, limit, page)
}
panic("required mock function not implemented")
}
@ -84,30 +92,30 @@ func (mds *MockDatastore) InsertVulnerabilities(vulnerabilities []Vulnerability,
panic("required mock function not implemented")
}
func (mds *MockDatastore) FindVulnerability(namespace Namespace, name string) (Vulnerability, error) {
func (mds *MockDatastore) FindVulnerability(namespaceID int, name string) (Vulnerability, error) {
if mds.FctFindVulnerability != nil {
return mds.FctFindVulnerability(namespace, name)
return mds.FctFindVulnerability(namespaceID, name)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) DeleteVulnerability(namespace Namespace, name string) error {
func (mds *MockDatastore) DeleteVulnerability(namespaceID int, name string) error {
if mds.FctDeleteVulnerability != nil {
return mds.FctDeleteVulnerability(namespace, name)
return mds.FctDeleteVulnerability(namespaceID, name)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) InsertVulnerabilityFixes(vulnerabilityNamespace Namespace, vulnerabilityName string, fixes []FeatureVersion) error {
func (mds *MockDatastore) InsertVulnerabilityFixes(vulnerabilityNamespaceID int, vulnerabilityName string, fixes []FeatureVersion) error {
if mds.FctInsertVulnerabilityFixes != nil {
return mds.FctInsertVulnerabilityFixes(vulnerabilityNamespace, vulnerabilityName, fixes)
return mds.FctInsertVulnerabilityFixes(vulnerabilityNamespaceID, vulnerabilityName, fixes)
}
panic("required mock function not implemented")
}
func (mds *MockDatastore) DeleteVulnerabilityFix(vulnerabilityNamespace Namespace, vulnerabilityName, featureName string) error {
func (mds *MockDatastore) DeleteVulnerabilityFix(vulnerabilityNamespaceID int, vulnerabilityName, featureName string) error {
if mds.FctDeleteVulnerabilityFix != nil {
return mds.FctDeleteVulnerabilityFix(vulnerabilityNamespace, vulnerabilityName, featureName)
return mds.FctDeleteVulnerabilityFix(vulnerabilityNamespaceID, vulnerabilityName, featureName)
}
panic("required mock function not implemented")
}

View File

@ -21,13 +21,13 @@ var DebianReleasesMapping = map[string]string{
"wheezy": "7",
"jessie": "8",
"stretch": "9",
"sid": "unstable",
"sid": "10",
// Class names
"oldstable": "7",
"stable": "8",
"testing": "9",
"unstable": "unstable",
"unstable": "10",
}
// UbuntuReleasesMapping translates Ubuntu code names to version numbers

View File

@ -52,28 +52,6 @@ func (pgSQL *pgSQL) FindLayer(name string, withFeatures, withVulnerabilities boo
Model: database.Model{ID: int(parentID.Int64)},
Name: parentName.String,
}
// Find its parent's namespaces
t = time.Now()
rows, err := pgSQL.Query(searchLayerNamespace, parentID)
observeQueryTime("FindLayer", "searchParentLayerNamespace", t)
if err != nil {
return layer, handleError("searchLayerNamespace", err)
}
defer rows.Close()
for rows.Next() {
var pn database.Namespace
err = rows.Scan(&pn.ID, &pn.Name, &pn.Version)
if err != nil {
return layer, handleError("searchLayerNamespace.Scan()", err)
}
layer.Parent.Namespaces = append(layer.Parent.Namespaces, pn)
}
if err = rows.Err(); err != nil {
return layer, handleError("searchLayerNamespace.Rows()", err)
}
}
// Find its namespaces

View File

@ -208,23 +208,29 @@ func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
},
// This layer changes the namespace and adds Features.
{
Name: "TestInsertLayer3",
Parent: &database.Layer{Name: "TestInsertLayer2"},
Name: "TestInsertLayer3",
Parent: &database.Layer{Name: "TestInsertLayer2",
Namespaces: []database.Namespace{testInsertLayerNamespace1},
},
Namespaces: []database.Namespace{testInsertLayerNamespace2},
Features: []database.FeatureVersion{f1, f2, f3},
},
// This layer covers the case where the last layer doesn't provide any new Feature.
{
Name: "TestInsertLayer4a",
Parent: &database.Layer{Name: "TestInsertLayer3"},
Name: "TestInsertLayer4a",
Parent: &database.Layer{Name: "TestInsertLayer3",
Namespaces: []database.Namespace{testInsertLayerNamespace1, testInsertLayerNamespace2},
},
Features: []database.FeatureVersion{f1, f2, f3},
},
// This layer covers the case where the last layer provides Features.
// It also modifies the Namespace ("upgrade") but keeps some Features not upgraded, their
// Namespaces should then remain unchanged.
{
Name: "TestInsertLayer4b",
Parent: &database.Layer{Name: "TestInsertLayer3"},
Name: "TestInsertLayer4b",
Parent: &database.Layer{Name: "TestInsertLayer3",
Namespaces: []database.Namespace{testInsertLayerNamespace1, testInsertLayerNamespace2},
},
Namespaces: []database.Namespace{testInsertLayerNamespace3},
Features: []database.FeatureVersion{
// Deletes TestInsertLayerFeature1.
@ -295,6 +301,8 @@ func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
}
l3, _ := datastore.FindLayer("TestInsertLayer3", true, false)
l3Parent, _ := datastore.FindLayer(l3.Parent.Name, false, false)
l3.Parent = &l3Parent
l3u := database.Layer{
Name: l3.Name,
Parent: l3.Parent,
@ -304,7 +312,7 @@ func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
l4u := database.Layer{
Name: "TestInsertLayer4",
Parent: &database.Layer{Name: "TestInsertLayer3"},
Parent: &database.Layer{Name: "TestInsertLayer3", Namespaces: l3.Namespaces},
Features: []database.FeatureVersion{f7},
EngineVersion: 2,
}

View File

@ -1,4 +1,4 @@
-- Copyright 2015 clair authors
-- Copyright 2016 clair authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
@ -33,7 +33,7 @@ CREATE INDEX ON LayerNamespace (layer_id, namespace_id);
INSERT INTO LayerNamespace(layer_id, namespace_id)
SELECT id, namespace_id
from Layer;
from Layer WHERE namespace_id IS NOT NULL;
-- -----------------------------------------------------
-- Layer table

View File

@ -50,6 +50,30 @@ func (pgSQL *pgSQL) insertNamespace(namespace database.Namespace) (int, error) {
return id, nil
}
func (pgSQL *pgSQL) GetNamespaceID(namespace database.Namespace) (int, error) {
if pgSQL.cache != nil {
promCacheQueriesTotal.WithLabelValues("namespace").Inc()
if id, found := pgSQL.cache.Get("namespace:" + namespace.Name + ":" + namespace.Version.String()); found {
promCacheHitsTotal.WithLabelValues("namespace").Inc()
return id.(int), nil
}
}
defer observeQueryTime("getNamespaceID", "all", time.Now())
var id int
err := pgSQL.QueryRow(getNamespaceID, namespace.Name, namespace.Version.String()).Scan(&id)
if err != nil {
return 0, handleError("getNamespaceID", err)
}
if pgSQL.cache != nil {
pgSQL.cache.Add("namespace:"+namespace.Name+":"+namespace.Version.String(), id)
}
return id, nil
}
func (pgSQL *pgSQL) ListNamespaces() (namespaces []database.Namespace, err error) {
rows, err := pgSQL.Query(listNamespace)
if err != nil {

View File

@ -118,7 +118,8 @@ func TestNotification(t *testing.T) {
assert.Nil(t, datastore.insertVulnerability(v1, false, true))
// Get the notification associated to the previously inserted vulnerability.
notification, err := datastore.GetAvailableNotification(time.Second)
var notification database.VulnerabilityNotification
notification, err = datastore.GetAvailableNotification(time.Second)
if assert.Nil(t, err) && assert.NotEmpty(t, notification.Name) {
// Verify the renotify behaviour.
@ -206,8 +207,12 @@ func TestNotification(t *testing.T) {
}
}
namespaceID, err := datastore.GetNamespaceID(database.Namespace{
Name: "TestNotificationNamespace",
Version: types.NewVersionUnsafe("1.0"),
})
// Delete a vulnerability and verify the notification.
if assert.Nil(t, datastore.DeleteVulnerability(v1b.Namespace, v1b.Name)) {
if assert.Nil(t, err) && assert.Nil(t, datastore.DeleteVulnerability(namespaceID, v1b.Name)) {
notification, err = datastore.GetAvailableNotification(time.Second)
assert.Nil(t, err)
assert.NotEmpty(t, notification.Name)

View File

@ -37,9 +37,9 @@ const (
SELECT id FROM Namespace WHERE name = $1 AND version = $2
UNION
SELECT id FROM new_namespace`
searchNamespace = `SELECT id FROM Namespace WHERE name = $1 AND version = $2`
listNamespace = `SELECT id, name, version FROM Namespace`
getNamespaceID = `SELECT id FROM Namespace WHERE name = $1 AND version = $2`
listNamespace = `SELECT id, name, version FROM Namespace where version IS NOT NULL`
getNamespaceByID = `SELECT name, version FROM Namespace WHERE id = $1`
// feature.go
soiFeature = `
@ -161,10 +161,10 @@ const (
searchVulnerabilityBase = `
SELECT v.id, v.name, n.id, n.name, n.version, v.description, v.link, v.severity, v.metadata
FROM Vulnerability v JOIN Namespace n ON v.namespace_id = n.id`
searchVulnerabilityForUpdate = ` FOR UPDATE OF v`
searchVulnerabilityByNamespaceAndName = ` WHERE n.name = $1 AND n.version = $2 AND v.name = $3 AND v.deleted_at IS NULL`
searchVulnerabilityByID = ` WHERE v.id = $1`
searchVulnerabilityByNamespaceID = ` WHERE n.id = $1 AND v.deleted_at IS NULL
searchVulnerabilityForUpdate = ` FOR UPDATE OF v`
searchVulnerabilityByNamespaceIDAndName = ` WHERE n.id = $1 AND v.name = $2 AND v.deleted_at IS NULL`
searchVulnerabilityByID = ` WHERE v.id = $1`
searchVulnerabilityByNamespaceID = ` WHERE n.id = $1 AND v.deleted_at IS NULL
AND v.id >= $2
ORDER BY v.id
LIMIT $3`
@ -189,8 +189,8 @@ const (
removeVulnerability = `
UPDATE Vulnerability
SET deleted_at = CURRENT_TIMESTAMP
WHERE namespace_id = (SELECT id FROM Namespace WHERE name = $1 AND version = $2)
AND name = $3
WHERE namespace_id = $1
AND name = $2
AND deleted_at IS NULL
RETURNING id`

View File

@ -28,21 +28,12 @@ import (
"github.com/guregu/null/zero"
)
func (pgSQL *pgSQL) ListVulnerabilities(namespace database.Namespace, limit int, startID int) ([]database.Vulnerability, int, error) {
func (pgSQL *pgSQL) ListVulnerabilities(namespaceID int, limit int, startID int) ([]database.Vulnerability, int, error) {
defer observeQueryTime("listVulnerabilities", "all", time.Now())
// Query Namespace.
var id int
err := pgSQL.QueryRow(searchNamespace, namespace.Name, &namespace.Version).Scan(&id)
if err != nil {
return nil, -1, handleError("searchNamespace", err)
} else if id == 0 {
return nil, -1, cerrors.ErrNotFound
}
// Query.
query := searchVulnerabilityBase + searchVulnerabilityByNamespaceID
rows, err := pgSQL.Query(query, id, startID, limit+1)
rows, err := pgSQL.Query(query, namespaceID, startID, limit+1)
if err != nil {
return nil, -1, handleError("searchVulnerabilityByNamespaceID", err)
}
@ -84,21 +75,21 @@ func (pgSQL *pgSQL) ListVulnerabilities(namespace database.Namespace, limit int,
return vulns, nextID, nil
}
func (pgSQL *pgSQL) FindVulnerability(namespace database.Namespace, name string) (database.Vulnerability, error) {
return findVulnerability(pgSQL, namespace, name, false)
func (pgSQL *pgSQL) FindVulnerability(namespaceID int, name string) (database.Vulnerability, error) {
return findVulnerability(pgSQL, namespaceID, name, false)
}
func findVulnerability(queryer Queryer, namespace database.Namespace, name string, forUpdate bool) (database.Vulnerability, error) {
func findVulnerability(queryer Queryer, namespaceID int, name string, forUpdate bool) (database.Vulnerability, error) {
defer observeQueryTime("findVulnerability", "all", time.Now())
queryName := "searchVulnerabilityBase+searchVulnerabilityByNamespaceAndName"
query := searchVulnerabilityBase + searchVulnerabilityByNamespaceAndName
queryName := "searchVulnerabilityBase+searchVulnerabilityByNamespaceIDAndName"
query := searchVulnerabilityBase + searchVulnerabilityByNamespaceIDAndName
if forUpdate {
queryName = queryName + "+searchVulnerabilityForUpdate"
query = query + searchVulnerabilityForUpdate
}
return scanVulnerability(queryer, queryName, queryer.QueryRow(query, namespace.Name, &namespace.Version, name))
return scanVulnerability(queryer, queryName, queryer.QueryRow(query, namespaceID, name))
}
func (pgSQL *pgSQL) findVulnerabilityByIDWithDeleted(id int) (database.Vulnerability, error) {
@ -195,9 +186,24 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on
tf := time.Now()
// Verify parameters
if vulnerability.Name == "" || vulnerability.Namespace.IsEmpty() {
return cerrors.NewBadRequestError("insertVulnerability needs at least the Name and the Namespace")
if vulnerability.Name == "" {
return cerrors.NewBadRequestError("insertVulnerability needs at least the Name")
}
if vulnerability.Namespace.ID <= 0 {
// Find or insert Vulnerability's Namespace.
namespaceID, err := pgSQL.insertNamespace(vulnerability.Namespace)
if err != nil {
return err
}
vulnerability.Namespace.ID = namespaceID
} else if vulnerability.Namespace.IsEmpty() {
err := pgSQL.QueryRow(getNamespaceByID, vulnerability.Namespace.ID).Scan(&vulnerability.Namespace.Name, &vulnerability.Namespace.Version)
if err != nil {
return cerrors.NewBadRequestError("could not get the related namespace")
}
}
if !onlyFixedIn && !vulnerability.Severity.IsValid() {
msg := fmt.Sprintf("could not insert a vulnerability that has an invalid Severity: %s", vulnerability.Severity)
log.Warning(msg)
@ -209,8 +215,7 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on
if fifv.Feature.Namespace.IsEmpty() {
// As there is no Namespace on that FixedIn FeatureVersion, set it to the Vulnerability's
// Namespace.
fifv.Feature.Namespace.Name = vulnerability.Namespace.Name
fifv.Feature.Namespace.Version = vulnerability.Namespace.Version
fifv.Feature.Namespace = vulnerability.Namespace
} else if !fifv.Feature.Namespace.Equal(vulnerability.Namespace) {
msg := "could not insert an invalid vulnerability that contains FixedIn FeatureVersion that are not in the same namespace as the Vulnerability"
log.Warning(msg)
@ -229,7 +234,7 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on
}
// Find existing vulnerability and its Vulnerability_FixedIn_Features (for update).
existingVulnerability, err := findVulnerability(tx, vulnerability.Namespace, vulnerability.Name, true)
existingVulnerability, err := findVulnerability(tx, vulnerability.Namespace.ID, vulnerability.Name, true)
if err != nil && err != cerrors.ErrNotFound {
tx.Rollback()
return err
@ -267,7 +272,7 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on
}
// Mark the old vulnerability as non latest.
_, err = tx.Exec(removeVulnerability, vulnerability.Namespace.Name, &vulnerability.Namespace.Version, vulnerability.Name)
_, err = tx.Exec(removeVulnerability, vulnerability.Namespace.ID, vulnerability.Name)
if err != nil {
tx.Rollback()
return handleError("removeVulnerability", err)
@ -284,16 +289,10 @@ func (pgSQL *pgSQL) insertVulnerability(vulnerability database.Vulnerability, on
vulnerability.FixedIn = fixedIn
}
// Find or insert Vulnerability's Namespace.
namespaceID, err := pgSQL.insertNamespace(vulnerability.Namespace)
if err != nil {
return err
}
// Insert vulnerability.
err = tx.QueryRow(
insertVulnerability,
namespaceID,
vulnerability.Namespace.ID,
vulnerability.Name,
vulnerability.Description,
vulnerability.Link,
@ -500,38 +499,41 @@ func linkVulnerabilityToFeatureVersions(tx *sql.Tx, fixedInID, vulnerabilityID,
return nil
}
func (pgSQL *pgSQL) InsertVulnerabilityFixes(vulnerabilityNamespace database.Namespace, vulnerabilityName string, fixes []database.FeatureVersion) error {
func (pgSQL *pgSQL) InsertVulnerabilityFixes(vulnerabilityNamespaceID int, vulnerabilityName string, fixes []database.FeatureVersion) error {
defer observeQueryTime("InsertVulnerabilityFixes", "all", time.Now())
var vns database.Namespace
if err := pgSQL.QueryRow(getNamespaceByID, vulnerabilityNamespaceID).Scan(&vns.Name, &vns.Version); err != nil {
return handleError("getNamespaceByID", err)
}
vns.ID = vulnerabilityNamespaceID
v := database.Vulnerability{
Name: vulnerabilityName,
Namespace: database.Namespace{
Name: vulnerabilityNamespace.Name,
Version: vulnerabilityNamespace.Version,
},
FixedIn: fixes,
Name: vulnerabilityName,
Namespace: vns,
FixedIn: fixes,
}
return pgSQL.insertVulnerability(v, true, true)
}
func (pgSQL *pgSQL) DeleteVulnerabilityFix(vulnerabilityNamespace database.Namespace, vulnerabilityName, featureName string) error {
func (pgSQL *pgSQL) DeleteVulnerabilityFix(vulnerabilityNamespaceID int, vulnerabilityName, featureName string) error {
defer observeQueryTime("DeleteVulnerabilityFix", "all", time.Now())
var vns database.Namespace
if err := pgSQL.QueryRow(getNamespaceByID, vulnerabilityNamespaceID).Scan(&vns.Name, &vns.Version); err != nil {
return handleError("getNamespaceByID", err)
}
vns.ID = vulnerabilityNamespaceID
v := database.Vulnerability{
Name: vulnerabilityName,
Namespace: database.Namespace{
Name: vulnerabilityNamespace.Name,
Version: vulnerabilityNamespace.Version,
},
Name: vulnerabilityName,
Namespace: vns,
FixedIn: []database.FeatureVersion{
{
Feature: database.Feature{
Name: featureName,
Namespace: database.Namespace{
Name: vulnerabilityNamespace.Name,
Version: vulnerabilityNamespace.Version,
},
Name: featureName,
Namespace: vns,
},
Version: types.MinVersion,
},
@ -541,7 +543,7 @@ func (pgSQL *pgSQL) DeleteVulnerabilityFix(vulnerabilityNamespace database.Names
return pgSQL.insertVulnerability(v, true, true)
}
func (pgSQL *pgSQL) DeleteVulnerability(namespace database.Namespace, name string) error {
func (pgSQL *pgSQL) DeleteVulnerability(namespaceID int, name string) error {
defer observeQueryTime("DeleteVulnerability", "all", time.Now())
// Begin transaction.
@ -552,7 +554,7 @@ func (pgSQL *pgSQL) DeleteVulnerability(namespace database.Namespace, name strin
}
var vulnerabilityID int
err = tx.QueryRow(removeVulnerability, namespace.Name, &namespace.Version, name).Scan(&vulnerabilityID)
err = tx.QueryRow(removeVulnerability, namespaceID, name).Scan(&vulnerabilityID)
if err != nil {
tx.Rollback()
return handleError("removeVulnerability", err)

View File

@ -37,8 +37,12 @@ func TestFindVulnerability(t *testing.T) {
Name: "debian",
Version: types.NewVersionUnsafe("7"),
}
var testExistNamespaceID int
testExistNamespaceID, err = datastore.GetNamespaceID(testExistNamespace)
assert.Nil(t, err)
// Find a vulnerability that does not exist.
_, err = datastore.FindVulnerability(database.Namespace{}, "")
_, err = datastore.FindVulnerability(-1, "")
assert.Equal(t, cerrors.ErrNotFound, err)
// Find a normal vulnerability.
@ -60,7 +64,7 @@ func TestFindVulnerability(t *testing.T) {
},
}
v1f, err := datastore.FindVulnerability(testExistNamespace, "CVE-OPENSSL-1-DEB7")
v1f, err := datastore.FindVulnerability(testExistNamespaceID, "CVE-OPENSSL-1-DEB7")
if assert.Nil(t, err) {
equalsVuln(t, &v1, &v1f)
}
@ -73,7 +77,7 @@ func TestFindVulnerability(t *testing.T) {
Severity: types.Unknown,
}
v2f, err := datastore.FindVulnerability(testExistNamespace, "CVE-NOPE")
v2f, err := datastore.FindVulnerability(testExistNamespaceID, "CVE-NOPE")
if assert.Nil(t, err) {
equalsVuln(t, &v2, &v2f)
}
@ -91,20 +95,28 @@ func TestDeleteVulnerability(t *testing.T) {
Name: "debian",
Version: types.NewVersionUnsafe("7"),
}
var testExistNamespaceID int
testExistNamespaceID, err = datastore.GetNamespaceID(testExistNamespace)
assert.Nil(t, err)
testNonExistNamespace := database.Namespace{
Name: "TestDeleteVulnerabilityNamespace",
Version: types.NewVersionUnsafe("1.0"),
}
// Delete non-existing Vulnerability.
err = datastore.DeleteVulnerability(testNonExistNamespace, "CVE-OPENSSL-1-DEB7")
var testNonExistNamespaceID int
testNonExistNamespaceID, err = datastore.GetNamespaceID(testNonExistNamespace)
assert.Equal(t, cerrors.ErrNotFound, err)
err = datastore.DeleteVulnerability(testExistNamespace, "TestDeleteVulnerabilityVulnerability1")
// Delete non-existing Vulnerability.
err = datastore.DeleteVulnerability(testNonExistNamespaceID, "CVE-OPENSSL-1-DEB7")
assert.Equal(t, cerrors.ErrNotFound, err)
err = datastore.DeleteVulnerability(testExistNamespaceID, "TestDeleteVulnerabilityVulnerability1")
assert.Equal(t, cerrors.ErrNotFound, err)
// Delete Vulnerability.
err = datastore.DeleteVulnerability(testExistNamespace, "CVE-OPENSSL-1-DEB7")
err = datastore.DeleteVulnerability(testExistNamespaceID, "CVE-OPENSSL-1-DEB7")
if assert.Nil(t, err) {
_, err := datastore.FindVulnerability(testExistNamespace, "CVE-OPENSSL-1-DEB7")
_, err := datastore.FindVulnerability(testExistNamespaceID, "CVE-OPENSSL-1-DEB7")
assert.Equal(t, cerrors.ErrNotFound, err)
}
}
@ -228,7 +240,11 @@ func TestInsertVulnerability(t *testing.T) {
}
err = datastore.InsertVulnerabilities([]database.Vulnerability{v1}, true)
if assert.Nil(t, err) {
v1f, err := datastore.FindVulnerability(n1, v1.Name)
var n1ID int
var v1f database.Vulnerability
n1ID, err := datastore.GetNamespaceID(n1)
assert.Nil(t, err)
v1f, err = datastore.FindVulnerability(n1ID, v1.Name)
if assert.Nil(t, err) {
equalsVuln(t, &v1, &v1f)
}
@ -244,7 +260,11 @@ func TestInsertVulnerability(t *testing.T) {
err = datastore.InsertVulnerabilities([]database.Vulnerability{v1}, true)
if assert.Nil(t, err) {
v1f, err := datastore.FindVulnerability(n1, v1.Name)
var n1ID int
var v1f database.Vulnerability
n1ID, err := datastore.GetNamespaceID(n1)
assert.Nil(t, err)
v1f, err = datastore.FindVulnerability(n1ID, v1.Name)
if assert.Nil(t, err) {
// We already had f1 before the update.
// Add it to the struct for comparison.

View File

@ -169,6 +169,7 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability,
// Determine the version of the package the vulnerability affects.
var version types.Version
var nsVersion types.Version
var err error
if releaseNode.FixedVersion == "0" {
// This means that the package is not affected by this vulnerability.
@ -187,13 +188,19 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.Vulnerability,
}
}
nsVersion, err = types.NewVersion(database.DebianReleasesMapping[releaseName])
if err != nil {
log.Warningf("could not parse namespace version '%s': %s. skipping", database.DebianReleasesMapping[releaseName], err.Error())
continue
}
// Create and add the feature version.
pkg := database.FeatureVersion{
Feature: database.Feature{
Name: pkgName,
Namespace: database.Namespace{
Name: "debian",
Version: types.NewVersionUnsafe(database.DebianReleasesMapping[releaseName]),
Version: nsVersion,
},
},
Version: version,

View File

@ -48,7 +48,7 @@ func TestDebianParser(t *testing.T) {
},
{
Feature: database.Feature{
Namespace: database.Namespace{Name: "debian", Version: types.NewVersionUnsafe("unstable")},
Namespace: database.Namespace{Name: "debian", Version: types.NewVersionUnsafe("10")},
Name: "aptdaemon",
},
@ -74,7 +74,7 @@ func TestDebianParser(t *testing.T) {
},
{
Feature: database.Feature{
Namespace: database.Namespace{Name: "debian", Version: types.NewVersionUnsafe("unstable")},
Namespace: database.Namespace{Name: "debian", Version: types.NewVersionUnsafe("10")},
Name: "aptdaemon",
},
Version: types.NewVersionUnsafe("0.7.0"),

View File

@ -291,8 +291,13 @@ func toFeatureVersions(criteria criteria) []database.FeatureVersion {
}
if osVersion > firstConsideredRHEL {
nsVersion, err := types.NewVersion(strconv.Itoa(osVersion))
if err != nil {
log.Warningf("could not parse namespace version '%s': %s. skipping", strconv.Itoa(osVersion), err.Error())
continue
}
featureVersion.Feature.Namespace.Name = "centos"
featureVersion.Feature.Namespace.Version = types.NewVersionUnsafe(strconv.Itoa(osVersion))
featureVersion.Feature.Namespace.Version = nsVersion
} else {
continue
}

View File

@ -373,10 +373,15 @@ func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability
continue
}
nsVersion, err := types.NewVersion(database.UbuntuReleasesMapping[md["release"]])
if err != nil {
log.Warningf("could not parse namespace version '%s': %s. skipping", database.UbuntuReleasesMapping[md["release"]], err)
}
// Create and add the new package.
featureVersion := database.FeatureVersion{
Feature: database.Feature{
Namespace: database.Namespace{Name: "ubuntu", Version: types.NewVersionUnsafe(database.UbuntuReleasesMapping[md["release"]])},
Namespace: database.Namespace{Name: "ubuntu", Version: nsVersion},
Name: md["package"],
},
Version: version,

View File

@ -68,8 +68,7 @@ func DetectFeatures(data map[string][]byte, namespaces []database.Namespace) ([]
}
// Ensure that every feature has a Namespace associated
for i := 0; i < len(pkgs); i++ {
pkgs[i].Feature.Namespace.Name = namespace.Name
pkgs[i].Feature.Namespace.Version = namespace.Version
pkgs[i].Feature.Namespace = namespace
}
packages = append(packages, pkgs...)
break

View File

@ -76,7 +76,11 @@ func (detector *AptSourcesNamespaceDetector) Detect(data map[string][]byte) *dat
}
if OS != "" && version != "" {
return &database.Namespace{Name: OS, Version: types.NewVersionUnsafe(version)}
if nsVersion, err := types.NewVersion(version); err != nil {
return nil
} else {
return &database.Namespace{Name: OS, Version: nsVersion}
}
}
return nil
}

View File

@ -24,7 +24,7 @@ import (
var aptSourcesOSTests = []namespace.NamespaceTest{
{
ExpectedNamespace: database.Namespace{Name: "debian", Version: types.NewVersionUnsafe("unstable")},
ExpectedNamespace: database.Namespace{Name: "debian", Version: types.NewVersionUnsafe("10")},
Data: map[string][]byte{
"etc/os-release": []byte(
`PRETTY_NAME="Debian GNU/Linux stretch/sid"

View File

@ -71,7 +71,11 @@ func (detector *LsbReleaseNamespaceDetector) Detect(data map[string][]byte) *dat
}
if OS != "" && version != "" {
return &database.Namespace{Name: OS, Version: types.NewVersionUnsafe(version)}
if nsVersion, err := types.NewVersion(version); err != nil {
return nil
} else {
return &database.Namespace{Name: OS, Version: nsVersion}
}
}
return nil
}

View File

@ -66,7 +66,11 @@ func (detector *OsReleaseNamespaceDetector) Detect(data map[string][]byte) *data
}
if OS != "" && version != "" {
return &database.Namespace{Name: OS, Version: types.NewVersionUnsafe(version)}
if nsVersion, err := types.NewVersion(version); err != nil {
return nil
} else {
return &database.Namespace{Name: OS, Version: nsVersion}
}
}
return nil
}

View File

@ -47,7 +47,11 @@ func (detector *RedhatReleaseNamespaceDetector) Detect(data map[string][]byte) *
r := redhatReleaseRegexp.FindStringSubmatch(string(f))
if len(r) == 4 {
return &database.Namespace{Name: strings.ToLower(r[1]), Version: types.NewVersionUnsafe(r[3])}
if nsVersion, err := types.NewVersion(r[3]); err != nil {
return nil
} else {
return &database.Namespace{Name: strings.ToLower(r[1]), Version: nsVersion}
}
}
}