mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-03 14:37:44 +01:00
API for system level vulnerability whitelist
Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
parent
766d5ee017
commit
4aca812ff2
@ -2,7 +2,7 @@ swagger: '2.0'
|
||||
info:
|
||||
title: Harbor API
|
||||
description: These APIs provide services for manipulating Harbor project.
|
||||
version: 1.8.0
|
||||
version: 1.9.0
|
||||
host: localhost
|
||||
schemes:
|
||||
- http
|
||||
@ -3478,6 +3478,44 @@ paths:
|
||||
description: The robot account is not found.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/system/CVEWhitelist':
|
||||
get:
|
||||
summary: Get the system level whitelist of CVE.
|
||||
description: Get the system level whitelist of CVE. This API can be called by all authenticated users.
|
||||
tags:
|
||||
- Products
|
||||
- System
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully retrieved the CVE whitelist.
|
||||
schema:
|
||||
$ref: "#/definitions/CVEWhitelist"
|
||||
'401':
|
||||
description: User is not authenticated.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
put:
|
||||
summary: Update the system level whitelist of CVE.
|
||||
description: This API overwrites the system level whitelist of CVE with the list in request body. Only system Admin
|
||||
has permission to call this API.
|
||||
tags:
|
||||
- Products
|
||||
- System
|
||||
parameters:
|
||||
- in: body
|
||||
name: whitelist
|
||||
description: The whitelist with new content
|
||||
schema:
|
||||
$ref: "#/definitions/CVEWhitelist"
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully updated the CVE whitelist.
|
||||
'401':
|
||||
description: User is not authenticated.
|
||||
'403':
|
||||
description: User does not have permission to call this API.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
responses:
|
||||
OK:
|
||||
description: 'Success'
|
||||
@ -5069,4 +5107,28 @@ definitions:
|
||||
description: The name of namespace
|
||||
metadata:
|
||||
type: object
|
||||
description: The metadata of namespace
|
||||
description: The metadata of namespace
|
||||
CVEWhitelist:
|
||||
type: object
|
||||
description: The CVE Whitelist for system or project
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: ID of the whitelist
|
||||
project_id:
|
||||
type: integer
|
||||
description: ID of the project which the whitelist belongs to. For system level whitelist this attribute is zero.
|
||||
expires_at:
|
||||
type: integer
|
||||
description: the time for expiration of the whitelist, in the form of seconds since epoch. This is an optional attribute, if it's not set the CVE whitelist does not expire.
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/CVEWhitelistItem"
|
||||
CVEWhitelistItem:
|
||||
type: object
|
||||
description: The item in CVE whitelist
|
||||
properties:
|
||||
cve_id:
|
||||
type: string
|
||||
description: The ID of the CVE, such as "CVE-2019-10164"
|
||||
|
10
make/migrations/postgresql/0010_1.9.0_schema.up.sql
Normal file
10
make/migrations/postgresql/0010_1.9.0_schema.up.sql
Normal file
@ -0,0 +1,10 @@
|
||||
/* add table for CVE whitelist */
|
||||
CREATE TABLE cve_whitelist (
|
||||
id SERIAL PRIMARY KEY NOT NULL,
|
||||
project_id int,
|
||||
creation_time timestamp default CURRENT_TIMESTAMP,
|
||||
update_time timestamp default CURRENT_TIMESTAMP,
|
||||
expires_at bigint,
|
||||
items text NOT NULL,
|
||||
UNIQUE (project_id)
|
||||
);
|
74
src/common/dao/cve_whitelist.go
Normal file
74
src/common/dao/cve_whitelist.go
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// UpdateCVEWhitelist Updates the vulnerability white list to DB
|
||||
func UpdateCVEWhitelist(l models.CVEWhitelist) (int64, error) {
|
||||
o := GetOrmer()
|
||||
itemsBytes, _ := json.Marshal(l.Items)
|
||||
l.ItemsText = string(itemsBytes)
|
||||
id, err := o.InsertOrUpdate(&l, "project_id")
|
||||
return id, err
|
||||
}
|
||||
|
||||
// GetSysCVEWhitelist Gets the system level vulnerability white list from DB
|
||||
func GetSysCVEWhitelist() (*models.CVEWhitelist, error) {
|
||||
return GetCVEWhitelist(0)
|
||||
}
|
||||
|
||||
// UpdateSysCVEWhitelist updates the system level CVE whitelist
|
||||
/*
|
||||
func UpdateSysCVEWhitelist(l models.CVEWhitelist) error {
|
||||
if l.ProjectID != 0 {
|
||||
return fmt.Errorf("system level CVE whitelist cannot set project ID")
|
||||
}
|
||||
l.ProjectID = -1
|
||||
_, err := UpdateCVEWhitelist(l)
|
||||
return err
|
||||
}
|
||||
*/
|
||||
|
||||
// GetCVEWhitelist Gets the CVE whitelist of the project based on the project ID in parameter
|
||||
func GetCVEWhitelist(pid int64) (*models.CVEWhitelist, error) {
|
||||
o := GetOrmer()
|
||||
qs := o.QueryTable(&models.CVEWhitelist{})
|
||||
qs = qs.Filter("ProjectID", pid)
|
||||
r := []*models.CVEWhitelist{}
|
||||
_, err := qs.All(&r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get CVE whitelist for project %d, error: %v", pid, err)
|
||||
}
|
||||
if len(r) == 0 {
|
||||
log.Infof("No CVE whitelist found for project %d, returning empty list.", pid)
|
||||
return &models.CVEWhitelist{ProjectID: pid, Items: []models.CVEWhitelistItem{}}, nil
|
||||
} else if len(r) > 1 {
|
||||
log.Infof("Multiple CVE whitelists found for project %d, length: %d, returning first element.", pid, len(r))
|
||||
}
|
||||
items := []models.CVEWhitelistItem{}
|
||||
err = json.Unmarshal([]byte(r[0].ItemsText), &items)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to decode item list, err: %v, text: %s", err, r[0].ItemsText)
|
||||
return nil, err
|
||||
}
|
||||
r[0].Items = items
|
||||
return r[0], nil
|
||||
}
|
72
src/common/dao/cve_whitelist_test.go
Normal file
72
src/common/dao/cve_whitelist_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUpdateAndGetCVEWhitelist(t *testing.T) {
|
||||
require.Nil(t, ClearTable("cve_whitelist"))
|
||||
l, err := GetSysCVEWhitelist()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, models.CVEWhitelist{ProjectID: 0, Items: []models.CVEWhitelistItem{}}, *l)
|
||||
l2, err := GetCVEWhitelist(5)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, models.CVEWhitelist{ProjectID: 5, Items: []models.CVEWhitelistItem{}}, *l2)
|
||||
|
||||
longList := []models.CVEWhitelistItem{}
|
||||
for i := 0; i < 50; i++ {
|
||||
longList = append(longList, models.CVEWhitelistItem{CVEID: "CVE-1999-0067"})
|
||||
}
|
||||
|
||||
e := int64(1573254000)
|
||||
in1 := models.CVEWhitelist{ProjectID: 3, Items: longList, ExpiresAt: &e}
|
||||
_, err = UpdateCVEWhitelist(in1)
|
||||
require.Nil(t, err)
|
||||
// assert.Equal(t, int64(1), n)
|
||||
out1, err := GetCVEWhitelist(3)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, int64(3), out1.ProjectID)
|
||||
assert.Equal(t, longList, out1.Items)
|
||||
assert.Equal(t, e, *out1.ExpiresAt)
|
||||
|
||||
in2 := models.CVEWhitelist{ProjectID: 3, Items: []models.CVEWhitelistItem{}}
|
||||
_, err = UpdateCVEWhitelist(in2)
|
||||
require.Nil(t, err)
|
||||
// assert.Equal(t, int64(1), n2)
|
||||
out2, err := GetCVEWhitelist(3)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, int64(3), out2.ProjectID)
|
||||
assert.Equal(t, []models.CVEWhitelistItem{}, out2.Items)
|
||||
|
||||
sysCVEs := []models.CVEWhitelistItem{
|
||||
{CVEID: "CVE-2019-10164"},
|
||||
{CVEID: "CVE-2017-12345"},
|
||||
}
|
||||
in3 := models.CVEWhitelist{Items: sysCVEs}
|
||||
_, err = UpdateCVEWhitelist(in3)
|
||||
require.Nil(t, err)
|
||||
// assert.Equal(t, int64(1), n3)
|
||||
sysList, err := GetSysCVEWhitelist()
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, int64(0), sysList.ProjectID)
|
||||
assert.Equal(t, sysCVEs, sysList.Items)
|
||||
|
||||
// require.Nil(t, ClearTable("cve_whitelist"))
|
||||
}
|
@ -36,5 +36,6 @@ func init() {
|
||||
new(AdminJob),
|
||||
new(JobLog),
|
||||
new(Robot),
|
||||
new(OIDCUser))
|
||||
new(OIDCUser),
|
||||
new(CVEWhitelist))
|
||||
}
|
||||
|
38
src/common/models/cve_whitelist.go
Normal file
38
src/common/models/cve_whitelist.go
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// CVEWhitelist defines the data model for a CVE whitelist
|
||||
type CVEWhitelist struct {
|
||||
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||
ExpiresAt *int64 `orm:"column(expires_at)" json:"expires_at,omitempty"`
|
||||
Items []CVEWhitelistItem `orm:"-" json:"items"`
|
||||
ItemsText string `orm:"column(items)" json:"-"`
|
||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||
}
|
||||
|
||||
// CVEWhitelistItem defines one item in the CVE whitelist
|
||||
type CVEWhitelistItem struct {
|
||||
CVEID string `json:"cve_id"`
|
||||
}
|
||||
|
||||
// TableName ...
|
||||
func (r *CVEWhitelist) TableName() string {
|
||||
return "cve_whitelist"
|
||||
}
|
@ -144,6 +144,7 @@ func init() {
|
||||
beego.Router("/api/system/gc/:id([0-9]+)/log", &GCAPI{}, "get:GetLog")
|
||||
beego.Router("/api/system/gc/schedule", &GCAPI{}, "get:Get;put:Put;post:Post")
|
||||
beego.Router("/api/system/scanAll/schedule", &ScanAllAPI{}, "get:Get;put:Put;post:Post")
|
||||
beego.Router("/api/system/CVEWhitelist", &SysCVEWhitelistAPI{}, "get:Get;put:Put")
|
||||
|
||||
beego.Router("/api/projects/:pid([0-9]+)/robots/", &RobotAPI{}, "post:Post;get:List")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/robots/:id([0-9]+)", &RobotAPI{}, "get:Get;put:Put;delete:Delete")
|
||||
|
74
src/core/api/sys_cve_whitelist.go
Normal file
74
src/core/api/sys_cve_whitelist.go
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// SysCVEWhitelistAPI Handles the requests to manage system level CVE whitelist
|
||||
type SysCVEWhitelistAPI struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
// Prepare validates the request initially
|
||||
func (sca *SysCVEWhitelistAPI) Prepare() {
|
||||
sca.BaseController.Prepare()
|
||||
if !sca.SecurityCtx.IsAuthenticated() {
|
||||
sca.SendUnAuthorizedError(errors.New("Unauthorized"))
|
||||
return
|
||||
}
|
||||
if !sca.SecurityCtx.IsSysAdmin() && sca.Ctx.Request.Method != http.MethodGet {
|
||||
msg := fmt.Sprintf("only system admin has permission issue %s request to this API", sca.Ctx.Request.Method)
|
||||
log.Errorf(msg)
|
||||
sca.SendForbiddenError(errors.New(msg))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get handles the GET request to retrieve the system level CVE whitelist
|
||||
func (sca *SysCVEWhitelistAPI) Get() {
|
||||
l, err := dao.GetSysCVEWhitelist()
|
||||
if err != nil {
|
||||
sca.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
sca.WriteJSONData(l)
|
||||
}
|
||||
|
||||
// Put handles the PUT request to update the system level CVE whitelist
|
||||
func (sca *SysCVEWhitelistAPI) Put() {
|
||||
var l models.CVEWhitelist
|
||||
if err := sca.DecodeJSONReq(&l); err != nil {
|
||||
log.Errorf("Failed to decode JSON array from request")
|
||||
sca.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
if l.ProjectID != 0 {
|
||||
msg := fmt.Sprintf("Non-zero project ID for system CVE whitelist: %d.", l.ProjectID)
|
||||
log.Error(msg)
|
||||
sca.SendBadRequestError(errors.New(msg))
|
||||
return
|
||||
}
|
||||
if _, err := dao.UpdateCVEWhitelist(l); err != nil {
|
||||
sca.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
}
|
110
src/core/api/sys_cve_whitelist_test.go
Normal file
110
src/core/api/sys_cve_whitelist_test.go
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSysCVEWhitelistAPIGet(t *testing.T) {
|
||||
url := "/api/system/CVEWhitelist"
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: url,
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: url,
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestSysCVEWhitelistAPIPut(t *testing.T) {
|
||||
url := "/api/system/CVEWhitelist"
|
||||
s := int64(1573254000)
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: url,
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: url,
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: url,
|
||||
bodyJSON: []string{"CVE-1234-1234"},
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: url,
|
||||
bodyJSON: models.CVEWhitelist{
|
||||
ExpiresAt: &s,
|
||||
Items: []models.CVEWhitelistItem{
|
||||
{CVEID: "CVE-2019-12310"},
|
||||
},
|
||||
ProjectID: 2,
|
||||
},
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: url,
|
||||
bodyJSON: models.CVEWhitelist{
|
||||
ExpiresAt: &s,
|
||||
Items: []models.CVEWhitelistItem{
|
||||
{CVEID: "CVE-2019-12310"},
|
||||
},
|
||||
},
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
@ -96,6 +96,7 @@ func initRouters() {
|
||||
beego.Router("/api/system/gc/:id([0-9]+)/log", &api.GCAPI{}, "get:GetLog")
|
||||
beego.Router("/api/system/gc/schedule", &api.GCAPI{}, "get:Get;put:Put;post:Post")
|
||||
beego.Router("/api/system/scanAll/schedule", &api.ScanAllAPI{}, "get:Get;put:Put;post:Post")
|
||||
beego.Router("/api/system/CVEWhitelist", &api.SysCVEWhitelistAPI{}, "get:Get;put:Put")
|
||||
|
||||
beego.Router("/api/logs", &api.LogAPI{})
|
||||
|
||||
|
@ -33,7 +33,7 @@ func TestMain(m *testing.M) {
|
||||
"harbor_label", "harbor_resource_label", "harbor_user", "img_scan_job", "img_scan_overview",
|
||||
"job_log", "project", "project_member", "project_metadata", "properties", "registry",
|
||||
"replication_policy", "repository", "robot", "role", "schema_migrations", "user_group",
|
||||
"replication_execution", "replication_task", "replication_schedule_job", "oidc_user";`,
|
||||
"replication_execution", "replication_task", "replication_schedule_job", "oidc_user", "cve_whitelist";`,
|
||||
`DROP FUNCTION "update_update_time_at_column"();`,
|
||||
}
|
||||
dao.PrepareTestData(clearSqls, nil)
|
||||
|
Loading…
Reference in New Issue
Block a user