Skip to content

Commit

Permalink
Merge 8698330 into f9ca414
Browse files Browse the repository at this point in the history
  • Loading branch information
matthyx committed Mar 27, 2023
2 parents f9ca414 + 8698330 commit ee24afb
Show file tree
Hide file tree
Showing 11 changed files with 368 additions and 38 deletions.
28 changes: 28 additions & 0 deletions api/v1/testdata/scan-registry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"session": {
"jobIDs": [
"80fc5ba7-e6df-4d8f-ae94-475242cd7345",
"b56211c7-716a-4f9f-b27f-b4942195fa5e"
],
"timestamp": "2023-02-02T13:18:50.316451657+02:00",
"rootJobID": "80fc5ba7-e6df-4d8f-ae94-475242cd7345",
"action": "vulnerability-scan"
},
"imageTag": "k8s.gcr.io/kube-proxy:v1.24.3",
"wlid": "wlid://cluster-minikube/namespace-kube-system/daemonset-kube-proxy",
"isScanned": false,
"containerName": "kube-proxy",
"jobID": "b56211c7-716a-4f9f-b27f-b4942195fa5e",
"parentJobID": "80fc5ba7-e6df-4d8f-ae94-475242cd7345",
"actionIDN": 3,
"credentials": {
"username": "oauth2accesstoken",
"password": "very secret"
},
"credentialsList": [
{
"username": "oauth2accesstoken",
"password": "very secret"
}
]
}
5 changes: 3 additions & 2 deletions cmd/http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ func main() {
router.GET("/v1/liveness", controller.Alive)
router.GET("/v1/readiness", controller.Ready)

group := router.Group(apis.WebsocketScanCommandVersion)
group := router.Group(apis.VulnerabilityScanCommandVersion)
{
group.Use(otelgin.Middleware("kubevuln-svc"))
group.POST("/"+apis.SBOMCalculationCommandPath, controller.GenerateSBOM)
group.POST("/"+apis.WebsocketScanCommandPath, controller.ScanCVE)
group.POST("/"+apis.ContainerScanCommandPath, controller.ScanCVE)
group.POST("/"+apis.RegistryScanCommandPath, controller.ScanRegistry)
}

srv := &http.Server{
Expand Down
42 changes: 33 additions & 9 deletions cmd/http/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,67 @@ func TestScan(t *testing.T) {
tests := []struct {
name string
yamlFile string
url string
expectedCode int
expectedBody string
storage bool
}{
{
"generate SBOM no storage",
"../../api/v1/testdata/scan.yaml",
"/v1/generateSBOM",
200,
"{\"detail\":\"ImageHash=k8s.gcr.io/kube-proxy@sha256:c1b135231b5b1a6799346cd701da4b59e5b7ef8e694ec7b04fb23b8dbe144137\",\"status\":200,\"title\":\"OK\"}",
false,
},
{
"generate SBOM storage",
"../../api/v1/testdata/scan.yaml",
"/v1/generateSBOM",
200,
"{\"detail\":\"ImageHash=k8s.gcr.io/kube-proxy@sha256:c1b135231b5b1a6799346cd701da4b59e5b7ef8e694ec7b04fb23b8dbe144137\",\"status\":200,\"title\":\"OK\"}",
true,
},
{
"phase 1: valid scan command succeeds and reports CVE",
"../../api/v1/testdata/scan.yaml",
"/v1/scanImage",
200,
"{\"detail\":\"Wlid=wlid://cluster-minikube/namespace-kube-system/daemonset-kube-proxy, ImageHash=k8s.gcr.io/kube-proxy@sha256:c1b135231b5b1a6799346cd701da4b59e5b7ef8e694ec7b04fb23b8dbe144137\",\"status\":200,\"title\":\"OK\"}",
false,
},
{
"phase 1: missing fields",
"../../api/v1/testdata/scan-incomplete.yaml",
"/v1/scanImage",
500,
"{\"detail\":\"Wlid=wlid://cluster-bez-longrun3/namespace-kube-system/deployment-coredns, ImageHash=\",\"status\":500,\"title\":\"Internal Server Error\"}",
false,
},
{
"phase 1: invalid yaml",
"../../api/v1/testdata/scan-invalid.yaml",
"/v1/scanImage",
400,
"{\"status\":400,\"title\":\"Bad Request\"}",
false,
},
{
"phase 2: valid scan command succeeds and reports CVE",
"../../api/v1/testdata/scan.yaml",
"/v1/scanImage",
200,
"{\"detail\":\"Wlid=wlid://cluster-minikube/namespace-kube-system/daemonset-kube-proxy, ImageHash=k8s.gcr.io/kube-proxy@sha256:c1b135231b5b1a6799346cd701da4b59e5b7ef8e694ec7b04fb23b8dbe144137\",\"status\":200,\"title\":\"OK\"}",
true,
},
{
"registry scan: valid scan command succeeds and reports CVE",
"../../api/v1/testdata/scan-registry.yaml",
"/v1/scanRegistryImage",
200,
"{\"detail\":\"ImageTag=k8s.gcr.io/kube-proxy:v1.24.3\",\"status\":200,\"title\":\"OK\"}",
false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand All @@ -67,10 +96,11 @@ func TestScan(t *testing.T) {
router.GET("/v1/liveness", controller.Alive)
router.GET("/v1/readiness", controller.Ready)

group := router.Group(apis.WebsocketScanCommandVersion)
group := router.Group(apis.VulnerabilityScanCommandVersion)
{
group.POST("/"+apis.SBOMCalculationCommandPath, controller.GenerateSBOM)
group.POST("/"+apis.WebsocketScanCommandPath, controller.ScanCVE)
group.POST("/"+apis.ContainerScanCommandPath, controller.ScanCVE)
group.POST("/"+apis.RegistryScanCommandPath, controller.ScanRegistry)
}

req, _ := http.NewRequest("GET", "/v1/liveness", nil)
Expand All @@ -83,13 +113,7 @@ func TestScan(t *testing.T) {

file, err := os.Open(test.yamlFile)
tools.EnsureSetup(t, err == nil)
req, _ = http.NewRequest("POST", "/v1/generateSBOM", file)
w = httptest.NewRecorder()
router.ServeHTTP(w, req)

file, err = os.Open(test.yamlFile)
tools.EnsureSetup(t, err == nil)
req, _ = http.NewRequest("POST", "/v1/scanImage", file)
req, _ = http.NewRequest("POST", test.url, file)
w = httptest.NewRecorder()
router.ServeHTTP(w, req)

Expand Down
82 changes: 74 additions & 8 deletions controllers/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,19 @@ func NewHTTPController(scanService ports.ScanService, concurrency int) *HTTPCont
func (h HTTPController) GenerateSBOM(c *gin.Context) {
ctx := c.Request.Context()

var newScan wssc.WebsocketScanCommand
err := c.ShouldBindJSON(&newScan)
var websocketScanCommand wssc.WebsocketScanCommand
err := c.ShouldBindJSON(&websocketScanCommand)
if err != nil {
logger.L().Ctx(ctx).Error("handler error", helpers.Error(err))
problem.Of(http.StatusBadRequest).WriteTo(c.Writer)
return
}

// TODO add proper transformation of wssc.WebsocketScanCommand to domain.ScanCommand
newScan := websocketScanCommandToScanCommand(websocketScanCommand)

details := problem.Detailf("ImageHash=%s", newScan.ImageHash)

ctx, err = h.scanService.ValidateGenerateSBOM(ctx, domain.ScanCommand(newScan))
ctx, err = h.scanService.ValidateGenerateSBOM(ctx, newScan)
if err != nil {
logger.L().Ctx(ctx).Error("validation error", helpers.Error(err), helpers.String("imageID", newScan.ImageHash))
problem.Of(http.StatusInternalServerError).Append(details).WriteTo(c.Writer)
Expand Down Expand Up @@ -80,18 +81,19 @@ func (h HTTPController) Ready(c *gin.Context) {
func (h HTTPController) ScanCVE(c *gin.Context) {
ctx := c.Request.Context()

var newScan wssc.WebsocketScanCommand
err := c.ShouldBindJSON(&newScan)
var websocketScanCommand wssc.WebsocketScanCommand
err := c.ShouldBindJSON(&websocketScanCommand)
if err != nil {
logger.L().Ctx(ctx).Error("handler error", helpers.Error(err))
problem.Of(http.StatusBadRequest).WriteTo(c.Writer)
return
}

// TODO add proper transformation of wssc.WebsocketScanCommand to domain.ScanCommand
newScan := websocketScanCommandToScanCommand(websocketScanCommand)

details := problem.Detailf("Wlid=%s, ImageHash=%s", newScan.Wlid, newScan.ImageHash)

ctx, err = h.scanService.ValidateScanCVE(ctx, domain.ScanCommand(newScan))
ctx, err = h.scanService.ValidateScanCVE(ctx, newScan)
if err != nil {
logger.L().Ctx(ctx).Error("validation error", helpers.Error(err), helpers.String("wlid", newScan.Wlid), helpers.String("imageID", newScan.ImageHash))
problem.Of(http.StatusInternalServerError).Append(details).WriteTo(c.Writer)
Expand All @@ -108,6 +110,70 @@ func (h HTTPController) ScanCVE(c *gin.Context) {
})
}

func websocketScanCommandToScanCommand(c wssc.WebsocketScanCommand) domain.ScanCommand {
command := domain.ScanCommand{
Credentialslist: c.Credentialslist,
ImageHash: c.ImageHash,
Wlid: c.Wlid,
ImageTag: c.ImageTag,
JobID: c.JobID,
ContainerName: c.ContainerName,
LastAction: c.LastAction,
ParentJobID: c.ParentJobID,
Args: c.Args,
Session: sessionChainToSession(c.Session),
}
if c.InstanceID != nil {
command.InstanceID = *c.InstanceID
}
return command
}

func sessionChainToSession(s wssc.SessionChain) domain.Session {
return domain.Session{
JobIDs: s.JobIDs,
}
}

func (h HTTPController) ScanRegistry(c *gin.Context) {
ctx := c.Request.Context()

var registryScanCommand wssc.RegistryScanCommand
err := c.ShouldBindJSON(&registryScanCommand)
if err != nil {
logger.L().Ctx(ctx).Error("handler error", helpers.Error(err))
problem.Of(http.StatusBadRequest).WriteTo(c.Writer)
return
}

newScan := registryScanCommandToScanCommand(registryScanCommand)

details := problem.Detailf("ImageTag=%s", newScan.ImageTag)

ctx, err = h.scanService.ValidateScanRegistry(ctx, newScan)
if err != nil {
logger.L().Ctx(ctx).Error("validation error", helpers.Error(err), helpers.String("imageID", newScan.ImageTag))
problem.Of(http.StatusInternalServerError).Append(details).WriteTo(c.Writer)
return
}

problem.Of(http.StatusOK).Append(details).WriteTo(c.Writer)

h.workerPool.Submit(func() {
err = h.scanService.ScanRegistry(ctx)
if err != nil {
logger.L().Ctx(ctx).Error("service error", helpers.Error(err), helpers.String("imageID", newScan.ImageTag))
}
})
}

func registryScanCommandToScanCommand(c wssc.RegistryScanCommand) domain.ScanCommand {
return domain.ScanCommand{
Credentialslist: c.Credentialslist,
ImageTag: c.ImageTag,
}
}

func (h HTTPController) Shutdown() {
logger.L().Info("purging SBOM creation queue", helpers.String("remaining jobs", strconv.Itoa(h.workerPool.WaitingQueueSize())))
h.workerPool.StopWait()
Expand Down
24 changes: 20 additions & 4 deletions core/domain/scan.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
package domain

import wssc "github.com/armosec/armoapi-go/apis"
import (
"github.com/docker/docker/api/types"
)

type ScanIDKey struct{}
type TimestampKey struct{}
type WorkloadKey struct{}

// ScanCommand is a proxy type for wssc.WebsocketScanCommand used to decouple business logic from implementation
// it might evolve into its own struct at a later time
type ScanCommand wssc.WebsocketScanCommand
type ScanCommand struct {
Credentialslist []types.AuthConfig
ImageHash string
InstanceID string
Wlid string
ImageTag string
JobID string
ContainerName string
LastAction int
ParentJobID string
Args map[string]interface{}
Session Session
}

type Session struct {
JobIDs []string
}
2 changes: 2 additions & 0 deletions core/ports/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ type ScanService interface {
GenerateSBOM(ctx context.Context) error
Ready(ctx context.Context) bool
ScanCVE(ctx context.Context) error
ScanRegistry(ctx context.Context) error
ValidateGenerateSBOM(ctx context.Context, workload domain.ScanCommand) (context.Context, error)
ValidateScanCVE(ctx context.Context, workload domain.ScanCommand) (context.Context, error)
ValidateScanRegistry(ctx context.Context, workload domain.ScanCommand) (context.Context, error)
}
14 changes: 14 additions & 0 deletions core/services/mockscan.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ func (m MockScanService) ScanCVE(context.Context) error {
return errors.New("mock error")
}

func (m MockScanService) ScanRegistry(context.Context) error {
if m.happy {
return nil
}
return errors.New("mock error")
}

func (m MockScanService) ValidateGenerateSBOM(ctx context.Context, _ domain.ScanCommand) (context.Context, error) {
if m.happy {
return ctx, nil
Expand All @@ -49,3 +56,10 @@ func (m MockScanService) ValidateScanCVE(ctx context.Context, _ domain.ScanComma
}
return ctx, errors.New("mock error")
}

func (m MockScanService) ValidateScanRegistry(ctx context.Context, _ domain.ScanCommand) (context.Context, error) {
if m.happy {
return ctx, nil
}
return ctx, errors.New("mock error")
}
Loading

0 comments on commit ee24afb

Please sign in to comment.