diff --git a/README.md b/README.md index 779685f..683c24d 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Flags: -o, --output string where to output SBOM results: (defaults to stdout when unspecified) -t, --tags strings tags to use when SBOMs are uploaded to Dependency Track (optional) -u, --upload-to-dependency-track whether to upload collected SBOMs to Dependency Track (default: false) + -g, --organization used to specify when using github app for token generation Use "subcommand [command] --help" for more information about a command. ``` diff --git a/build/Dockerfile b/build/Dockerfile index 7cf8e1f..7643f3e 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -20,8 +20,8 @@ RUN npm install -g @appthreat/cdxgen retire yarn bower && gem install bundler bu # Install golang WORKDIR /opt -RUN wget https://go.dev/dl/go1.21.1.linux-${TARGETARCH}.tar.gz \ - && tar -C /usr/local -xzf /opt/go1.21.1.linux-${TARGETARCH}.tar.gz && rm /opt/go1.21.1.linux-${TARGETARCH}.tar.gz +RUN wget https://go.dev/dl/go1.22.0.linux-${TARGETARCH}.tar.gz \ + && tar -C /usr/local -xzf /opt/go1.22.0.linux-${TARGETARCH}.tar.gz && rm /opt/go1.22.0.linux-${TARGETARCH}.tar.gz ENV PATH="/usr/local/go/bin:${PATH}" # Android SDK setup diff --git a/cmd/root.go b/cmd/root.go index e218099..e2d7292 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -36,6 +36,7 @@ const ( classifierFlag = "classifier" uploadToDTrackFlag = "upload-to-dependency-track" purgeCacheFlag = "purge-cache" + orgFlag = "organization" ) // ENV keys. @@ -127,6 +128,7 @@ func init() { uploadToDependencyTrackUsage = "whether to upload collected SBOMs to Dependency Track (default: false)" tagsUsage = "tags to use when SBOMs are uploaded to Dependency Track (optional)" purgeCacheUsage = "whether to purge gradle and go caches after a successful run (default: false)" + orgFlagUsage = "used when using organization github app" ) const classifierUsageTemplate = "classifier to use when uploading to Dependency Track. Valid values are: %s" @@ -142,6 +144,8 @@ func init() { rootCmd.PersistentFlags().BoolP(uploadToDTrackFlag, "u", false, uploadToDependencyTrackUsage) rootCmd.PersistentFlags().BoolP(purgeCacheFlag, "p", false, purgeCacheUsage) + + rootCmd.PersistentFlags().StringP(orgFlag, "g", "", orgFlagUsage) } func initConfig() { diff --git a/cmd/util.go b/cmd/util.go index 6b687c5..5e01de0 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -64,6 +64,14 @@ func createAppFromCLI(cmd *cobra.Command, verbose bool) (*app.App, error) { options = append(options, app.WithTags(tags)) + orgName, err := cmd.Flags().GetString(orgFlag) + if err != nil { + log.Warn("github app org won't be used as no org set") + } + if orgName != "" { + options = append(options, app.WithOrganization(orgName)) + } + outputFile, err := cmd.Flags().GetString(outputFlag) if err != nil { return nil, fmt.Errorf(errTemplate, outputFlag) diff --git a/go.mod b/go.mod index c2a75c0..bc84e34 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/vinted/sbomsftw go 1.21 -toolchain go1.21.1 +toolchain go1.22.0 require ( github.com/CycloneDX/cyclonedx-go v0.6.0 @@ -11,7 +11,7 @@ require ( github.com/go-git/go-git/v5 v5.7.0 github.com/google/uuid v1.3.1 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.5.0 + github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.8.4 ) @@ -29,6 +29,7 @@ require ( github.com/anchore/packageurl-go v0.1.1-0.20220428202044-a072fa3cb6d7 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/bmatcuk/doublestar/v4 v4.2.0 // indirect + github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/containerd v1.6.18 // indirect github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect @@ -49,25 +50,30 @@ require ( github.com/go-git/go-billy/v5 v5.4.1 // indirect github.com/go-restruct/restruct v1.2.0-alpha // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-containerregistry v0.11.0 // indirect + github.com/google/go-github/v62 v62.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.15 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jinzhu/copier v0.3.5 // indirect + github.com/k0kubun/pp/v3 v3.2.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.15.11 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/knqyf263/go-rpmdb v0.0.0-20220830120628-c11b1c45080a // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mholt/archiver/v3 v3.5.1 // indirect diff --git a/go.sum b/go.sum index ee47244..d93d66c 100644 --- a/go.sum +++ b/go.sum @@ -190,6 +190,8 @@ github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTS github.com/bmatcuk/doublestar/v4 v4.2.0 h1:Qu+u9wR3Vd89LnlLMHvnZ5coJMWKQamqdz9/p5GNthA= github.com/bmatcuk/doublestar/v4 v4.2.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 h1:R9d0v+iobRHSaE4wKUnXFiZp53AL4ED5MzgEMwGTZag= +github.com/bradleyfalzon/ghinstallation/v2 v2.11.0/go.mod h1:0LWKQwOHewXO/1acI6TtyE0Xc4ObDb2rFN7eHBAG71M= github.com/bradleyjkemp/cupaloy/v2 v2.7.0 h1:AT0vOjO68RcLyenLCHOGZzSNiuto7ziqzq6Q1/3xzMQ= github.com/bradleyjkemp/cupaloy/v2 v2.7.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= @@ -368,6 +370,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -536,6 +539,8 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -590,12 +595,16 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-containerregistry v0.7.0/go.mod h1:2zaoelrL0d08gGbpdP3LqyUuBmhWbpD6IOe2s9nLS2k= github.com/google/go-containerregistry v0.11.0 h1:Xt8x1adcREjFcmDoDK8OdOsjxu90PHkGuwNP8GiHMLM= github.com/google/go-containerregistry v0.11.0/go.mod h1:BBaYtsHPHA42uEgAvd/NejvAfPSlz281sJWqupjSxfk= +github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= +github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -713,8 +722,8 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= @@ -742,6 +751,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= +github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -802,6 +813,8 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -1091,8 +1104,9 @@ github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -1162,6 +1176,8 @@ github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlI github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= github.com/vifraa/gopom v0.2.0 h1:GaLxNleCvIFC6kUwWMDu2mRu7W8u2f0AFUMTwr9koSs= github.com/vifraa/gopom v0.2.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o= +github.com/vinted/go-gha-token-generate v1.0.2 h1:zUrDhnY34r91xHv0lTsoJ9psWcYfTaeGKw0qL0HvPso= +github.com/vinted/go-gha-token-generate v1.0.2/go.mod h1:WAdYojW7K4MJ1KxCJTuw+iiCrzBlqz3K3JyLxwEQPGI= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= diff --git a/internal/app/app.go b/internal/app/app.go index 6c683e9..5223cc1 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -25,11 +25,11 @@ import ( ) type App struct { - outputFile string - tags []string - githubUsername, githubAPIToken string // TODO Move later on to a separate GitHub client - dependencyTrackClient *dtrack.DependencyTrackClient - purgeCache bool + outputFile string + tags []string + githubUsername, githubAPIToken, organization string // TODO Move later on to a separate GitHub client + dependencyTrackClient *dtrack.DependencyTrackClient + purgeCache bool } type SBOMsFromFilesystemConfig struct { @@ -39,10 +39,10 @@ type SBOMsFromFilesystemConfig struct { } type options struct { - tags []string - githubUsername, githubAPIToken string // TODO Move later on to a separate GitHub client - dependencyTrackClient *dtrack.DependencyTrackClient - purgeCache bool + tags []string + githubUsername, githubAPIToken, organization string // TODO Move later on to a separate GitHub client + dependencyTrackClient *dtrack.DependencyTrackClient + purgeCache bool } type Option func(options *options) error @@ -104,6 +104,13 @@ func WithTags(tags []string) Option { } } +func WithOrganization(orgName string) Option { + return func(options *options) error { + options.organization = orgName + return nil + } +} + func New(outputFile string, opts ...Option) (*App, error) { var options options for _, opt := range opts { @@ -125,6 +132,8 @@ func New(outputFile string, opts ...Option) (*App, error) { app.purgeCache = options.purgeCache app.dependencyTrackClient = options.dependencyTrackClient + app.organization = options.organization + return app, nil } @@ -174,7 +183,10 @@ func (a App) SBOMsFromOrganization(organizationURL string, delayAmount uint16) { processing next repository. */ - collectSBOMsFromRepositories := func(repositoryURLs []string) { + collectSBOMsFromRepositories := func(repositoryURLs []string, apiToken string) { + if apiToken != a.githubAPIToken && apiToken != "" { + a.githubAPIToken = apiToken + } for idx, repositoryURL := range repositoryURLs { if idx == 0 { a.sbomsFromRepositoryInternal(ctx, repositoryURL) @@ -193,7 +205,7 @@ func (a App) SBOMsFromOrganization(organizationURL string, delayAmount uint16) { } } - c := internal.NewGetRepositoriesConfig(ctx, organizationURL, a.githubUsername, a.githubAPIToken) + c := internal.NewGetRepositoriesConfig(ctx, organizationURL, a.githubUsername, a.githubAPIToken, a.organization) err := internal.WalkRepositories(c, collectSBOMsFromRepositories) if err != nil && !errors.Is(err, context.Canceled) { @@ -267,22 +279,37 @@ func (a App) SBOMsFromFilesystem(config *SBOMsFromFilesystemConfig) { // sbomsFromRepositoryInternal collect SBOMs from a single repository, given the VCS URL of the repository. func (a App) sbomsFromRepositoryInternal(ctx context.Context, repositoryURL string) { + var repo *repository.Repository + var err error + deleteRepository := func(repositoryPath string) { if err := os.RemoveAll(repositoryPath); err != nil { log.WithError(err).Errorf("can't remove repository at: %s", repositoryPath) } } - repo, err := repository.New(ctx, repositoryURL, repository.Credentials{ + repo, err = repository.New(ctx, repositoryURL, repository.Credentials{ Username: a.githubUsername, AccessToken: a.githubAPIToken, }) if errors.Is(err, context.Canceled) { return } else if err != nil { + // If error is not null, we try to get new token and assign it to github API token log.WithError(err).Errorf("can't clone %s", repositoryURL) - - return + token, errToken := internal.RegenerateGithubToken(a.organization) + if errToken != nil { + log.WithError(errToken).Errorf("can't generate github token") + } + a.githubAPIToken = token + repo, err = repository.New(ctx, repositoryURL, repository.Credentials{ + Username: a.githubUsername, + AccessToken: a.githubAPIToken, + }) + // If err is still here after we attempt to regen, return + if err != nil { + log.WithError(err).Errorf("could not fetch after regenerated token %s", repositoryURL) + } } defer deleteRepository(repo.FSPath) diff --git a/internal/requests.go b/internal/requests.go index 44f28f1..364180e 100644 --- a/internal/requests.go +++ b/internal/requests.go @@ -10,6 +10,9 @@ import ( "strconv" "time" + log "github.com/sirupsen/logrus" + gh "github.com/vinted/sbomsftw/pkg/github" + "github.com/vinted/sbomsftw/pkg" ) @@ -22,17 +25,18 @@ type BackoffConfig struct { type GetRepositoriesConfig struct { BackoffConfig - ctx context.Context - URL, Username, APIToken string - IncludeArchivedRepositories bool + ctx context.Context + URL, Username, APIToken, Organization string + IncludeArchivedRepositories bool } -func NewGetRepositoriesConfig(ctx context.Context, url, username, apiToken string) GetRepositoriesConfig { +func NewGetRepositoriesConfig(ctx context.Context, url, username, apiToken string, org string) GetRepositoriesConfig { return GetRepositoriesConfig{ ctx: ctx, URL: url, Username: username, APIToken: apiToken, + Organization: org, IncludeArchivedRepositories: false, BackoffConfig: BackoffConfig{ RequestTimeout: defaultRequestTimeout * time.Second, // Good defaults @@ -147,7 +151,11 @@ func GetRepositories(conf GetRepositoriesConfig) ([]repositoryMapping, error) { return exponentialBackoff(getRepositories, conf.BackoffPolicy...) } -func WalkRepositories(conf GetRepositoriesConfig, callback func(repositoryURLs []string)) error { +func WalkRepositories(conf GetRepositoriesConfig, callback func(repositoryURLs []string, apiToken string)) error { + var repositories []repositoryMapping + var err error + regenCount := 0 + endpoint, err := url.Parse(conf.URL) if err != nil { return fmt.Errorf("can't walk repository with malformed URL - %s: %w", conf.URL, err) @@ -160,20 +168,52 @@ func WalkRepositories(conf GetRepositoriesConfig, callback func(repositoryURLs [ endpoint.RawQuery = query.Encode() conf.URL = endpoint.String() - repositories, err := GetRepositories(conf) + repositories, err = GetRepositories(conf) if err != nil { - return fmt.Errorf("repository walking failed: %w", err) + if regenCount < 1 { + token, errToken := RegenerateGithubToken(conf.Organization) + if errToken != nil { + return fmt.Errorf("repository walking regen failed: %w", errToken) + } + // Regenerate the token and retry + conf.APIToken = token + + // Try fetching the repositories again with the new token + repositories, err = GetRepositories(conf) + if err != nil { + return fmt.Errorf("repository walking after regen failed: %w", err) + } + regenCount += 1 + } else { + return fmt.Errorf("repository walking failed: %w", err) + } + } else { + // Reset regen count upon a successful fetch + regenCount = 0 } + if len(repositories) == 0 { - return nil // Done all repositories have been walked + return nil // Done, all repositories have been walked } + var repositoryURLs []string for _, r := range repositories { repositoryURLs = append(repositoryURLs, r.URL) } - callback(repositoryURLs) + callback(repositoryURLs, conf.APIToken) + // reset regen count page++ } } -// UploadBOM uploads BOM to Dependency Track based on the configuration given +func RegenerateGithubToken(org string) (string, error) { + log.Infof("Trying to generate github token for %s", org) + if org == "" { + return "", fmt.Errorf("no org specified to regen") + } + token := gh.GenerateGithubAppTokenInternal(org) + if token == "" { + return "", fmt.Errorf("could not fetch new github app token") + } + return token, nil +} diff --git a/internal/requests_test.go b/internal/requests_test.go index b5760f8..5d6d34c 100644 --- a/internal/requests_test.go +++ b/internal/requests_test.go @@ -142,7 +142,7 @@ func TestWalkRepositories(t *testing.T) { var collectedRepos []string reqConf := createGetRepositoriesConfig(goodResponseServer.URL) - err := WalkRepositories(reqConf, func(repos []string) { + err := WalkRepositories(reqConf, func(repos []string, apiToken string) { collectedRepos = append(collectedRepos, repos...) }) require.NoError(t, err) diff --git a/pkg/github/generate.go b/pkg/github/generate.go new file mode 100644 index 0000000..9f070a8 --- /dev/null +++ b/pkg/github/generate.go @@ -0,0 +1,71 @@ +package github + +import ( + "context" + "encoding/base64" + "net/http" + "os" + "strconv" + "strings" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/bradleyfalzon/ghinstallation/v2" +) + +func GenerateGithubAppTokenInternal(org string) string { + appID, installID, privateKeyB64 := fetchEnvVars(org) + + key, err := base64.StdEncoding.DecodeString(privateKeyB64) + if err != nil { + log.Fatalf("Failed to decode private key: %v", err) + } + + itr, err := ghinstallation.New(http.DefaultTransport, appID, installID, key) + if err != nil { + log.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + token, err := itr.Token(ctx) + if err != nil { + log.Fatalf("unable to get github token: %s", err) + } + + return token +} + +func fetchEnvVars(org string) (int64, int64, string) { + var prefix string + if org != "" { + prefix = strings.ToUpper(org) + "_" + } + + appID := getInt64EnvVar(prefix + "APP_ID") + installID := getInt64EnvVar(prefix + "INSTALLATION_ID") + privateKeyB64 := os.Getenv(prefix + "GH_APP_KEY") + + if privateKeyB64 == "" { + log.Printf("Environment variable %sGH_APP_KEY not set", prefix) + } + + return appID, installID, privateKeyB64 +} + +func getInt64EnvVar(envVar string) int64 { + value := os.Getenv(envVar) + if value == "" { + log.Printf("Environment variable %s not set", envVar) + return -1 // Or any other sentinel value to indicate missing variable + } + + parsedValue, err := strconv.ParseInt(value, 10, 64) + if err != nil { + log.Printf("Failed to convert %s to int64: %v", envVar, err) + return 0 // Or any other sentinel value to indicate parsing failure + } + return parsedValue +}