Skip to content

Commit

Permalink
add scvs command
Browse files Browse the repository at this point in the history
Signed-off-by: Vivek Kumar Sahu <[email protected]>
  • Loading branch information
viveksahu26 committed Sep 5, 2024
1 parent 9827d76 commit 4c7085d
Show file tree
Hide file tree
Showing 3 changed files with 285 additions and 0 deletions.
69 changes: 69 additions & 0 deletions cmd/scvs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2023 Interlynk.io
//
// 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 cmd

import (
"context"
"fmt"

"github.com/interlynk-io/sbomqs/pkg/engine"
"github.com/interlynk-io/sbomqs/pkg/logger"
"github.com/spf13/cobra"
)

var scvsCmd = &cobra.Command{
Use: "scvs",
Short: "sbom component vs",
SilenceUsage: true,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) <= 0 {
if len(inFile) <= 0 && len(inDirPath) <= 0 {
return fmt.Errorf("provide a path to an sbom file or directory of sbom files")
}
}
return nil
},
RunE: processScvs,
}

func processScvs(cmd *cobra.Command, args []string) error {
debug, _ := cmd.Flags().GetBool("debug")
if debug {
logger.InitDebugLogger()
} else {
logger.InitProdLogger()
}

ctx := logger.WithLogger(context.Background())
uCmd := toUserCmd(cmd, args)

if err := validateFlags(uCmd); err != nil {
return err
}

engParams := toEngineParams(uCmd)
return engine.RunScvs(ctx, engParams)
}

func init() {
rootCmd.AddCommand(scvsCmd)

// Debug Control
scvsCmd.Flags().BoolP("debug", "D", false, "scvs compliance")

// Output Control
// scvsCmd.Flags().BoolP("json", "j", false, "results in json")
scvsCmd.Flags().BoolP("detailed", "d", true, "results in table format, default")
// scvsCmd.Flags().BoolP("basic", "b", false, "results in single line format")
}
134 changes: 134 additions & 0 deletions pkg/engine/scvs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2023 Interlynk.io
//
// 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 engine

import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/interlynk-io/sbomqs/pkg/logger"
"github.com/interlynk-io/sbomqs/pkg/sbom"
"github.com/interlynk-io/sbomqs/pkg/scvs"
)

func RunScvs(ctx context.Context, ep *Params) error {
log := logger.FromContext(ctx)
log.Debug("engine.Run()")
log.Debug(ep)

if len(ep.Path) <= 0 {
log.Fatal("path is required")
}

return handleScvsPaths(ctx, ep)
}

func handleScvsPaths(ctx context.Context, ep *Params) error {
log := logger.FromContext(ctx)
log.Debug("engine.handlePaths()")

var docs []sbom.Document
var paths []string
var scores []scvs.ScvsScores

for _, path := range ep.Path {
log.Debugf("Processing path :%s\n", path)
pathInfo, _ := os.Stat(path)
if pathInfo.IsDir() {
files, err := os.ReadDir(path)
if err != nil {
log.Debugf("os.ReadDir failed for path:%s\n", path)
log.Debugf("%s\n", err)
continue
}
for _, file := range files {
log.Debugf("Processing file :%s\n", file.Name())
if file.IsDir() {
continue
}
path := filepath.Join(path, file.Name())
doc, scs, err := processScvsFile(ctx, ep, path)
if err != nil {
continue
}
docs = append(docs, doc)
scores = append(scores, scs)
paths = append(paths, path)
}
continue
}

doc, scs, err := processScvsFile(ctx, ep, path)
if err != nil {
continue
}
docs = append(docs, doc)
scores = append(scores, scs)
paths = append(paths, path)
}

reportFormat := "detailed"
if ep.Basic {
reportFormat = "basic"
} else if ep.JSON {
reportFormat = "json"
}

nr := scvs.NewScvsReport(ctx,
docs,
scores,
paths,
scvs.WithFormat(strings.ToLower(reportFormat)))

fmt.Println("Print the scvs report")
nr.ScvsReport()

return nil
}

func processScvsFile(ctx context.Context, ep *Params, path string) (sbom.Document, scvs.ScvsScores, error) {
log := logger.FromContext(ctx)
log.Debugf("Processing file :%s\n", path)

if _, err := os.Stat(path); err != nil {
log.Debugf("os.Stat failed for file :%s\n", path)
fmt.Printf("failed to stat %s\n", path)
return nil, nil, err
}

f, err := os.Open(path)
if err != nil {
log.Debugf("os.Open failed for file :%s\n", path)
fmt.Printf("failed to open %s\n", path)
return nil, nil, err
}
defer f.Close()

doc, err := sbom.NewSBOMDocument(ctx, f)
if err != nil {
log.Debugf("failed to create sbom document for :%s\n", path)
log.Debugf("%s\n", err)
fmt.Printf("failed to parse %s : %s\n", path, err)
return nil, nil, err
}

sr := scvs.NewScvsScorer(ctx, doc)

scores := sr.ScvsScore()
return doc, scores, nil
}
82 changes: 82 additions & 0 deletions pkg/scvs/scvsReport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package scvs

import (
"context"
"fmt"
"os"
"sort"
"strings"

"github.com/interlynk-io/sbomqs/pkg/sbom"
"github.com/olekukonko/tablewriter"
)

type ScvsReporter struct {
Ctx context.Context

Docs []sbom.Document
Scores []ScvsScores
Paths []string

// optional params
Format string
}
type Option func(r *ScvsReporter)

func WithFormat(c string) Option {
return func(r *ScvsReporter) {
r.Format = c
}
}

func NewScvsReport(ctx context.Context, doc []sbom.Document, scores []ScvsScores, paths []string, opts ...Option) *ScvsReporter {
r := &ScvsReporter{
Ctx: ctx,
Docs: doc,
Scores: scores,
Paths: paths,
}
return r
}

func (r *ScvsReporter) ScvsReport() {
r.detailedScvsReport()
}

func (r *ScvsReporter) detailedScvsReport() {
for index := range r.Paths {
// doc := r.Docs[index]
scores := r.Scores[index]

outDoc := [][]string{}

for _, score := range scores.ScoreList() {
var l []string

Check failure on line 54 in pkg/scvs/scvsReport.go

View workflow job for this annotation

GitHub Actions / lint

S1021: should merge variable declaration with assignment on next line (gosimple)

l = []string{score.Feature(), score.L1Score(), score.L2Score(), score.L3Score(), score.Descr()}

outDoc = append(outDoc, l)
}

sort.Slice(outDoc, func(i, j int) bool {
switch strings.Compare(outDoc[i][0], outDoc[j][0]) {
case -1:
return true
case 1:
return false
}
return outDoc[i][1] < outDoc[j][1]
})

// fmt.Printf("SBOM Quality by Interlynk Score:%0.1f\tcomponents:%d\t%s\n", scores.AvgScore(), len(doc.Components()), path)
fmt.Println("Analysis of SCVS Report by OWASP Organization using SBOMQS Tool")
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Feature", "Level 1", "Level 2", "Level 3", "Desc"})
table.SetRowLine(true)
table.SetAutoWrapText(false)
table.SetColMinWidth(0, 60)
table.SetAutoMergeCellsByColumnIndex([]int{0})
table.AppendBulk(outDoc)
table.Render()
}
}

0 comments on commit 4c7085d

Please sign in to comment.