diff --git a/api/codehash.go b/api/codehash.go new file mode 100644 index 00000000..fc692ce7 --- /dev/null +++ b/api/codehash.go @@ -0,0 +1,30 @@ +package api + +import ( + "context" + "github.com/murphysecurity/murphysec/utils" + "github.com/murphysecurity/murphysec/utils/must" + "io" + "net/http" + "os" +) + +func SubmitCodeHash(ctx context.Context, client *Client, path string) error { + checkNotNull(client) + var u = joinURL(client.baseUrl, "/platform3/v3/client/upload_feature").String() + var fBody = func() (io.ReadCloser, error) { + f, e := os.Open(path) + if e != nil { + return nil, e + } + return utils.NewBufferedReader(f), nil + } + firstTimeBody, e := fBody() + if e != nil { + return e + } + var req = must.A(http.NewRequestWithContext(ctx, http.MethodPost, u, firstTimeBody)) + req.GetBody = fBody + req.Header.Set("Content-Type", "application/json") + return client.DoJson(req, nil) +} diff --git a/api/create_sub_task.go b/api/create_sub_task.go index 09d43653..944ea2a8 100644 --- a/api/create_sub_task.go +++ b/api/create_sub_task.go @@ -27,6 +27,7 @@ type CreateSubTaskRequest struct { WebhookAddr *string `json:"webhook_addr,omitempty"` WebhookMode *string `json:"webhook_mode,omitempty"` ExtraData *string `json:"extra_data,omitempty"` + IsAutonomous bool `json:"is_autonomous"` } type CreateSubTaskResponse struct { diff --git a/cmd/murphy/internal/scan/cmd.go b/cmd/murphy/internal/scan/cmd.go index 7042b0d9..84883d89 100644 --- a/cmd/murphy/internal/scan/cmd.go +++ b/cmd/murphy/internal/scan/cmd.go @@ -39,6 +39,7 @@ var sbomOutputType common.SBOMFormatFlag var webhookAddr string var webhookMode common.WebhookModeFlag var extraData string +var scanCodeHash bool func Cmd() *cobra.Command { var c cobra.Command @@ -58,6 +59,7 @@ func Cmd() *cobra.Command { c.Flags().StringVar(&webhookAddr, "webhook-addr", "", "specify the webhook address") c.Flags().Var(&webhookMode, "webhook-mode", "specify the webhook mode, currently supports: simple, full") c.Flags().StringVar(&extraData, "extra-data", "", "specify the extra data") + c.Flags().BoolVar(&scanCodeHash, "scan-snippets", false, "Enable scanning of code snippets to detect SBOM and vulnerabilities. Disabled by default") return &c } @@ -79,6 +81,7 @@ func DfCmd() *cobra.Command { c.Flags().StringVar(&webhookAddr, "webhook-addr", "", "specify the webhook address") c.Flags().Var(&webhookMode, "webhook-mode", "specify the webhook mode, currently supports: simple, full(default)") c.Flags().StringVar(&extraData, "extra-data", "", "specify the extra data") + c.Flags().BoolVar(&scanCodeHash, "scan-snippets", false, "Enable scanning of code snippets to detect SBOM and vulnerabilities. Disabled by default") return &c } diff --git a/cmd/murphy/internal/scan/scan.go b/cmd/murphy/internal/scan/scan.go index d340764d..c97027ff 100644 --- a/cmd/murphy/internal/scan/scan.go +++ b/cmd/murphy/internal/scan/scan.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/murphysecurity/murphysec/codehash" "os" "path/filepath" @@ -151,6 +152,7 @@ func scan(ctx context.Context, dir string, accessType model.AccessType, mode mod createSubtask.PackagePrivateId = privateSourceId createSubtask.PackagePrivateName = privateSourceName createSubtask.ProjectTagNames = projectTagNames + createSubtask.IsAutonomous = scanCodeHash if createSubtask.ProjectTagNames == nil { createSubtask.ProjectTagNames = make([]string, 0) } @@ -204,12 +206,18 @@ func scan(ctx context.Context, dir string, accessType model.AccessType, mode mod MavenSourceId: privateSourceId, MavenSourceName: privateSourceName, IsNoBuild: noBuild, + IsAutonomous: scanCodeHash, } if gitSummary != nil { task.GitUrl = gitSummary.RemoteAddr } ctx = model.WithScanTask(ctx, task) + if scanCodeHash && mode == model.ScanModeSource { + logger.Infof("code hash scanning begin...") + codehash.Scan(ctx) + logger.Infof("completed") + } if task.Mode == model.ScanModeSource { // do scan e = inspector.ManagedInspect(ctx) diff --git a/codehash/codehash.go b/codehash/codehash.go new file mode 100644 index 00000000..a7db3ab8 --- /dev/null +++ b/codehash/codehash.go @@ -0,0 +1,55 @@ +package codehash + +import ( + "bufio" + "context" + "github.com/murphysecurity/murphysec/api" + "github.com/murphysecurity/murphysec/infra/logctx" + "github.com/murphysecurity/murphysec/model" + "github.com/murphysecurity/murphysec/utils/must" + "os" + "os/exec" + "path/filepath" + "runtime" +) + +func Scan(ctx context.Context) { + var logger = logctx.Use(ctx) + var base = filepath.Dir(must.A(os.Executable())) + + var st = model.UseScanTask(ctx) + tempFile := must.A(os.CreateTemp("", "codehash-output-*.json")) + var tempFilePath = tempFile.Name() + _ = tempFile.Close() + _ = os.Remove(tempFilePath) + defer func() { _ = os.Remove(tempFilePath) }() + var cmd *exec.Cmd + var shPath string + if runtime.GOOS == "windows" { + shPath = filepath.Join(base, "bin\\cli.bat") + cmd = exec.CommandContext(ctx, shPath, st.ProjectPath, tempFilePath, st.SubtaskId) + } else { + shPath = filepath.Join(base, "bin/cli") + fi := must.A(os.Stat(shPath)) + must.Must(os.Chmod(shPath, fi.Mode()|0111)) + cmd = exec.CommandContext(ctx, "sh", filepath.Join(base, "bin/cli"), st.ProjectPath, tempFilePath, st.SubtaskId) + } + stderr := must.A(cmd.StderrPipe()) + go func() { + defer func() { _ = stderr.Close() }() + b := bufio.NewScanner(stderr) + for b.Scan() { + logger.Sugar().Infof("codehash[e]: %s", b.Text()) + } + }() + e := cmd.Run() + if e != nil { + logger.Sugar().Errorf("codehash error: %s", e.Error()) + return + } + e = api.SubmitCodeHash(ctx, api.DefaultClient(), tempFilePath) + if e != nil { + logger.Sugar().Errorf("codehash submit error: %s", e.Error()) + return + } +} diff --git a/model/scantask.go b/model/scantask.go index 8e49035e..9b858965 100644 --- a/model/scantask.go +++ b/model/scantask.go @@ -20,6 +20,7 @@ type ScanTask struct { MavenSourceName string IsNoBuild bool IsInternalCmd bool + IsAutonomous bool } func (s *ScanTask) BuildInspectionTask(dir string) *InspectionTask { diff --git a/utils/io.go b/utils/io.go index cf855ec6..6b10a16e 100644 --- a/utils/io.go +++ b/utils/io.go @@ -44,3 +44,19 @@ func (m mwc) Close() error { } var _ io.WriteCloser = (*mwc)(nil) + +func NewBufferedReader(reader io.Reader) io.ReadCloser { + return &br{Reader: reader} +} + +type br struct { + io.Reader +} + +func (b *br) Close() error { + rc, ok := b.Reader.(io.Closer) + if !ok { + return nil + } + return rc.Close() +}