Skip to content
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# NoNonSec No-nonsense Security
# NoNonSec - No-nonsense Security

## Overview

Expand Down
212 changes: 212 additions & 0 deletions cmd/nononsec/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"

cdx "github.com/CycloneDX/cyclonedx-go"

Check failure on line 14 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, e2e)

import 'github.com/CycloneDX/cyclonedx-go' is not allowed from list 'main' (depguard)

Check failure on line 14 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, integration)

import 'github.com/CycloneDX/cyclonedx-go' is not allowed from list 'main' (depguard)

Check failure on line 14 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, component)

import 'github.com/CycloneDX/cyclonedx-go' is not allowed from list 'main' (depguard)
log "github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -100,4 +104,212 @@
}

log.Infof("Detected project type: %s", projectType)

GenerateAllSBOMs()
}

type GoModule struct {
Path string
Version string
Indirect bool
Replace *struct {
Path string
Version string
}
}

type GoPackage struct {
Module *GoModule
}

// loadModuleIndirectMap loads the indirect flag for all modules in the repo (go list -m -json all)

Check failure on line 125 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, e2e)

Comment should end in a period (godot)

Check failure on line 125 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, integration)

Comment should end in a period (godot)

Check failure on line 125 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, component)

Comment should end in a period (godot)
func loadModuleIndirectMap(root string) (map[string]bool, error) {
cmd := exec.Command("go", "list", "-m", "-json", "all")
cmd.Dir = root
cmd.Env = append(os.Environ(), "GO111MODULE=on")

out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("go list -m -json all failed: %w", err)
}

dec := json.NewDecoder(bytes.NewReader(out))
indirectMap := make(map[string]bool)
for dec.More() {
var mod GoModule
if err := dec.Decode(&mod); err != nil {

Check failure on line 140 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, e2e)

the given struct should be annotated with the `json` tag (musttag)

Check failure on line 140 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, integration)

the given struct should be annotated with the `json` tag (musttag)

Check failure on line 140 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, component)

the given struct should be annotated with the `json` tag (musttag)
return nil, err
}
path := mod.Path
version := mod.Version
if mod.Replace != nil {
path = mod.Replace.Path
version = mod.Replace.Version
}
key := path + "@" + version
indirectMap[key] = mod.Indirect
}

return indirectMap, nil
}

// findApps looks for dirs under cmd/ with main.go file

Check failure on line 156 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, e2e)

Comment should end in a period (godot)

Check failure on line 156 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, integration)

Comment should end in a period (godot)

Check failure on line 156 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, component)

Comment should end in a period (godot)
func findApps(cmdDir string) ([]string, error) {
entries, err := os.ReadDir(cmdDir)
if err != nil {
return nil, err
}
var apps []string
for _, e := range entries {
if e.IsDir() {
mainGo := filepath.Join(cmdDir, e.Name(), "main.go")
if fi, err := os.Stat(mainGo); err == nil && !fi.IsDir() {
apps = append(apps, filepath.Join(cmdDir, e.Name()))
}
}
}
return apps, nil

Check failure on line 171 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, e2e)

return with no blank line before (nlreturn)

Check failure on line 171 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, integration)

return with no blank line before (nlreturn)

Check failure on line 171 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, component)

return with no blank line before (nlreturn)
}

// listUsedModules runs `go list -json -deps ./...` in dir and collects all used modules

Check failure on line 174 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, e2e)

Comment should end in a period (godot)

Check failure on line 174 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, integration)

Comment should end in a period (godot)

Check failure on line 174 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, component)

Comment should end in a period (godot)
func listUsedModules(dir string) (map[string]GoModule, error) {
cmd := exec.Command("go", "list", "-json", "-deps", "./...")
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GO111MODULE=on")

out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("go list failed in %s: %w", dir, err)
}

dec := json.NewDecoder(bytes.NewReader(out))
modules := make(map[string]GoModule)

for dec.More() {
var pkg GoPackage
if err := dec.Decode(&pkg); err != nil {

Check failure on line 190 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, e2e)

the given struct should be annotated with the `json` tag (musttag)

Check failure on line 190 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, integration)

the given struct should be annotated with the `json` tag (musttag)

Check failure on line 190 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, component)

the given struct should be annotated with the `json` tag (musttag)
return nil, err
}
if pkg.Module == nil {
continue
}
mod := *pkg.Module
// Use replaced module path/version if any
if mod.Replace != nil {
mod.Path = mod.Replace.Path
mod.Version = mod.Replace.Version
}
if mod.Version == "" {
mod.Version = "unknown"
}
key := mod.Path + "@" + mod.Version
if _, exists := modules[key]; !exists {
modules[key] = mod
}
}

return modules, nil
}

func generateSBOM(appDir string, modules map[string]GoModule, indirectMap map[string]bool) error {
appName := filepath.Base(appDir)

var comps []cdx.Component
for _, mod := range modules {
key := mod.Path + "@" + mod.Version
indirect := indirectMap[key]

componentsType := cdx.ComponentTypeLibrary
name := mod.Path
version := mod.Version

c := cdx.Component{
Type: componentsType,
Name: name,
Version: version,
PackageURL: fmt.Sprintf("pkg:golang/%s@%s", name, version),
}

if indirect {
// Add indirect info as evidence in the properties
c.Properties = &[]cdx.Property{
{
Name: "indirect",
Value: "true",
},
}
}

comps = append(comps, c)
}

bom := &cdx.BOM{
SpecVersion: cdx.SpecVersion1_6,
Version: 1,
Components: &comps,
Metadata: &cdx.Metadata{
Tools: &cdx.ToolsChoice{
Components: &[]cdx.Component{{
Type: cdx.ComponentTypeApplication,
Name: appName,
Version: "1.0.0",
Supplier: &cdx.OrganizationalEntity{
Name: "SBOM Generator",
},
}},
},
},
}

outFile := fmt.Sprintf("sbom-%s.json", appName)
f, err := os.Create(outFile)
if err != nil {
return err
}
defer f.Close()

enc := cdx.NewBOMEncoder(f, cdx.BOMFileFormatJSON)
enc.SetPretty(true)
return enc.Encode(bom)

Check failure on line 273 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, e2e)

return with no blank line before (nlreturn)

Check failure on line 273 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, integration)

return with no blank line before (nlreturn)

Check failure on line 273 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, component)

return with no blank line before (nlreturn)
}

func GenerateAllSBOMs() {
root, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get working dir: %v\n", err)
os.Exit(1)
}

cmdDir := filepath.Join(root, "cmd")
apps, err := findApps(cmdDir)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to find apps in %s: %v\n", cmdDir, err)
os.Exit(1)
}

if len(apps) == 0 {
fmt.Fprintf(os.Stderr, "no apps found in %s\n", cmdDir)
os.Exit(1)
}

indirectMap, err := loadModuleIndirectMap(root)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load indirect modules map: %v\n", err)
os.Exit(1)
}

for _, app := range apps {
fmt.Printf("Generating SBOM for app: %s\n", filepath.Base(app))

Check failure on line 302 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, e2e)

use of `fmt.Printf` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` (forbidigo)

Check failure on line 302 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, integration)

use of `fmt.Printf` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` (forbidigo)

Check failure on line 302 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, component)

use of `fmt.Printf` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` (forbidigo)
modules, err := listUsedModules(app)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to list used modules for %s: %v\n", app, err)
continue
}
err = generateSBOM(app, modules, indirectMap)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to generate SBOM for %s: %v\n", app, err)
} else {
fmt.Printf("SBOM for %s written\n", filepath.Base(app))

Check failure on line 312 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, e2e)

use of `fmt.Printf` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` (forbidigo)

Check failure on line 312 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, integration)

use of `fmt.Printf` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` (forbidigo)

Check failure on line 312 in cmd/nononsec/main.go

View workflow job for this annotation

GitHub Actions / mcvs-golang-action (lint, component)

use of `fmt.Printf` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` (forbidigo)
}
}
}
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ module github.com/030/nononsec

go 1.24.3

require github.com/sirupsen/logrus v1.9.3
require (
github.com/CycloneDX/cyclonedx-go v0.9.2
github.com/sirupsen/logrus v1.9.3
)

require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
18 changes: 16 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo=
github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -6,10 +10,20 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo=
github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading