Skip to content

feat: resource scanning support #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@ ADD . /go/src/github.com/devtron-labs/image-scanner
RUN GOOS=linux make

FROM alpine:3.17
RUN apk update && apk add --no-cache --virtual .build-deps && apk add bash && apk add make && apk add curl && apk add git && apk add zip && apk add jq
COPY --from=aquasec/trivy:0.46.1 /usr/local/bin/trivy /usr/local/bin/trivy
RUN apk add --no-cache ca-certificates
RUN mkdir -p /security
RUN adduser -D devtron

COPY ./git-ask-pass.sh /git-ask-pass.sh
RUN chmod +x /git-ask-pass.sh

COPY --from=build-env /go/src/github.com/devtron-labs/image-scanner/image-scanner .
COPY ./ssh-config /root/.ssh/config
RUN chmod 644 /root/.ssh/config

RUN chown -R devtron:devtron ./image-scanner
RUN chmod +x ./image-scanner
RUN chown -R devtron:devtron ./security
Expand Down
14 changes: 12 additions & 2 deletions Wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/devtron-labs/common-lib/monitoring"
client "github.com/devtron-labs/common-lib/pubsub-lib"
"github.com/devtron-labs/image-scanner/api"
"github.com/devtron-labs/image-scanner/helper"
"github.com/devtron-labs/image-scanner/internal/logger"
"github.com/devtron-labs/image-scanner/internal/sql"
"github.com/devtron-labs/image-scanner/internal/sql/repository"
Expand Down Expand Up @@ -37,8 +38,8 @@ func InitializeApp() (*App, error) {
pubsub.NewNatSubscription,
grafeasService.NewKlarServiceImpl,
wire.Bind(new(grafeasService.GrafeasService), new(*grafeasService.GrafeasServiceImpl)),
pubsub.NewTestPublishImpl,
wire.Bind(new(pubsub.TestPublish), new(*pubsub.TestPublishImpl)),
//pubsub.NewTestPublishImpl,
//wire.Bind(new(pubsub.TestPublish), new(*pubsub.TestPublishImpl)),

clairService.GetClairConfig,
clairService.NewClairServiceImpl,
Expand Down Expand Up @@ -80,6 +81,15 @@ func InitializeApp() (*App, error) {
repository.NewScanToolExecutionHistoryMappingRepositoryImpl,
wire.Bind(new(repository.ScanToolExecutionHistoryMappingRepository), new(*repository.ScanToolExecutionHistoryMappingRepositoryImpl)),
monitoring.NewMonitoringRouter,
helper.NewGitManagerImpl,
helper.NewGitCliManager,
wire.Bind(new(helper.GitCliManager), new(*helper.GitCliManagerImpl)),

security.NewCodeScanServiceImpl,
wire.Bind(new(security.CodeScanService), new(*security.CodeScanServiceImpl)),

repository.NewResourceScanResultRepositoryImpl,
wire.Bind(new(repository.ResourceScanResultRepository), new(*repository.ResourceScanResultRepositoryImpl)),
)
return &App{}, nil
}
85 changes: 51 additions & 34 deletions api/RestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/devtron-labs/image-scanner/pkg/klarService"
"github.com/devtron-labs/image-scanner/pkg/security"
"github.com/devtron-labs/image-scanner/pkg/user"
"github.com/devtron-labs/image-scanner/pubsub"
//"github.com/devtron-labs/image-scanner/pubsub"
"go.uber.org/zap"
"net/http"
"os"
Expand All @@ -20,33 +20,37 @@ type RestHandler interface {
}

func NewRestHandlerImpl(logger *zap.SugaredLogger,
testPublish pubsub.TestPublish,
//testPublish pubsub.TestPublish,
grafeasService grafeasService.GrafeasService,
userService user.UserService, imageScanService security.ImageScanService,
klarService klarService.KlarService,
clairService clairService.ClairService,
imageScanConfig *security.ImageScanConfig) *RestHandlerImpl {
imageScanConfig *security.ImageScanConfig,
codeScanService security.CodeScanService,
) *RestHandlerImpl {
return &RestHandlerImpl{
logger: logger,
testPublish: testPublish,
logger: logger,
//testPublish: testPublish,
grafeasService: grafeasService,
userService: userService,
imageScanService: imageScanService,
klarService: klarService,
clairService: clairService,
imageScanConfig: imageScanConfig,
codeScanService: codeScanService,
}
}

type RestHandlerImpl struct {
logger *zap.SugaredLogger
testPublish pubsub.TestPublish
logger *zap.SugaredLogger
//testPublish pubsub.TestPublish
grafeasService grafeasService.GrafeasService
userService user.UserService
imageScanService security.ImageScanService
klarService klarService.KlarService
clairService clairService.ClairService
imageScanConfig *security.ImageScanConfig
codeScanService security.CodeScanService
}
type Response struct {
Code int `json:"code,omitempty"`
Expand Down Expand Up @@ -76,6 +80,17 @@ func (impl *RestHandlerImpl) ScanForVulnerability(w http.ResponseWriter, r *http
writeJsonResp(w, err, nil, http.StatusBadRequest)
return
}
result, err := impl.ScanForVulnerabilityEvent(&scanConfig)
if err != nil {
writeJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
impl.logger.Debugw("save", "status", result)
writeJsonResp(w, err, result, http.StatusOK)
}

func (impl *RestHandlerImpl) ScanForVulnerabilityEvent(scanConfig *common.ImageScanEvent) (*common.ScanEventResponse, error) {

if scanConfig.UserId == 0 {
scanConfig.UserId = 1 //setting user as system user in case of empty user data
}
Expand All @@ -84,45 +99,47 @@ func (impl *RestHandlerImpl) ScanForVulnerability(w http.ResponseWriter, r *http
tool, err := impl.imageScanService.GetActiveTool()
if err != nil {
impl.logger.Errorw("err in image scanning", "err", err)
writeJsonResp(w, err, nil, http.StatusInternalServerError)
return
return nil, err
}
executionHistory, executionHistoryDirPath, err := impl.imageScanService.RegisterScanExecutionHistoryAndState(&scanConfig, tool)
executionHistory, executionHistoryDirPath, err := impl.imageScanService.RegisterScanExecutionHistoryAndState(scanConfig, tool)
if err != nil {
impl.logger.Errorw("service err, RegisterScanExecutionHistoryAndState", "err", err)
writeJsonResp(w, err, nil, http.StatusInternalServerError)
return
return nil, err
}
if tool.Name == bean.ScanToolClair && tool.Version == bean.ScanToolVersion2 {
result, err = impl.klarService.Process(&scanConfig, executionHistory)
if err != nil {
impl.logger.Errorw("err in process msg", "err", err)
writeJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
} else if tool.Name == bean.ScanToolClair && tool.Version == bean.ScanToolVersion4 {
result, err = impl.clairService.ScanImage(&scanConfig, tool, executionHistory)

if scanConfig.SourceType == common.SourceTypeCode {
err = impl.codeScanService.ScanCode(scanConfig, tool, executionHistory, executionHistoryDirPath)
if err != nil {
impl.logger.Errorw("err in process msg", "err", err)
writeJsonResp(w, err, nil, http.StatusInternalServerError)
return
impl.logger.Errorw("Error scanning code", "err", err)

}
} else {
err = impl.imageScanService.ScanImage(&scanConfig, tool, executionHistory, executionHistoryDirPath)
if err != nil {
impl.logger.Errorw("err in process msg", "err", err)
writeJsonResp(w, err, nil, http.StatusInternalServerError)
return
if tool.Name == bean.ScanToolClair && tool.Version == bean.ScanToolVersion2 {
result, err = impl.klarService.Process(scanConfig, executionHistory)
if err != nil {
impl.logger.Errorw("err in process msg", "err", err)
return nil, err
}
} else if tool.Name == bean.ScanToolClair && tool.Version == bean.ScanToolVersion4 {
result, err = impl.clairService.ScanImage(scanConfig, tool, executionHistory)
if err != nil {
impl.logger.Errorw("err in process msg", "err", err)
return nil, err
}
} else {
err = impl.imageScanService.ScanImage(scanConfig, tool, executionHistory, executionHistoryDirPath)
if err != nil {
impl.logger.Errorw("err in process msg", "err", err)
return nil, err
}
}
}

//deleting executionDirectoryPath with files as well
err = os.RemoveAll(executionHistoryDirPath)
if err != nil {
impl.logger.Errorw("error in deleting executionHistoryDirectory", "err", err)
writeJsonResp(w, err, nil, http.StatusInternalServerError)
return
return nil, err
}

impl.logger.Debugw("save", "status", result)
writeJsonResp(w, err, result, http.StatusOK)
return result, nil
}
130 changes: 130 additions & 0 deletions cicodescan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
"SchemaVersion": 2,
"CreatedAt": "2024-04-15T22:03:08.717492+05:30",
"ArtifactName": "/tmp/security/devtronimagescan/211/code",
"ArtifactType": "filesystem",
"Metadata": {
"ImageConfig": {
"architecture": "",
"created": "0001-01-01T00:00:00Z",
"os": "",
"rootfs": {
"type": "",
"diff_ids": null
},
"config": {}
}
},
"Results": [
{
"Target": "Dockerfile",
"Class": "config",
"Type": "dockerfile",
"MisconfSummary": {
"Successes": 24,
"Failures": 3,
"Exceptions": 0
},
"Misconfigurations": [
{
"Type": "Dockerfile Security Check",
"ID": "DS001",
"AVDID": "AVD-DS-0001",
"Title": "':latest' tag used",
"Description": "When using a 'FROM' statement you should use a specific tag to avoid uncontrolled behavior when the image is updated.",
"Message": "Specify a tag in the 'FROM' statement for image 'nginx'",
"Namespace": "builtin.dockerfile.DS001",
"Query": "data.builtin.dockerfile.DS001.deny",
"Resolution": "Add a tag to the image in the 'FROM' statement",
"Severity": "MEDIUM",
"PrimaryURL": "https://avd.aquasec.com/misconfig/ds001",
"References": [
"https://avd.aquasec.com/misconfig/ds001"
],
"Status": "FAIL",
"Layer": {},
"CauseMetadata": {
"Provider": "Dockerfile",
"Service": "general",
"StartLine": 1,
"EndLine": 1,
"Code": {
"Lines": [
{
"Number": 1,
"Content": "FROM nginx",
"IsCause": true,
"Annotation": "",
"Truncated": false,
"Highlighted": "\u001b[38;5;64mFROM\u001b[0m\u001b[38;5;37m nginx",
"FirstCause": true,
"LastCause": true
}
]
}
}
},
{
"Type": "Dockerfile Security Check",
"ID": "DS002",
"AVDID": "AVD-DS-0002",
"Title": "Image user should not be 'root'",
"Description": "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.",
"Message": "Specify at least 1 USER command in Dockerfile with non-root user as argument",
"Namespace": "builtin.dockerfile.DS002",
"Query": "data.builtin.dockerfile.DS002.deny",
"Resolution": "Add 'USER \u003cnon root user name\u003e' line to the Dockerfile",
"Severity": "HIGH",
"PrimaryURL": "https://avd.aquasec.com/misconfig/ds002",
"References": [
"https://docs.docker.com/develop/develop-images/dockerfile_best-practices/",
"https://avd.aquasec.com/misconfig/ds002"
],
"Status": "FAIL",
"Layer": {},
"CauseMetadata": {
"Provider": "Dockerfile",
"Service": "general",
"Code": {
"Lines": null
}
}
},
{
"Type": "Dockerfile Security Check",
"ID": "DS026",
"AVDID": "AVD-DS-0026",
"Title": "No HEALTHCHECK defined",
"Description": "You should add HEALTHCHECK instruction in your docker container images to perform the health check on running containers.",
"Message": "Add HEALTHCHECK instruction in your Dockerfile",
"Namespace": "builtin.dockerfile.DS026",
"Query": "data.builtin.dockerfile.DS026.deny",
"Resolution": "Add HEALTHCHECK instruction in Dockerfile",
"Severity": "LOW",
"PrimaryURL": "https://avd.aquasec.com/misconfig/ds026",
"References": [
"https://blog.aquasec.com/docker-security-best-practices",
"https://avd.aquasec.com/misconfig/ds026"
],
"Status": "FAIL",
"Layer": {},
"CauseMetadata": {
"Provider": "Dockerfile",
"Service": "general",
"Code": {
"Lines": null
}
}
}
]
},
{
"Target": "OS Packages",
"Class": "license"
},
{
"Target": "Loose File License(s)",
"Class": "license-file"
}
]
}
26 changes: 26 additions & 0 deletions common/bean.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package common

import (
"github.com/devtron-labs/image-scanner/helper"
//"github.com/devtron-labs/image-scanner/internal/sql/repository"
"github.com/optiopay/klar/clair"
"github.com/quay/claircore"
"strings"
Expand All @@ -16,6 +18,7 @@ const (
GCR_FILE_PATH = "FILE_PATH"
IMAGE_NAME = "IMAGE_NAME"
OUTPUT_FILE_PATH = "OUTPUT_FILE_PATH"
EXTRA_ARGS = "EXTRA_ARGS"
)

const (
Expand Down Expand Up @@ -49,6 +52,13 @@ type ImageScanEvent struct {
Token string `json:"token"`
AwsRegion string `json:"awsRegion"`
DockerRegistryId string `json:"dockerRegistryId"`
//ScanHistoryId int `json:"scanHistoryId"`
CiProjectDetails []helper.CiProjectDetails `json:"ciProjectDetails"`
SourceType SourceType `json:"sourceType"`
SourceSubType SourceSubType `json:"sourceSubType"`
CiWorkflowId int `json:"ciWorkflowId"`
CdWorkflowId int `json:"cdWorkflowId"`
ChartHistoryId int `json:"chartHistoryId"`
}

type ScanEventResponse struct {
Expand Down Expand Up @@ -132,3 +142,19 @@ func RemoveTrailingComma(jsonString string) string {
}
return jsonString
}

// multiple history rows for one source event
type SourceType int

const (
SourceTypeImage SourceType = 1
SourceTypeCode SourceType = 2
SourceTypeSbom SourceType = 3 // can be used in future for direct sbom scanning
)

type SourceSubType int

const (
SourceSubTypeCi SourceSubType = 1 // relevant for ci code(2,1) or ci built image(1,1)
SourceSubTypeManifest SourceSubType = 2 // relevant for devtron app deployment manifest/helm app manifest(2,2) or images retrieved from manifest(1,2))
)
Loading