Skip to content

Commit

Permalink
Merge pull request #120 from l3montree-dev/119-respect-applied-mitiga…
Browse files Browse the repository at this point in the history
…tion-options-during-scan-integrate-devguard-scanner-into-this-github-repo

119 respect applied mitigation options during scan integrate devguard scanner into this GitHub repo
  • Loading branch information
seb-kw authored Jul 17, 2024
2 parents 25e573c + 6728313 commit 681e7b2
Show file tree
Hide file tree
Showing 39 changed files with 624 additions and 213 deletions.
4 changes: 3 additions & 1 deletion charts/devguard/templates/devguard/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ spec:
value: devguard
- name: POSTGRES_HOST
value: postgresql
- name: ORY_KRATOS
- name: ORY_KRATOS_PUBLIC
value: http://kratos:4433
- name: ORY_KRATOS_ADMIN
value: http://kratos:4434
- name: GITHUB_APP_ID
value: {{ .Values.api.github.appId | quote }}
- name: GITHUB_PRIVATE_KEY
Expand Down
20 changes: 20 additions & 0 deletions charts/devguard/templates/kratos/kratos-networkpolicy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# only devguard is allowed to communicate with kratos
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: kratos
namespace: "{{ .Release.Namespace }}"
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: kratos
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: devguard-api
ports:
- protocol: TCP
port: 4434 # restrict access to the admin port
3 changes: 3 additions & 0 deletions charts/devguard/templates/kratos/kratos-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ spec:
- name: http
port: 4433
targetPort: 4433
- name: http-admin
port: 4434
targetPort: 4434
selector:
app.kubernetes.io/name: kratos
160 changes: 148 additions & 12 deletions cmd/flawfind/main.go → cmd/devguard-scanner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ import (
"encoding/json"
"fmt"
"log"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
"unicode/utf8"

"log/slog"
"net/http"
Expand All @@ -32,16 +35,19 @@ import (
"time"

"github.com/google/uuid"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/l3montree-dev/devguard/internal/core"
"github.com/l3montree-dev/devguard/internal/core/flaw"
"github.com/l3montree-dev/devguard/internal/core/pat"
"github.com/l3montree-dev/devguard/internal/core/vulndb/scan"
"github.com/l3montree-dev/devguard/internal/utils"
"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "flawfind",
Use: "devguard-scanner",
Short: "Vulnerability management for devs.",
Long: `Flawfind is a tool to identify vulnerabilities and flaws in a software. It communicates the result to a devguard instance.`,
Long: `Devguard-Scanner is a tool to identify vulnerabilities and flaws in a software. It communicates the result to a devguard instance.`,
}

func Execute() {
Expand All @@ -51,12 +57,13 @@ func Execute() {
}
}

func generateSBOM() (*os.File, error) {
func generateSBOM(path string) (*os.File, error) {
// generate random name
filename := uuid.New().String() + ".json"

// run the sbom generator
cmd := exec.Command("cdxgen", "-o", filename)
cmd.Dir = path

err := cmd.Run()

Expand All @@ -65,13 +72,58 @@ func generateSBOM() (*os.File, error) {
}

// open the file and return the path
return os.Open(filename)
return os.Open(filepath.Join(path, filename))
}

func getCurrentVersion() (string, int, error) {
// containsRune checks if a string contains a specific rune
func containsRune(s string, r rune) bool {
for _, char := range s {
if char == r {
return true
}
}
return false
}

// IsValidPath checks if a string is a valid file path
func isValidPath(path string) (bool, error) {
// Check for null bytes
if !utf8.ValidString(path) || len(path) == 0 {
return false, fmt.Errorf("path contains null bytes")
}

// Check for invalid characters
invalidChars := `<>:"\|?*`
for _, char := range invalidChars {
if containsRune(path, char) {
return false, fmt.Errorf("invalid character '%c' in path", char)
}
}

// Check if the path length is within the acceptable limit
if len(path) > 260 {
return false, fmt.Errorf("path length exceeds 260 characters")
}

// Check if the path is either absolute or relative
absPath, err := filepath.Abs(path)
if err != nil {
return false, err
}

// Check if the path exists
if _, err := os.Stat(absPath); os.IsNotExist(err) {
return false, fmt.Errorf("path does not exist")
}

return true, nil
}

func getCurrentVersion(path string) (string, int, error) {
cmd := exec.Command("git", "tag", "--sort=-v:refname")
var out bytes.Buffer
cmd.Stdout = &out
cmd.Dir = path
err := cmd.Run()
if err != nil {
log.Fatal(err)
Expand All @@ -96,6 +148,7 @@ func getCurrentVersion() (string, int, error) {
cmd = exec.Command("git", "rev-list", "--count", latestTag+"..HEAD") // nolint:all:Latest Tag is already checked against a semver regex.
var commitOut bytes.Buffer
cmd.Stdout = &commitOut
cmd.Dir = path
err = cmd.Run()
if err != nil {
log.Fatal(err)
Expand All @@ -116,6 +169,7 @@ func init() {
rootCmd.PersistentFlags().String("assetName", "", "The id of the asset which is scanned")
rootCmd.PersistentFlags().String("token", "", "The personal access token to authenticate the request")
rootCmd.PersistentFlags().String("apiUrl", "https://api.devguard.dev", "The url of the API to send the scan request to")

err := rootCmd.MarkPersistentFlagRequired("assetName")
if err != nil {
slog.Error("could not mark flag as required", "err", err)
Expand All @@ -127,11 +181,11 @@ func init() {
os.Exit(1)
}

rootCmd.AddCommand(&cobra.Command{
scaCommand := &cobra.Command{
Use: "sca",
Short: "Software composition analysis",
Long: `Scan a SBOM for vulnerabilities. This command will scan a SBOM for vulnerabilities and return a list of vulnerabilities found in the SBOM. The SBOM must be passed as an argument.`,
Args: cobra.ExactArgs(0),
// Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
core.InitLogger()
token, err := cmd.Flags().GetString("token")
Expand All @@ -149,16 +203,39 @@ func init() {
slog.Error("could not get api url", "err", err)
return
}
failOnRisk, err := cmd.Flags().GetString("fail-on-risk")
if err != nil {
slog.Error("could not get fail-on-risk", "err", err)
return
}

webUI, err := cmd.Flags().GetString("webUI")
if err != nil {
slog.Error("could not get webUI", "err", err)
return
}

err = core.LoadConfig()
if err != nil {
slog.Warn("could not initialize config", "err", err)
}

path, err := cmd.Flags().GetString("path")
if err != nil {
slog.Error("could not get path", "err", err)
return
}

if isValid, err := isValidPath(path); !isValid && err != nil {
slog.Error("invalid path", "err", err)
return
}

// we use the commit count, to check if we should create a new version - or if its dirty.
// v1.0.0 - . . . . . . . . . . - v1.0.1
// all commits after v1.0.0 are part of v1.0.1
// if there are no commits after the tag, we are on a clean tag
version, commitAfterTag, err := getCurrentVersion()
version, commitAfterTag, err := getCurrentVersion(path)
if err != nil {
// do a detailed explaination on how to version the software using git tags

Expand Down Expand Up @@ -195,7 +272,7 @@ func init() {
slog.Info("starting scan", "version", version, "asset", assetName)
// read the sbom file and post it to the scan endpoint
// get the flaws and print them to the console
file, err := generateSBOM()
file, err := generateSBOM(path)
if err != nil {
slog.Error("could not open file", "err", err)
return
Expand Down Expand Up @@ -253,11 +330,70 @@ func init() {
return
}

for _, f := range scanResponse.Flaws {
slog.Info("flaw found", "cve", f.CVEID, "package", f.ArbitraryJsonData["packageName"], "severity", f.CVE.Severity, "introduced", f.ArbitraryJsonData["introducedVersion"], "fixed", f.ArbitraryJsonData["fixedVersion"])
// order the flaws by their risk
slices.SortFunc(scanResponse.Flaws, func(a, b flaw.FlawDTO) int {
return int(*(a.RawRiskAssessment)*100) - int(*b.RawRiskAssessment*100)
})

// get the max risk of open!!! flaws
openRisks := utils.Map(utils.Filter(scanResponse.Flaws, func(f flaw.FlawDTO) bool {
return f.State == "open"
}), func(f flaw.FlawDTO) float64 {
return *f.RawRiskAssessment
})

maxRisk := 0.
for _, risk := range openRisks {
if risk > maxRisk {
maxRisk = risk
}
}

tw := table.NewWriter()
tw.AppendHeader(table.Row{"Library", "Vulnerability", "Risk", "Installed", "Fixed", "Status", "URL"})
tw.AppendRows(utils.Map(
scanResponse.Flaws,
func(f flaw.FlawDTO) table.Row {
cleanPkgName := strings.ReplaceAll(f.ArbitraryJsonData["packageName"].(string), "pkg:", "")
// remove the ecosystem from the package name
// pkg:ecosystem/package -> package
// pkg:ecosystem/package@version -> package
cleanPkgName = strings.Join(strings.Split(cleanPkgName, "/")[1:], "/")

clickableLink := fmt.Sprintf("\033]8;;%s/%s/flaws/%s\033\\View in Web UI\033]8;;\033\\", webUI, assetName, f.ID)
return table.Row{cleanPkgName, f.CVEID, *f.RawRiskAssessment, f.ArbitraryJsonData["installedVersion"], f.ArbitraryJsonData["fixedVersion"], f.State, clickableLink}
},
))

fmt.Println(tw.Render())

switch failOnRisk {
case "low":
if maxRisk > 0.1 {
os.Exit(1)
}
case "medium":
if maxRisk >= 4 {
os.Exit(1)
}

case "high":
if maxRisk >= 7 {
os.Exit(1)
}

case "critical":
if maxRisk >= 9 {
os.Exit(1)
}
}
},
})
}
scaCommand.Flags().String("path", ".", "The path to the project to scan. Defaults to the current directory.")
scaCommand.Flags().String("fail-on-risk", "critical", "The risk level to fail the scan on. Can be 'low', 'medium', 'high' or 'critical'. Defaults to 'critical'.")
scaCommand.Flags().String("webUI", "http://localhost:3000", "The url of the web UI to show the scan results in. Defaults to 'https://app.devguard.dev'.")

rootCmd.AddCommand(scaCommand)
}

func main() {
Expand Down
13 changes: 12 additions & 1 deletion cmd/devguard/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ func health(c echo.Context) error {
}

func Start(db core.DB) {
ory := auth.GetOryApiClient(os.Getenv("ORY_KRATOS"))
ory := auth.GetOryApiClient(os.Getenv("ORY_KRATOS_PUBLIC"))
oryAdmin := auth.GetOryApiClient(os.Getenv("ORY_KRATOS_ADMIN"))
casbinRBACProvider, err := accesscontrol.NewCasbinRBACProvider(db)

if err != nil {
Expand Down Expand Up @@ -304,6 +305,14 @@ func Start(db core.DB) {
}
})

apiV1Router.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c core.Context) error {
// set the ory admin client to the context
core.SetAuthAdminClient(c, oryAdmin)
return next(c)
}
})

apiV1Router.POST("/webhook/", integrationController.HandleWebhook)
// apply the health route without any session or multi tenant middleware
apiV1Router.GET("/health/", health)
Expand Down Expand Up @@ -333,6 +342,8 @@ func Start(db core.DB) {
tenantRouter.GET("/", orgController.Read, core.AccessControlMiddleware("organization", accesscontrol.ActionRead))

tenantRouter.GET("/metrics/", orgController.Metrics)

tenantRouter.GET("/members/", orgController.Members)
tenantRouter.GET("/integrations/finish-installation/", integrationController.FinishInstallation)
tenantRouter.GET("/integrations/repositories/", integrationController.ListRepositories)

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ services:
- postgresql
ports:
- '4433:4433' # public
# - '4434:4434' # admin
- '4434:4434' # admin
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
environment:
- DSN=postgres://kratos:secret@postgresql:5432/kratos?sslmode=disable
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/google/go-github/v62 v62.0.0
github.com/google/uuid v1.6.0
github.com/gosimple/slug v1.14.0
github.com/jedib0t/go-pretty/v6 v6.5.9
github.com/joho/godotenv v1.5.1
github.com/labstack/echo/v4 v4.12.0
github.com/lib/pq v1.10.9
Expand Down Expand Up @@ -73,10 +74,12 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/microsoft/go-mssqldb v1.7.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nD
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU=
github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
Expand Down Expand Up @@ -175,6 +177,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
Expand All @@ -199,6 +203,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
Expand Down
Loading

0 comments on commit 681e7b2

Please sign in to comment.