diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index dfa1e02..1891598 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,6 @@ "customizations": { "vscode": { "settings": { - "editor.formatOnSave": true, "editor.fontFamily": "'0xProto Nerd Font','Courier New', monospace", "terminal.integrated.fontFamily": "'0xProto Nerd Font','Courier New', monospace", "notebook.output.fontFamily" : "'0xProto Nerd Font','Courier New', monospace", @@ -45,12 +44,17 @@ "source.organizeImports": "always" } }, - "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[markdown]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, + "window.menuBarVisibility": "classic", + "workbench.activityBar.location": "top", + "debug.toolBarLocation": "docked", + "workbench.colorTheme": "Default Light Modern", + "editor.useTabStops": true, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "git.autofetch": true, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, "markiscodecoverage.coverageThreshold": 95, "markiscodecoverage.enableOnStartup": true, "markiscodecoverage.searchCriteria": "*.lcov*" @@ -66,7 +70,7 @@ "jebbs.plantuml", "foxundermoon.shell-format", "ahebrank.yaml2json", - "AmazonWebServices.amazon-q-vscode", + "Github.copilot", "markis.code-coverage", "Gruntfuggly.todo-tree", "esbenp.prettier-vscode" diff --git a/.vscode/launch.json b/.vscode/launch.json index 695a3e4..b43993c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,32 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Debug File (gad)", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "args": [ + "--provider=gad", + "--debug=true", + "scan", + "./_TESTDATA_/sbom/small.cyclonedx.json" + ] + }, + { + "name": "Debug File (ossindex)", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "args": [ + "--provider=ossindex", + "--debug=true", + "scan", + "./_TESTDATA_/sbom/small.cyclonedx.json" + ] + }, { "name": "Debug File (ossindex - railsgoat - AI Output)", "type": "go", diff --git a/_TESTDATA_/sbom/small.cyclonedx.json b/_TESTDATA_/sbom/small.cyclonedx.json new file mode 100644 index 0000000..593b511 --- /dev/null +++ b/_TESTDATA_/sbom/small.cyclonedx.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", + "version": 1, + "components": [ + { + "purl": "pkg:pypi/pycrypto@2.6.1" + }, + { + "purl": "pkg:pypi/pycryptopayapi@0.0.8" + }, + { + "purl": "pkg:npm/body-parser@1.19.0" + }, + { + "purl": "pkg:npm/qs@6.7.0" + } + ] +} \ No newline at end of file diff --git a/cmd/scan.go b/cmd/scan.go index 9e458f8..23d4128 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -61,7 +61,7 @@ func init() { scanCmd.PersistentFlags().StringVar(&scanner.Credentials.Username, "username", "", "the user name for the provider being used.") scanCmd.PersistentFlags().StringVar(&scanner.Credentials.ProviderToken, "token", "", "the API token for the provider being used.") scanCmd.PersistentFlags().StringVar(&scanner.Credentials.OpenAIAPIKey, "openai-api-key", "", "an OpenAI API key used for generating AI output. AI Reports are EXPERIMENTAL.") - scanCmd.PersistentFlags().StringVar(&scanner.ProviderName, "provider", "osv", "the vulnerability provider (ossindex, osv).") + scanCmd.PersistentFlags().StringVar(&scanner.ProviderName, "provider", "osv", "the vulnerability provider (ossindex, osv, snyk, github).") scanCmd.PersistentFlags().StringVar(&scanner.IgnoreFile, "ignore-file", "", "an optional file containing CVEs to ignore when rendering output.") scanCmd.PersistentFlags().StringVar(&scanner.Severity, "severity", "", "anything equal to or above this severity will be returned with non-zero error code.") scanCmd.PersistentFlags().BoolVar(&scanner.ExitCode, "exitcode", false, "if set will return an exit code representing the highest severity detected.") diff --git a/go.mod b/go.mod index e303d28..9f9c2b4 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.22 toolchain go1.22.2 require ( - github.com/CycloneDX/cyclonedx-go v0.9.0 + github.com/CycloneDX/cyclonedx-go v0.9.1 github.com/briandowns/spinner v1.23.1 github.com/devops-kung-fu/common v0.2.6 - github.com/go-resty/resty/v2 v2.14.0 + github.com/go-resty/resty/v2 v2.15.0 github.com/gomarkdown/markdown v0.0.0-20240730141124-034f12af3bf6 github.com/google/go-github v17.0.0+incompatible github.com/gookit/color v1.5.4 @@ -32,15 +32,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // 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.16 // indirect + github.com/mattn/go-runewdth v0.0.16 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sashabaranov/go-openai v1.28.2 - github.com/spf13/pflag v1.0.5 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) + github.com/sashabaranov/go-openai v1.29.2 diff --git a/go.sum b/go.sum index 7581ffd..1ebc568 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/CycloneDX/cyclonedx-go v0.9.0 h1:inaif7qD8bivyxp7XLgxUYtOXWtDez7+j72qKTMQTb8= -github.com/CycloneDX/cyclonedx-go v0.9.0/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw= +github.com/CycloneDX/cyclonedx-go v0.9.1 h1:yffaWOZsv77oTJa/SdVZYdgAgFioCeycBUKkqS2qzQM= +github.com/CycloneDX/cyclonedx-go v0.9.1/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= @@ -13,13 +13,12 @@ github.com/devops-kung-fu/common v0.2.6 h1:HNL9suXELXHiSg7Ze0VinNkbngrBjovKYWPOc github.com/devops-kung-fu/common v0.2.6/go.mod h1:ZLp6W5ewDWxmx45KF/Oj3IfJ3EhRALBkcfqLQnz23OU= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/go-resty/resty/v2 v2.14.0 h1:/rhkzsAqGQkozwfKS5aFAbb6TyKd3zyFRWcdRXLPCAU= -github.com/go-resty/resty/v2 v2.14.0/go.mod h1:IW6mekUOsElt9C7oWr0XRt9BNSD6D5rr9mhk6NjmNHg= +github.com/go-resty/resty/v2 v2.15.0 h1:clPQLZ2x9h4yGY81IzpMPnty+xoGyFaDg0XMkCsHf90= +github.com/go-resty/resty/v2 v2.15.0/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/gomarkdown/markdown v0.0.0-20240730141124-034f12af3bf6 h1:ZPy+2XJ8u0bB3sNFi+I72gMEMS7MTg7aZCCXPOjV8iw= github.com/gomarkdown/markdown v0.0.0-20240730141124-034f12af3bf6/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -55,8 +54,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sashabaranov/go-openai v1.28.2 h1:Q3pi34SuNYNN7YrqpHlHbpeYlf75ljgHOAVM/r1yun0= -github.com/sashabaranov/go-openai v1.28.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sashabaranov/go-openai v1.29.2 h1:jYpp1wktFoOvxHnum24f/w4+DFzUdJnu83trr5+Slh0= +github.com/sashabaranov/go-openai v1.29.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -75,83 +74,20 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/providers/gad/gad.go b/providers/gad/gad.go new file mode 100644 index 0000000..f676168 --- /dev/null +++ b/providers/gad/gad.go @@ -0,0 +1,163 @@ +// Package gad contains functionality to retrieve vulnerability information from the GitHub Advisory Database +package gad + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "os" + "strings" + + "github.com/package-url/packageurl-go" + + "github.com/devops-kung-fu/bomber/models" +) + +//var client *resty.Client + +func init() { + //client = resty.New(). + // SetTransport(&http.Transport{TLSHandshakeTimeout: 60 * time.Second}) +} + +// Provider represents the OSSIndex provider +type Provider struct{} + +// Info provides basic information about the GAD Provider +func (Provider) Info() string { + return "GitHub Advisory Database (https://github.com/advisories)" +} + +func (Provider) Scan(purls []string, credentials *models.Credentials) (packages []models.Package, err error) { + if err = validateCredentials(credentials); err != nil { + return + } + + for _, purl := range purls { + response, e := queryGitHubAdvisories(purl, *credentials) + if e != nil { + return nil, e + } + + for _, edge := range response.Data.SecurityVulnerabilities.Edges { + log.Printf("Vulnerabilities for %s:\n", purl) + advisory := edge.Node.Advisory + log.Printf("Summary: %s\n", advisory.Summary) + log.Printf("Severity: %s\n", advisory.Severity) + for _, identifier := range advisory.Identifiers { + if identifier.Type == "CVE" { + fmt.Printf("CVE: %s\n", identifier.Value) + } + } + log.Println("---") + } + } + return +} + +const githubGraphQLEndpoint = "https://api.github.com/graphql" + +type GraphQLRequest struct { + Query string `json:"query"` + Variables map[string]interface{} `json:"variables"` +} + +type GraphQLResponse struct { + Data struct { + SecurityVulnerabilities struct { + Edges []struct { + Node struct { + Advisory struct { + Identifiers []struct { + Type string `json:"type"` + Value string `json:"value"` + } `json:"identifiers"` + Summary string `json:"summary"` + Description string `json:"description"` + Severity string `json:"severity"` + } `json:"advisory"` + } `json:"node"` + } `json:"edges"` + } `json:"securityVulnerabilities"` + } `json:"data"` +} + +func queryGitHubAdvisories(purl string, credentials models.Credentials) (*GraphQLResponse, error) { + p, err := packageurl.FromString(purl) + if err != nil { + return nil, fmt.Errorf("invalid PURL: %v", err) + } + + query := ` + query($ecosystem: SecurityAdvisoryEcosystem!, $package: String!) { + securityVulnerabilities(ecosystem: $ecosystem, package: $package, first: 100) { + edges { + node { + advisory { + identifiers { + type + value + } + summary + description + severity + } + } + } + } + } + ` + + variables := map[string]interface{}{ + "ecosystem": strings.ToUpper(p.Type), + "package": p.Name, + } + + requestBody, err := json.Marshal(GraphQLRequest{Query: query, Variables: variables}) + if err != nil { + return nil, fmt.Errorf("error marshalling request: %v", err) + } + + req, err := http.NewRequest("POST", githubGraphQLEndpoint, bytes.NewBuffer(requestBody)) + if err != nil { + return nil, fmt.Errorf("error creating request: %v", err) + } + + req.Header.Set("Authorization", "Bearer "+credentials.ProviderToken) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("error sending request: %v", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response: %v", err) + } + + var graphQLResponse GraphQLResponse + err = json.Unmarshal(body, &graphQLResponse) + if err != nil { + return nil, fmt.Errorf("error unmarshalling response: %v", err) + } + + return &graphQLResponse, nil +} + +func validateCredentials(credentials *models.Credentials) (err error) { + if credentials.ProviderToken == "" { + credentials.ProviderToken = os.Getenv("GITHUB_TOKEN") + } + + if credentials.ProviderToken == "" { + err = errors.New("bomber requires an GitHub PAT to utilize the GitHub Advisory Database") + } + return +} diff --git a/providers/gad/gad_test.go b/providers/gad/gad_test.go new file mode 100644 index 0000000..7cd8d2e --- /dev/null +++ b/providers/gad/gad_test.go @@ -0,0 +1,13 @@ +package gad + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInfo(t *testing.T) { + provider := Provider{} + info := provider.Info() + assert.Equal(t, "GitHub Advisory Database (https://github.com/advisories)", info) +} diff --git a/providers/providerfactory.go b/providers/providerfactory.go index 51c4c0b..853cf5a 100644 --- a/providers/providerfactory.go +++ b/providers/providerfactory.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/devops-kung-fu/bomber/models" + "github.com/devops-kung-fu/bomber/providers/gad" "github.com/devops-kung-fu/bomber/providers/ossindex" "github.com/devops-kung-fu/bomber/providers/osv" "github.com/devops-kung-fu/bomber/providers/snyk" @@ -19,6 +20,8 @@ func NewProvider(name string) (provider models.Provider, err error) { provider = osv.Provider{} case "snyk": provider = snyk.Provider{} + case "github": + provider = gad.Provider{} default: err = fmt.Errorf("%s is not a valid provider type", name) } diff --git a/providers/providerfactory_test.go b/providers/providerfactory_test.go index 5519084..aae1950 100644 --- a/providers/providerfactory_test.go +++ b/providers/providerfactory_test.go @@ -8,6 +8,7 @@ import ( "github.com/devops-kung-fu/bomber/providers/ossindex" "github.com/devops-kung-fu/bomber/providers/osv" "github.com/devops-kung-fu/bomber/providers/snyk" + "github.com/devops-kung-fu/bomber/providers/gad" ) func TestNewProvider(t *testing.T) { @@ -23,6 +24,10 @@ func TestNewProvider(t *testing.T) { assert.NoError(t, err) assert.IsType(t, snyk.Provider{}, provider) + provider, err = NewProvider("gad") + assert.NoError(t, err) + assert.IsType(t, gad.Provider{}, provider) + _, err = NewProvider("test") assert.Error(t, err) }